Files
xpamych 1d679f6919 Устранено дублирование опроса устройств между сервисом и интерфейсом
Добавлено:
- передача состояния устройств и команд управления между сервисом и графическим интерфейсом
- автоматическое восстановление связи интерфейса с сервисом

Изменено:
- графический интерфейс использует единый запущенный сервис вместо собственного опроса устройств

Исправлено:
- одновременный опрос устройств сервисом и графическим интерфейсом

Удалено:
- отдельный фоновый опрос устройств в графическом интерфейсе
2026-10-27 21:12:35 +03:00

18 KiB
Raw Permalink Blame History

AGENTS.md — LGCompanion

Этот файл предназначен для ИИ-ассистентов, работающих с проектом. Здесь собрана архитектурная и практическая информация, необходимая для внесения изменений.

Обзор проекта

LGCompanion — Linux-приложение на Python для автоматического переключения аудио профиля USB-гарнитур при работе с PipeWire. Из коробки поддерживаются гарнитуры SteelSeries Arctis, но программа настраивается под любое HID-совместимое USB-аудиоустройство.

  • Когда наушники включены и связались с USB-донглом, приложение переводит звук на виртуальный 7.1 sink с HRTF-сверткой.
  • Когда наушники выключены или потеряли связь, звук переключается на прямой стерео sink донгла.

Приложение состоит из:

  • CLI-демона lgcompanion;
  • отдельного Qt GUI lgcompanion-gui (иконка в трее, окно настроек, автозапуск);
  • встроенного PipeWire-конфига filter-chain для виртуального surround.

Технологический стек

  • Язык: Python 3.11+.
  • Сборка и пакетирование: Hatchling (pyproject.toml).
  • Основные зависимости:
    • tomli-w>=1.0 — запись TOML-конфига.
  • Опциональные зависимости GUI:
    • PySide6>=6.5.
  • Внешние системные зависимости времени выполнения:
    • PipeWire + pactl (PulseAudio-совместимый CLI);
    • права на чтение/запись /dev/hidraw* для HID-опроса донгла.

Структура репозитория

.
├── pyproject.toml                    # Метаданные пакета, зависимости, точки входа
├── README.md                         # Пользовательская документация (русский язык)
├── LICENSE                           # Лицензия MIT
├── install.sh                        # Инсталлятор: editable install, PipeWire-конфиг, сервис, .desktop
├── install/
│   ├── lgcompanion.desktop     # Шаблон .desktop для меню приложений
│   └── lgcompanion.service     # Шаблон systemd user service
├── tests/                            # pytest-тесты
├── src/lgcompanion/
│   ├── __init__.py                   # Версия пакета (2.0.0)
│   ├── config.py                     # Модели конфигурации, загрузка/сохранение TOML
│   ├── cli.py                        # Аргументы и команды CLI
│   ├── engine.py                     # Демон опроса HID и переключения sink
│   ├── hid_backend.py                # Поиск hidraw-устройств, запрос батареи, диагностика
│   ├── known_devices.py              # База известных гарнитур и автообнаружение по VID/PID
│   ├── pipewire.py                   # Утилиты для pactl: переключение sink, перемещение input
│   ├── surround.py                   # Генерация PipeWire-конфига surround, симлинк, перезапуск PipeWire
│   ├── data/
│   │   ├── hrir.wav                  # HRIR-импульс (HeSuVi 14 каналов, 48 кГц)
│   │   ├── hrir-eac-default-backup.wav
│   │   └── virtual-surround-7.1.conf.template
│   └── gui/
│       ├── app.py                    # Точка входа GUI: трей, иконка, автозапуск
│       ├── main_window.py            # Главное окно: sidebar, список устройств и панель настроек выбранного устройства
│       ├── sidebar.py                # Боковая панель навигации
│       ├── device_card.py            # Выбираемая карточка устройства в списке
│       ├── device_detector.py        # Диалог автообнаружения подключенных наушников
│       ├── device_editor.py          # Диалог добавления/редактирования профиля
│       ├── hrir_manager.py           # Диалог управления HRIR-профилями
│       ├── ipc_client.py             # Qt-клиент состояния и команд сервиса
│       └── styles.py                 # QSS-стили интерфейса

Архитектура выполнения

