Skip to content

Merci v0.1.2

Latest

Choose a tag to compare

@github-actions github-actions released this 28 Aug 17:45

Выпуск про многооконность: теперь окон столько, сколько влезет в память, и
каждое — отдельный Android со своими данными. Больше половины списка ниже —
починки того, что по дороге ломалось: контейнер, потерявший картинку,
умирает за минуту, и почти все странности вырастали из этого одного корня.

Предупреждение. Многооконность новая и местами шершавая. Окно иногда не
открывается с первого раза — Merci замечает это сама и перезапускает его,
на что уходит минута-другая. Если что-то пошло не так, расскажите:
https://github.com/melineceo/Merci/issues

Добавлено

  • У каждого приложения свои окна. Раньше список окон был общий на всю
    библиотеку: клоны, заведённые для одной игры, показывались в карточке
    любой другой — хотя внутри у них своя копия данных и своя установленная
    сборка. Теперь окно принадлежит той записи, для которой его завели, и
    видно только в её карточке. Основное окно общее — оно у Waydroid одно на
    всех. Окна, заведённые до этого изменения, достаются первой карточке,
    которую откроют: они и правда были сделаны для неё.

  • Окна — отдельные контейнеры Android. В карточке появился раздел «Окна»:
    каждое окно это самостоятельный Android со своими данными, своим набором
    устройств binder, своим адресом в сети и своим окном на экране. Кнопка
    «+ Окно» заводит новый контейнер, «Запустить все» открывает приложение во
    всех, «Остановить все» закрывает. Ускорение достаётся каждому: контейнеры
    ходят в один и тот же сервер venus, а он принимает несколько клиентов.

  • Общий размер окон. Размер задаётся один раз — кнопкой в строке окна
    или строкой «Разрешение рендера», это теперь один и тот же регулятор, — и
    достаётся всем окнам: работающим, выключенным и созданным позже. Значение
    живёт в waydroid_base.prop, его читают все контейнеры при старте. Окно,
    которому размер когда-то задавали отдельно, выправляется само при первом
    запуске: Merci сверяет его с общим и, если разошлось, переписывает.
    Смена размера стоит двух запросов пароля вместо шести: работа под root
    собрана в две команды, потому что sudo здесь пароль не запоминает.

  • Оптимизация под многооконность: режим экономии и ограничение кадров.

Почему именно контейнеры, а не профили Android: профиль виден только тогда,
когда контейнер на него переключён, а профилей-соседей у основного ровно два
(клон и рабочий) — это зашито во framework. Отсюда прежний потолок в три окна.
Контейнеров же столько, сколько влезет в память: примерно гигабайт на пустой
плюс само приложение.

Про размер окна. Waydroid рисует кадр того размера, который задан контейнеру,
и на просьбу композитора изменить размер не отвечает. Поэтому в тайлинговой
раскладке, где размер окна навязан, картинка обрезается по краям — это и
видно как «не подгоняется».

Лечится не подгонкой, а тем, что окну не навязывают размер: плавающее окно
берёт его у самой картинки, и она видна целиком, пиксель в пиксель. Для
Hyprland правило одно:

hl.window_rule({match = {class = "^(waydroid\\..*)$"}, float = true})

Дальше размер меняется в Merci: кнопка «Подогнать под окно» или разрешение
рендера — контейнер перезапускается с новым размером, и окно следует за ним.

Чего сделать нельзя: чтобы картинка тянулась за рамкой на лету, как в
LDPlayer. Для этого нужен промежуточный композитор — gamescope, — а он на
NVIDIA не стартует вовсе: «vulkan: returned zero modifiers for DRM format».
Проверено сегодня заново.

Что нужно знать: контейнеры не делят установленные приложения, поэтому в
каждое новое окно APK ставится заново; права root нужны только на действия
(создать, запустить, погасить, удалить) — список окон Merci читает без них.

