Своя орбита: Яндекс Музыка на системном Electron
Тень, которой не было
Всё началось с мелочи, которую нельзя развидеть. Официальная Яндекс Музыка для Linux — это Electron-приложение из deb-пакета; в AUR его аккуратно перепаковывают в yandex-music. Приложение работало, играло, показывало иконку в трее — но в Wayland-сессии его безрамочное окно висело на экране плоским прямоугольником, без тени. Все соседние окна тень отбрасывали, а это — нет.
Причина оказалась не косметической, а архитектурной. Окно с собственным заголовком рисует клиентское оформление (CSD) и тень обязано рисовать само. Frameless-окна в Electron научились этому на Wayland только с 41-й версии. А внутри официального deb — и, следовательно, внутри AUR-пакета — лежал встроенный Electron 38. Он не попадал в эту границу и попасть не мог: версию среды выбирает не пользователь, а сборщик deb.
Проверка заняла один вечер: тот же код приложения, запущенный на системном electron42 из репозитория extra, дал окно с тенью. Диагноз превратился в план: взять из официального deb только само приложение — архив app.asar — и поставить его на системную среду выполнения.
Три подводных камня
План выглядел тривиально: распаковать deb, положить app.asar в /opt, написать обёртку electron42 /opt/yandex-music/app.asar. Реальность внесла три поправки, и каждая по-своему поучительна.
Иконка трея — пустой квадрат. Место под иконку есть, меню работает, кликается — а изображения нет. Код приложения строит путь к иконкам от process.resourcesPath, но при запуске на системном Electron этот путь указывает в каталог самого Electron (/usr/lib/electron42/resources), где никаких ассетов Яндекса, разумеется, нет. Соблазнительно было списать всё на баг публикации иконки в D-Bus — живой объект StatusNotifierItem честно показывал меню и статус, но не пиксели. На деле это оказалось следствием, а не причиной: когда у трея пустое изображение, выставлять в IconPixmap попросту нечего.
Разлогин на ровном месте. Имя приложения определяет каталог профиля в ~/.config, где живут куки и сессия. Запускаешь голый скрипт — Electron берёт имя по умолчанию, и приложение просыпается в чужом профиле ~/.config/Electron, забыв, кто вы такой. Лобовое лечение — вызвать app.setName() в рантайме — имя поправляло, но воспроизводимо ломало загрузку контента: A/B-прогон показал, что с setName/setVersion фирменный протокол music-application:// отвечает ошибкой, без них — работает.
Тирания semver. Без package.json рядом с точкой входа app.getVersion() возвращает строку "0.0", и приложение падает на старте: «App version is not a valid semver version». Особую пикантность добавляло то, что при некоторых способах запуска версия «случайно» бралась от самого Electron и проверку проходила — картина плавала.
Все три камня обходятся одним манёвром, и в этом главная находка сборки: /opt/yandex-music делается не складом файлов, а каталогом-приложением — со своим package.json:
{ "name": "YandexMusic", "version": "<версия>", "main": "launcher.js" }
Electron читает этот файл нативно, до всякого пользовательского кода: имя даёт правильный профиль (сессия на месте), версия — валидный semver (старт проходит), и никаких проблемных рантайм-вызовов. А main указывает на тонкий шим в дюжину строк, который до загрузки app.asar правит ровно два пути:
'use strict';
const APP_DIR = '/opt/yandex-music';
const ASAR = APP_DIR + '/app.asar';
const { app } = require('electron');
Object.defineProperty(process, 'resourcesPath', { value: APP_DIR, configurable: true });
Object.defineProperty(app, 'getAppPath', { value: () => ASAR, configurable: true });
require(ASAR);
resourcesPath — чтобы нашлись иконки трея; getAppPath — чтобы нашёлся web-контент внутри asar. Свойство process.resourcesPath защищено от записи, но не от переопределения — configurable: true оставляет дверь открытой.
Изящная деталь: версия из фида
Официальный клиент обновляет себя сам — значит, где-то лежит машиночитаемый ответ на вопрос «какая версия последняя». Он нашёлся: фид latest-linux.yml на серверах Яндекса, где указаны номер версии, имя deb-файла и его контрольная сумма sha512.
PKGBUILD построен вокруг этого фида. На каждой пересборке prepare() читает фид, скачивает актуальный deb и сверяет сумму; pkgver() подхватывает номер версии автоматически. Обновление приложения — это одна команда:
makepkg -si
Ни правки версии руками, ни ожидания, пока кто-то обновит чужой PKGBUILD. Вышел релиз — собрал — pacman штатно обновил пакет. Сборка всегда «latest» по определению, потому что источником истины служит тот же фид, которым пользуется официальный автообновлятор.
Побочный эффект перехода на системный Electron приятен и сам по себе: из пакета исчезла встроенная копия Chromium — минус триста мегабайт дублирующего кода, который к тому же не получал обновлений безопасности между релизами Музыки.
Отвязка от предшественника
Пока пакет назывался yandex-music, AUR-помощники считали его своим: видели в AUR одноимённый пакет посвежее и услужливо предлагали «обновление», которое затёрло бы всю сборку встроенным Electron 38 — вместе с тенью, ради которой всё затевалось.
Лечится это на уровне самого пакета, а не настроек машины: пакет переименован в yandex-music-system-electron, а строки
provides=('yandex-music')
conflicts=('yandex-music')
обеспечивают преемственность: установленный предшественник заменяется одной транзакцией pacman, зависимости от старого имени продолжают выполняться, но чужие обновления больше не приходят — такого имени в AUR нет.
Почему не в AUR
Казалось бы, естественный финал — выложить результат в AUR рядом с предшественником: вариант «на системном Electron» отличается содержательно, такие пакеты там приняты.
Но у AUR есть устав, и устав этот написан для другой конструкции. Источники обязаны лежать в массиве source=() с контрольными суммами, пришитыми в текст PKGBUILD; динамический pkgver() дозволен только VCS-пакетам. Иными словами — никакого фида: версию и сумму надо вбивать руками на каждый релиз, а релизы у Музыки частые. Схема, ради которой пакет строился, — самообновляющаяся, с проверкой целостности по тому же каналу, которому доверяет официальный клиент, — в эти рамки не помещается в принципе.
Можно было бы пойти доказывать на списках рассылки, что правила стоит читать шире. Но устав старше задачи, его хранители непреклонны, и исход таких споров известен заранее. Вместо того чтобы ампутировать лучшую часть решения ради строки в чужом каталоге, пакет живёт на GitHub: git clone, makepkg -si — и у вас всегда свежая Музыка на системном Electron. Даже дистрибутив не обязателен тот же: подход переносится на любое Electron-приложение, которое хочется снять с встроенной среды и посадить на системную.
Что в итоге
- Чистый пакет, отслеживаемый
pacman, на ~300 МБ легче официального. - Нативный Wayland — и та самая тень окна.
- Иконка в трее, MPRIS, воспроизведение потоком через ALSA вплоть до 176,4 кГц.
- Обновление одной командой, версия и контрольная сумма — из официального фида.
- Полная независимость от одноимённого пакета AUR.
Тень окна — мелочь. Но именно мелочи заставляют разобрать систему до винтиков, а собранное обратно оказывается лучше исходного: легче, свежее и на своей орбите.
Комментарии
Войдите, чтобы комментировать.