CLI-демон (lgcompanion run)

  1. Загружает конфиг ~/.config/lgcompanion/config.toml.
  2. Периодически (по умолчанию раз в 2 секунды) ищет /dev/hidraw* узел, соответствующий одному из включённых профилей устройств.
  3. Отправляет HID-запрос battery_request (по умолчанию 06 18) и ждёт ответ.
  4. Если ответ получен и байт заряда в диапазоне 1–100 — наушники подключены:
    • переключает default sink на sinks.surround;
    • перенаправляет выход фильтра sinks.filter_output на sinks.stereo.
  5. Если таймаут или байт заряда некорректен — наушники отключены:
    • переключает default sink на sinks.stereo.

Qt GUI (lgcompanion-gui)

  • Создаёт QSystemTrayIcon с динамически рисуемой иконкой (цвет зависит от заряда и статуса).
  • Подключается к Unix-сокету CLI-демона через gui/ipc_client.py; собственный Engine и HID-опрос не запускает.
  • Получает от сервиса snapshot и события, отображает статус, заряд и режим в меню трея; при недоступном сервисе периодически переподключается.
  • Позволяет вручную переключать surround/stereo/auto через трей; режим сохраняется в активное (или первое включённое) устройство.
  • Открывает окно настроек, позволяет управлять автозапуском (~/.config/autostart/lgcompanion-gui.desktop).
  • В окне настроек выбор устройства открывает его персональные настройки: включение/отключение, режим работы, HRIR для устройства, редактирование/удаление профиля.
  • При запуске выполняет автообнаружение известных гарнитур (known_devices.py) по VID/PID/USB-интерфейсу и добавляет отсутствующие профили в конфиг. Дубликаты по VID/PID/interface не создаются.

PipeWire-конфиг

  • Шаблон data/virtual-surround-7.1.conf.template содержит плейсхолдер {hrir_path}.
  • Модуль surround.py генерирует конфиг в ~/.config/lgcompanion/virtual-surround.conf и создаёт симлинк ~/.config/pipewire/pipewire.conf.d/lgcompanion-virtual-surround.conf (apply_hrir_config).
  • Генерация вызывается автоматически при смене активного HRIR: из CLI (hrir add, когда профиль стал активным; hrir set-default; hrir remove активного) и из GUI (после закрытия менеджера HRIR, если активный профиль изменился). После генерации PipeWire перезапускается (systemctl --user restart pipewire pipewire-pulse wireplumber), поэтому звук кратковременно прерывается.
  • Если активный HRIR не задан или файл удалён, симлинк удаляется, чтобы виртуальный sink исчез вместо молчаливого пропуска модуля (flags = [ nofail ] в шаблоне).
  • Ручное применение: lgcompanion hrir apply (флаг --no-restart пропускает перезапуск PipeWire).
  • В пользовательском режиме install.sh вызывает lgcompanion hrir apply --no-restart; в системном режиме (PREFIX=/usr) конфиг устанавливается в $PREFIX/share/pipewire/pipewire.conf.d/.
  • При генерации также удаляется устаревший симлинк headset-companion-virtual-surround.conf (миграция после ребрендинга).

Конфигурация

Конфиг хранится в ~/.config/lgcompanion/config.toml.

Основные секции:

  • general — poll_interval, log_level, show_battery, theme (auto/light/dark).
  • devices — список профилей DeviceProfile (vid/pid, HID-интерфейс, запрос/префикс батареи, подстроки HID-имени, флаг enabled, режим mode: auto/surround/stereo, опциональный hrir_file).
  • sinks — имена PipeWire/PulseAudio sink: surround, stereo, filter_output. Пустые значения автоматически заменяются на значения по умолчанию, чтобы демон не вызывал pactl с пустым именем.
  • hrir — список profiles (имя + путь) и активный file.

Если конфига нет, Config.load() создаёт файл с пустым списком устройств. Пользователь добавляет профили через автообнаружение подключённых hidraw-устройств (GUI/CLI) или вручную через редактор профиля.

Сборка и установка

Локальная установка (рекомендуемый способ)

cd ~/Projects/lgcompanion
./install.sh
systemctl --user restart pipewire pipewire-pulse wireplumber
systemctl --user start lgcompanion.service

install.sh:

  • проверяет Python 3.11+;
  • в пользовательском режиме (PREFIX=$HOME/.local) устанавливает пакет в editable-режиме (pip3 --user или uv pip install -e . в активном venv);
  • в системном режиме (PREFIX=/usr или /usr/local) устанавливает пакет через pip3 install --root=DESTDIR --prefix=PREFIX;
  • генерирует PipeWire-конфиг (пользовательский симлинк или системный файл);
  • в пользовательском режиме создаёт дефолтный конфиг приложения;
  • устанавливает systemd user service из шаблона install/lgcompanion.service;
  • создаёт .desktop файл в ~/.local/share/applications/ (пользовательский) или $PREFIX/share/applications/ (системный).