Исправлено

  • Merci не убиралась с глаз после запуска в клоне. Сворачивалась она
    только при запуске в основном окне, а после клона библиотека оставалась
    поверх игры. Теперь прячется одинаково — и при запуске в окне, и при
    «Запустить все», и только когда запуск удался.

  • Первый запуск в новом окне не открывался — мешал экран «Доступ ко всем
    файлам».
    Android поднимает его поверх игры при первом же запуске, и всё
    останавливается: игра стоит за ним и не рисует ни кадра, окна нет, а
    контейнер без картинки задыхается. Нажать «Разрешить» в отдельном окне
    некому — окна-то и нет. Это не обычное разрешение из манифеста (те выдаёт
    установка), а особое, со своим экраном; Merci теперь выдаёт его заранее.
    После этого игра в свежем окне нарисовала первый кадр за 7 секунд вместо
    трёх минут ожидания впустую.

  • Окно перенимает настройки основного контейнера. Основной настраивает
    под себя сессия Waydroid и сама Merci, а дополнительные поднимаются
    напрямую и берут только общий файл свойств — из-за чего частота
    обновления у них оказывалась 180 Гц против 60 у основного, а
    многооконный режим не был выставлен вовсе. Разница не косметическая:
    окно со свойствами основного открыло тяжёлую игру три раза из трёх, а
    соседнее, оставленное как было, — ноль из трёх, каждый раз с зависшей
    картинкой. Теперь при запуске окна свойства сверяются с основным и, если
    разошлись, переносятся; окно после этого перезагружается само, потому
    что читаются они при старте.

  • «Нажимаю запустить, и ничего не происходит». От прошлой жизни
    приложения остаётся запись задачи, и Android доставляет запуск в неё:

    Warning: Activity not started, its current task has been brought
    to the front
    

    Звучит благополучно, а процесса нет — задача пустая. Ни повторный
    запуск, ни принудительное закрытие её не убирают. Merci теперь проверяет,
    появился ли процесс, и если нет — открывает приложение новой задачей.
    Заодно запуск больше не отвечает успехом, когда приложение не поднялось.

  • Окно перезапускается и когда Android в нём лёг целиком — раньше
    Merci возвращала к жизни только окно без картинки, а мёртвый контейнер
    оставляла человеку.

  • Тяжёлая игра не открывалась в дополнительном окне. Экран отдавался
    приложению по появлению его задачи, а задача появляется задолго до
    первого кадра: у Roblox при холодном запуске между этим проходит минута и
    больше. Всё это время контейнеру велено показывать то, что ещё не
    рисует, — окна нет, кадровых сигналов от композитора хоста нет, и Android
    задыхается: dequeueBuffer failed, error = -110, следом виснет
    WindowManager, и сторож убивает system_server по кругу. Merci теперь
    ждёт, пока приложение и правда нарисует, и только потом отдаёт ему
    экран; до тех пор окно стоит в покое — без картинки, но и без вреда.
    Признак взят у SurfaceFlinger (слой «(BLAST)»), потому что счётчик
    кадров про приложение из второго профиля не знает ничего.

  • Запуск окна лечит сам себя. Первый запуск дополнительного окна
    иногда виснет: SurfaceFlinger внутри застревает на инициализации
    графики, и контейнер остаётся без сети навсегда. Помощник теперь сам
    дожидается сети и, если её нет, перезапускает окно — в том же заходе под
    root, чтобы пароль не спрашивали дважды. В карточке при этом честно
    написано, что ждать до трёх минут.

  • Закрытое окно приложения больше не открывалось совсем. Композитор
    запоминает задачу, окно которой закрыли, и окна ей больше не даёт —
    сколько ни нажимай «Запустить»:

    single-window: tid 1000009 (com.roblox.client) in ignored_apps -> no window
    

    Обычно приложение слышит просьбу закрыться и убирает задачу само; тогда
    следующий запуск заводит новую, и окно появляется. Но занятое приложение
    (игра на загрузке, например) просьбу пропускает — и остаётся навсегда без
    окна. Merci теперь читает решение композитора прямо из журнала
    контейнера: он объявляет его за секунды. Отказ значит, что задача
    помечена закрытой, — Merci гасит приложение и открывает заново, уже с
    новой задачей.

  • Покой контейнера — «ничего не показывать», а не «весь Android».
    Прежнее «показывать полный интерфейс» гарантировало смерть: лаунчер у
    этой сборки композитора в чёрном списке, показать нечего, окна нет, а
    контейнер считает, что экран у него есть — и задыхается без кадровых
    сигналов.

  • Присмотр за окнами. Раз в двадцать секунд Merci проверяет, что
    контейнеру велено показывать то, что на экране и правда есть. Если окно
    закрыли, свернули или увели, контейнер возвращается в покой — и
    переживает это спокойно вместо смерти на шестидесятой секунде. Раньше
    такое окно можно было только перезапускать.

  • Подписи окон говорили неправду. «Работает · приложения здесь ещё нет»
    появлялось и тогда, когда приложение стоит на месте, а Android внутри
    мёртв: список пакетов приходил пустым, и Merci принимала это за «не
    установлено». Теперь так и написано: «нет сети» или «не отвечает — нужен
    перезапуск окна».

  • После перезапуска контейнера «Запустить» не открывало приложение.
    Merci отвечала «Окно откроет Waydroid», окна не появлялось, а картинка
    вставала намертво. Ловушка та же, что и в дополнительных окнах, только в
    основном: экран отдавался приложению до того, как оно нарисует первый
    кадр. Обычно приложение успевает, и никто ничего не замечает, — но сразу
    после перезапуска оно стартует холодно и медленно, и всё это время
    контейнер стоит без единого окна. А без окна нет кадровых сигналов от
    композитора хоста, и Android задыхается. Теперь экран отдаётся после
    запуска, а если окна так и нет — контейнеру возвращается полный
    интерфейс Android, и только потом перезапуск.

  • Одно окно из четырёх не запускалось — и виноваты устройства binder.
    У каждого окна свой набор, и он переживает контейнер. Выключаем мы окна
    жёстко (иначе они не выключаются), и в устройствах остаются ссылки от
    убитого Android. Следующий запуск получает их вместе с чужими остатками:
    вызовы к SurfaceFlinger по binder виснут насмерть, system_server
    блокируется прямо в конструкторе DisplayManagerService, сторож убивает
    его по кругу. Контейнер при этом «работает», а внутри ни сети, ни
    картинки — снаружи видно только «окно не получило адрес в сети». Набор
    теперь пересоздаётся перед каждым запуском окна; если контейнер ещё не
    отпустил устройства, Merci ждёт его несколько секунд и в крайнем случае
    отсоединяет их отложенно.

  • Окно оставалось без экрана — и от этого умирало. В однооконном режиме
    композитор показывает ровно то приложение, которое названо в
    waydroid.active_apps. Merci называла его до запуска, и пока приложение
    не начало рисовать, композитор не показывал ничего — закрывал окно совсем
    («single-window: NO TID layer among 4 layers»). А без окна композитор
    хоста не шлёт кадровых сигналов, и Android внутри задыхается:
    dequeueBuffer failed, error = -110, следом блокируется WindowManager, и
    дальше уже знакомый круг. Лечить это поздно: свойство композитор
    перечитывает только на кадре. Теперь экран отдаётся приложению после
    запуска, а если окна так и нет — окну возвращается полный интерфейс
    Android. Закрытие приложения возвращает его же: раньше окно оставалось с
    указанием показывать закрытое приложение.

  • Проверка «появилось ли окно» смотрела не туда. Она считала окном любое
    упоминание пакета среди слоёв, а композитор смотрит только на слои задачи
    (TID:) — те самые, которых и не было. И у выключенного окна проверка
    молча брала картинку основного контейнера, потому что пустой адрес
    подменялся адресом по умолчанию.

  • Работающее окно Merci считала выключенным — и от этого ломалось
    всё остальное. Состояние определялось по сети: есть адрес и отвечает
    adb — значит работает. Контейнер, поднявшийся без сети, для Merci был
    выключен, поэтому она не гасила его перед стартом основной сессии. А он
    держит монтирование образа, из-за чего у сессии не встаёт оверлей;
    Waydroid отвечает на это раз и навсегда — пишет mount_overlays = False,
    — и вместе с оверлеем пропадает libhoudini. В карточке это всплывает
    через полчаса и совсем другими словами: «Нужна трансляция ARM64 →
    x86_64». Теперь состояние окна берётся у самого контейнера.

  • Клон не запускался после перезагрузки машины: «окно не получило
    адрес в сети». Конфиг окна — копия конфига основного контейнера, а тот
    сессия переписывает под себя при каждом старте: там пути сокета, образа
    и данных. Копия снималась один раз, при создании окна, и после
    перезагрузки вела в никуда. Контейнер поднимался, композитор внутри
    зависал, не дождавшись своего, а следом не вставали ни SurfaceFlinger,
    ни сеть. Теперь копия снимается заново при каждом запуске окна; данные
    окна лежат отдельно и не страдают.

  • Размер окон снова пропадал после обновления образа. Он лежал только
    в waydroid_base.prop, а этот файл Waydroid пересобирает с нуля. Теперь
    размер пишется ещё и в waydroid.cfg, откуда Waydroid его же и берёт при
    пересборке.

  • Подготовка к старту контейнера просила пароль трижды подряд — погасить
    лишние окна, вернуть оверлей, восстановить размер. Пропущенный запрос
    оставлял машину на середине: с выключенным оверлеем и без транслятора ARM
    во всех окнах сразу. Теперь это один заход под root.

  • Окно, закрытое на Win+Q, больше не открывалось. Композитор закрывает
    окно Waydroid по-настоящему, а Android об этом не знает: приложение
    продолжает работать, но поверхности, на которой оно рисовало, больше нет.
    После нескольких таких закрытий SurfaceFlinger перестаёт отвечать, и
    нового окна не появится уже никогда. Снаружи всё выглядит исправным —
    службы окон и пакетов отвечают «найдены», waydroid app launch
    возвращает ноль, — поэтому Merci рапортовала об успехе и продолжала
    проверять пустоту. Теперь она спрашивает сам SurfaceFlinger, дожидается
    окна на экране и, если его нет, возвращает картинку перезапуском
    контейнера: около двадцати секунд вместо бесконечной проверки.

  • Пустой список приложений принимался за «ничего не установлено», и
    Merci начинала ставить APK заново — долгая установка вместо внятного
    отказа. Пустым он бывает только когда служба не ответила: системные
    приложения в контейнере есть всегда.

  • Запуск уходил в фон после перезапуска контейнера. Перезапуск
    возвращает Android в основной профиль, а приложение живёт в своём —
    и запускалось в непереключённом. Работает, окна нет. Профиль теперь
    возвращается на место.

  • «Activity class does not exist» сразу после перезапуска: служба
    пакетов отвечает раньше, чем дочитывает установленные приложения. Merci
    ждёт именно это приложение и повторяет запуск, а не сдаётся с первой
    попытки.

  • adb переставал пускать в контейнер после обновления образа. Строка
    ro.adb.secure=0 жила в waydroid_base.prop, а этот файл Waydroid
    пересобирает с нуля при каждом waydroid upgrade — из своих настроек
    плюс раздел [properties] в waydroid.cfg. Теперь строка лежит там, где
    переживает пересборку.

  • «Нажмите галочку» — а галочки нет. Кнопка «подставить размер монитора»
    вписывала число в поле и советовала нажать галочку. У строки ввода она
    появляется лишь когда текст правит человек — программная подстановка её не
    показывает. Совет отправлял искать несуществующую кнопку, и размер не
    применялся. Теперь кнопка применяет сразу.

  • Заголовок строки обещал растягивание на монитор — этого не происходит.
    Теперь он говорит по делу: «Размер всех окон».

  • Merci запоминала размер, который не применился: выбор сохранялся до
    работы, а не после.

  • adb переставал пускать в основной контейнер: из базовых свойств
    пропадала строка ro.adb.secure=0. Merci сама кладёт ключ хоста в
    доверенные и повторяет подключение.

  • Android иногда застревает на загрузке — без адреса и без службы
    пакетов. Перезапуск сессии не помогает, поэтому Merci один раз
    перезапускает службу контейнера целиком.

  • Перезапуск контейнера не срабатывал — ни обычный, ни полный. Причин
    было две. Дополнительные окна держали монтирование образа, и контейнер не
    поднимался за отведённые две минуты; теперь оба перезапуска гасят окна
    первым делом и заодно возвращают оверлей, если тот был отключён. А в шаге
    полного перезапуска pkill -f "waydroid session start" убивал сам скрипт:
    эта же строка стояла у него в конце, в команде запуска. Команда теперь
    собирается из переменной, и шаблон с ней не совпадает.

  • Адрес контейнера определялся неверно после перезапуска: waydroid status называет адреса из старых аренд DHCP, которых уже нет на мосту.
    Merci берёт живого соседа, чей MAC не принадлежит дополнительным окнам.

  • Дополнительные окна отбирали у Waydroid транслятор ARM. Пока они
    работают, они держат прежнее монтирование образа, и оверлей при старте
    основной сессии не встаёт. Waydroid отвечает на это раз и навсегда —
    пишет mount_overlays = False и больше не пробует, а в оверлее лежит
    libhoudini. Merci теперь соблюдает порядок сама: гасит дополнительные
    окна перед стартом сессии и возвращает оверлей, если его уже отключили.

  • Шаг «Трансляция ARM64» падал при запущенном контейнере.
    waydroid_script считает провалом любой вывод в stderr, а waydroid container stop пишет туда обычное «Stopping container» — получалось
    «returned non-zero exit status 0» при успешной команде. Контейнер теперь
    гасится до вызова скрипта.

  • Проверка транслятора врала из-за кавычек: вложенный sh -c терял
    путь при передаче через adb, ls показывал корень, и мастер предлагал
    ставить трансляцию, которая уже стоит.

  • ARM-приложения переставали запускаться во всех контейнерах сразу.
    Waydroid выключает свой оверлей после первой же неудачи монтирования
    (mount_overlays = False в waydroid.cfg) и больше не пробует, а в оверлее
    лежит libhoudini. Снаружи это выглядит как «игра вылетает при запуске»:
    UnsatisfiedLinkError: dlopen ... is for EM_AARCH64 instead of EM_X86_64.
    Merci теперь проверяет не свойство, а саму библиотеку в контейнере, и
    предлагает вернуть оверлей отдельным шагом мастера.

  • waydroid status показывает чужой адрес, когда рядом работает ещё один
    контейнер: он берёт первого попавшегося соседа на мосту. Merci определяет
    адрес своего контейнера по MAC и на этот ответ больше не полагается.

  • Переключение профиля упиралось в лимит одновременно работающих:
    Android отвечал «Failed to switch to user N», и выглядело это как поломка.
    Теперь Merci сама останавливает лишние профили и повторяет попытку.

  • Окна не открывались, если контейнер стоял на другом профиле — запуск не
    возвращал контейнер на нужный.

  • Запуск не называл приложение Waydroid (waydroid.active_apps), без чего
    контейнер не рисует его окна.

  • Создание окна не работало никогда: команда шла с ключом --type,
    которого у pm create-user нет.

  • «Запустить все» молчало об отказах — исключения проглатывались, и полный
    провал выглядел как успех.

  • Многооконный режим включался без перезапуска сессии, то есть не
    включался вовсе.

  • Каждое новое окно открывало экран «Доступ ко всем файлам» поверх игры.

Установка и обновление

flatpak remote-add --user --if-not-exists merci \
  https://melineceo.github.io/Merci/merci.flatpakrepo
flatpak install --user merci xyz.hackerstone.Merci

Уже установленной Merci хватит flatpak update. Файл ниже — на
случай установки без репозитория; обновляться такая установка не
умеет.

Полный список изменений: CHANGELOG.md · English