Добавлено: - передача состояния устройств и команд управления между сервисом и графическим интерфейсом - автоматическое восстановление связи интерфейса с сервисом Изменено: - графический интерфейс использует единый запущенный сервис вместо собственного опроса устройств Исправлено: - одновременный опрос устройств сервисом и графическим интерфейсом Удалено: - отдельный фоновый опрос устройств в графическом интерфейсе
18 KiB
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-опроса донгла.
- PipeWire +
Структура репозитория
.
├── 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)
- Загружает конфиг
~/.config/lgcompanion/config.toml. - Периодически (по умолчанию раз в 2 секунды) ищет
/dev/hidraw*узел, соответствующий одному из включённых профилей устройств. - Отправляет HID-запрос
battery_request(по умолчанию06 18) и ждёт ответ. - Если ответ получен и байт заряда в диапазоне 1–100 — наушники подключены:
- переключает default sink на
sinks.surround; - перенаправляет выход фильтра
sinks.filter_outputнаsinks.stereo.
- переключает default sink на
- Если таймаут или байт заряда некорректен — наушники отключены:
- переключает default sink на
sinks.stereo.
- переключает default sink на
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 — обычные строки.
Ручное тестирование:
- Убедиться, что
lgcompanion list-hidвидит донгл. - Запустить
lgcompanion diagnoseи проверить HID-ответы в включённом/выключенном состоянии наушников. - Запустить
lgcompanion run -vи убедиться, что default sink меняется междуsinks.surroundиsinks.stereo. - Проверить 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