Для подготовки пакета используйте:

DESTDIR="$(pwd)/pkgroot" PREFIX=/usr ./install.sh

Сборка wheel/sdist

python -m pip install build
python -m build

Используется hatchling. В pyproject.toml:

  • точки входа: lgcompanion → lgcompanion.cli:main, lgcompanion-gui → lgcompanion.gui.app:main;
  • GUI-зависимость доступна как pip install lgcompanion[gui];
  • dev-зависимости (включая GUI и pytest) доступны как pip install lgcompanion[dev].

Запуск тестов

python -m pip install -e ".[dev]"
python -m pytest

Команды для разработки

CLI

lgcompanion run -v                # демон с подробным логом
lgcompanion run --once            # одиночная проверка
lgcompanion run --force-surround  # принудительно surround
lgcompanion run --force-stereo    # принудительно stereo
lgcompanion diagnose              # сырые HID-ответы
lgcompanion list-hid              # список hidraw-устройств
lgcompanion config show           # показать конфиг
lgcompanion service install       # установить systemd user service
lgcompanion service status        # статус сервиса

GUI

lgcompanion-gui

Проверка синтаксиса

python3 -m py_compile src/lgcompanion/*.py src/lgcompanion/gui/*.py tests/*.py

Тестирование

Автоматические тесты расположены в tests/ и запускаются через pytest:

python -m pytest

Тесты мокируют subprocess.run, os.open, select.select и файловую систему sysfs.

Дополнительно tests/test_styles.py содержит smoke-тест для GUI: он создаёт QApplication на offscreen-платформе, применяет QSS каждой темы и перехватывает сообщения Qt через qInstallMessageHandler. Это позволяет автоматически ловить ошибки QSS (Unknown property ..., Could not parse ...), которые Python-линтеры не видят, так как QSS — обычные строки.

Ручное тестирование:

  1. Убедиться, что lgcompanion list-hid видит донгл.
  2. Запустить lgcompanion diagnose и проверить HID-ответы в включённом/выключенном состоянии наушников.
  3. Запустить lgcompanion run -v и убедиться, что default sink меняется между sinks.surround и sinks.stereo.
  4. Проверить GUI: иконка трея, окно настроек, ручное переключение режимов, автозапуск.

Стиль кода и соглашения

  • В каждом модуле первой строкой: from __future__ import annotations.
  • Используются pathlib.Path, dataclasses, typing (например, Literal, NamedTuple).
  • Все пути к bundled-данным разрешаются через importlib.resources.files("lgcompanion"), с fallback на PROJECT_ROOT / "data".
  • Интерфейсные строки и пользовательские сообщения пишутся на русском языке.
  • Логирование использует стандартный logging с форматом %(asctime)s [%(levelname)s] %(message)s.
  • Имена PipeWire sink вынесены в конфиг, а не захардкожены.
  • Весь QSS-стиль GUI централизован в src/lgcompanion/gui/styles.py. Inline-стили (setStyleSheet(...) с литералами) запрещены; для динамических состояний используются objectName и динамические свойства (setProperty) с последующим style().unpolish/.polish.

Безопасность

  • Приложение работает от имени обычного пользователя (systemd user service).
  • Для HID-опроса требуется доступ к /dev/hidraw*. Убедитесь, что у пользователя достаточно прав (udev-правила или членство в соответствующей группе); в противном случае os.open() вернёт PermissionError.
  • CLI и GUI используют subprocess.run(["pactl", ...]) с английской локалью (LC_ALL=C) для стабильного парсинга вывода.
  • Приложение не использует сетевых соединений и не хранит чувствительных данных.
  • В пользовательском режиме конфиг и сервис пишутся в домашний каталог пользователя (~/.config, ~/.local). Не запускайте ./install.sh с sudo.
  • В системном режиме (PREFIX=/usr) install.sh предназначен для подготовки пакета внутри DESTDIR и должен запускаться при сборке пакета, а не на целевой системе.

Развёртывание

  • Основной способ доставки: исходный репозиторий + ./install.sh.
  • Альтернатива: сборка wheel/sdist через python -m build и установка через pip.
  • Для системного пакетирования используйте DESTDIR + PREFIX=/usr и шаблоны из install/.
  • systemd user service зависит от pipewire.service и pipewire-pulse.service.
  • Для автозапуска GUI используется ~/.config/autostart/lgcompanion-gui.desktop.

Полезные ссылки

  • Репозиторий: git@git.linux-gaming.ru:Linux-Gaming/LGCompanion.git
  • Версия: 2.0.0