Repository navigation
Выпуск про многооконность: теперь окон столько, сколько влезет в память, и
каждое — отдельный 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