From a98d6ec9cd45f9b3a7b111f15b80548007d6f5d6 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Fri, 21 Aug 2026 21:49:44 -0400 Subject: [PATCH] =?UTF-8?q?[FIX]=20script=20todo:=20r=C3=A9gler=20l'=C3=A9?= =?UTF-8?q?cran=20de=20l'=C3=A9mulateur=20au=20lancement,=20densit=C3=A9?= =?UTF-8?q?=20comprise?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Écrire hw.lcd.* dans le config.ini de l'AVD ne servait à rien : l'émulateur réécrit ce fichier depuis le profil du téléphone au premier démarrage, et l'AVD repartait en 1080x2400 densité 420 — quatre fois les pixels voulus. Constaté sur la VM, où le réglage était censé s'appliquer depuis des semaines. La taille passe donc au lancement, et la DENSITÉ avec elle. C'est ce point qui surprend, et il est mesuré sur une charge identique : 540x1140 en densité 420 est PIRE que le plein écran — 81 ms de médiane contre 40, 57 % d'images en retard contre 37, tout étant rendu énorme. En densité 240 : 38 ms, 32 %, et le 99e centile tombe de 950 ms à 250. « -no-snapshot-save » vient avec : ce menu propose de tuer l'émulateur par pkill, et le lancement suivant mourait alors sur « A snapshot operation is pending ». Vérifié après un pkill : plus aucun FATAL. --- EN --- Writing hw.lcd.* into the AVD's config.ini did nothing: the emulator rewrites that file from the phone profile on first boot, and the AVD came back at 1080x2400 density 420 — four times the intended pixels. Found on the VM, where the setting was supposed to have applied for weeks. The size therefore moves to launch time, and the DENSITY with it. That is the surprising part, measured on an identical workload: 540x1140 at density 420 is WORSE than the full screen — 81 ms median against 40, 57 % janky frames against 37, everything rendered huge. At density 240: 38 ms, 32 %, and the 99th percentile drops from 950 ms to 250. "-no-snapshot-save" comes along: this menu offers to kill the emulator with pkill, and the next start then died on "A snapshot operation is pending". Checked after a pkill: no FATAL left. Assisted-by: Claude Opus 5 --- script/todo/todo.py | 60 ++++++++++++++++++++++----------- test/test_qemu_desktop_tools.py | 34 +++++++++++++------ test/test_qemu_emulator_menu.py | 37 ++++++++++++++++++-- 3 files changed, 98 insertions(+), 33 deletions(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index 168a072..55aeea6 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -1659,6 +1659,26 @@ class TODO: # Commande de l'émulateur dans la VM. Le chemin est ABSOLU : un # « ssh hôte 'commande' » ne lit ni ~/.profile ni ~/.bashrc. _QEMU_EMULATOR_BIN = "$HOME/android/emulator/emulator" + + # Drapeaux passés à CHAQUE lancement, et non écrits dans le config.ini de + # l'AVD : l'émulateur réécrit ce fichier depuis le profil du téléphone au + # premier démarrage, et les hw.lcd.* y étaient effacés — l'AVD repartait en + # 1080x2400 densité 420, quatre fois les pixels voulus. Mesuré. + # + # La résolution et la DENSITÉ vont ensemble, et c'est contre-intuitif : + # 540x1140 en densité 420 est PIRE que le plein écran — 81 ms de médiane + # contre 40, et 57 % d'images en retard contre 37, tout étant rendu énorme. + # Avec la densité 240, la queue s'effondre : 99e centile à 250 ms contre + # 950, et 32 % d'images en retard. + # + # « -no-snapshot-save » : sans lui, un émulateur tué par pkill — ce que ce + # menu propose lui-même — laisse un instantané en cours, et le lancement + # SUIVANT meurt sur « A snapshot operation is pending and timeout has + # expired ». Vécu, et le message ne dit pas quoi faire. + _QEMU_EMULATOR_FLAGS = ( + "-no-audio -no-boot-anim -no-snapshot-save -gpu swangle" + " -skin 540x1140 -prop qemu.sf.lcd_density=240" + ) _QEMU_AVD_NAME = "erplibre" def _qemu_emulator_running(self, target, src="virsh"): @@ -1778,8 +1798,8 @@ class TODO: # partir d'ici, où il n'y a pas d'écran à lui donner. print(f"\n {t('Run this on YOUR workstation:')}") print( - f"\n ssh -XC {target} '{emu} -avd {avd}" - " -no-audio -no-boot-anim'\n" + f"\n ssh -XC {target} '{emu} -avd {avd} " + f"{self._QEMU_EMULATOR_FLAGS}'\n" ) print( f" {t('X11 compression is on (-XC); the screen is 540x1140.')}" @@ -1792,8 +1812,9 @@ class TODO: # l'émulateur refuse de démarrer. setsid le détache, pour qu'il survive # à la fermeture de ce ssh. start = ( - f'setsid -f sg kvm -c "{emu} -avd {avd} -no-window -no-audio' - ' -no-boot-anim > /tmp/erplibre-emulator.log 2>&1"' + f'setsid -f sg kvm -c "{emu} -avd {avd} -no-window ' + f"{self._QEMU_EMULATOR_FLAGS}" + ' > /tmp/erplibre-emulator.log 2>&1"' ) res = subprocess.run( ["ssh"] + self._qemu_ssh_opts(src) + [target, start], @@ -1865,8 +1886,8 @@ class TODO: ) print( f"\n ssh {target} '{self._QEMU_EMULATOR_BIN} " - f"-avd {self._QEMU_AVD_NAME}" - " -no-window -no-audio -no-boot-anim'\n" + f"-avd {self._QEMU_AVD_NAME} -no-window " + f"{self._QEMU_EMULATOR_FLAGS}'\n" ) print(f" {t('2. Open the tunnel from YOUR workstation:')}") if src == "ssh_config": @@ -6692,16 +6713,16 @@ class TODO: "dev=$(cat $HOME/.erplibre-avd-device); " 'echo no | avdmanager create avd -n erplibre -k "$img" ' '-d "$dev" --force && ' - # Rendu logiciel, écrit dans la config : par ssh -X il n'y a pas de - # GLX direct, et « auto » donnerait un écran noir. - # L'écran, RÉDUIT, et c'est ce réglage qui décide du confort. Le - # profil Pixel donne 1080x2400, soit 2,6 Mpixels à pousser - # image par image à travers SSH, en rendu logiciel : « ça se - # lance mais c'est trop lent ». En 540x1140 il en reste - # 0,62 Mpixel — 4,2 fois moins. Android gère la densité et - # l'application ne s'en aperçoit pas ; qui veut la taille - # réelle l'écrase au lancement par « -skin 1080x2400 ». - 'printf "hw.gpu.enabled=yes\\nhw.gpu.mode=swangle\\nhw.lcd.width=540\\nhw.lcd.height=1140\\nhw.lcd.density=240\\n" ' + # Rendu logiciel, écrit dans la config : par ssh -X il n'y a pas + # de GLX direct, et « auto » donnerait un écran noir. Ces deux + # clés-là SURVIVENT, elles ne viennent pas du profil du téléphone. + # + # L'écran, en revanche, ne s'écrit PAS ici : l'émulateur réécrit + # config.ini depuis le profil Pixel au premier démarrage, et les + # hw.lcd.* y étaient effacés — l'AVD repartait en 1080x2400 + # densité 420. C'est donc au LANCEMENT qu'il se règle, par + # _QEMU_EMULATOR_FLAGS, et la commande affichée plus bas les porte. + 'printf "hw.gpu.enabled=yes\\nhw.gpu.mode=swangle\\n" ' ">> $HOME/.android/avd/erplibre.avd/config.ini' && " f'echo " ✅ {t("AVD ready:")} ' "$(cat $HOME/.erplibre-avd-device) / " @@ -6717,10 +6738,11 @@ class TODO: # found ». Vécu, sur la ligne que ce message affichait lui-même. f'echo " {t("open it from your workstation:")} ' # « -XC » et non « -X » : la compression X11 change tout sur un - # écran distant. « -no-boot-anim » retire une animation qui - # ne sert qu'à faire attendre. + # écran distant. Les autres drapeaux viennent de la même autorité + # que le lancement du menu : écran réduit, densité qui va avec, et + # pas d'instantané en attente si on tue l'émulateur. 'ssh -XC erplibre@$ip \\"$HOME/android/emulator/emulator ' - '-avd erplibre -no-audio -no-boot-anim\\""; ' + f'-avd erplibre {self._QEMU_EMULATOR_FLAGS}\\""; ' f'echo " {t("then install the APK:")} ' # « -t » : l'ABI injectée fait marquer l'APK « testOnly » par AGP, # et adb le refuse sans ce drapeau — « INSTALL_FAILED_TEST_ONLY ». diff --git a/test/test_qemu_desktop_tools.py b/test/test_qemu_desktop_tools.py index b115f9f..2e1e2d8 100644 --- a/test/test_qemu_desktop_tools.py +++ b/test/test_qemu_desktop_tools.py @@ -526,17 +526,29 @@ class TestAndroidEmulator(unittest.TestCase): "avd", self.todo._qemu_tools_for(("avd",), "amd64", "", "ubuntu") ) - def test_the_screen_is_small_enough_to_travel(self): - """Le profil Pixel donne 1080x2400 : 2,6 Mpixels par image à pousser - dans SSH, et « ça se lance mais c'est trop lent ». Mesuré après - réduction : 540x1140 confirmé par « wm size », et la capture pleine - page tombe de 220 Ko à 49 Ko.""" - for key in ( - "hw.lcd.width=540", - "hw.lcd.height=1140", - "hw.lcd.density=240", - ): - self.assertIn(key, self.cmd, key) + def test_the_screen_is_set_at_launch_not_in_the_config(self): + """Écrire hw.lcd.* dans config.ini ne SERT À RIEN : l'émulateur réécrit + ce fichier depuis le profil du téléphone au premier démarrage, et l'AVD + repartait en 1080x2400 densité 420 — constaté sur la VM. La taille se + règle donc au lancement, et la commande affichée la porte.""" + self.assertNotIn("hw.lcd.width", self.cmd) + self.assertIn("-skin 540x1140", self.cmd) + # Ces deux clés-là survivent : elles ne viennent pas du profil. + self.assertIn("hw.gpu.mode=swangle", self.cmd) + + def test_the_density_travels_with_the_resolution(self): + """Contre-intuitif, et mesuré : 540x1140 en densité 420 est PIRE que le + plein écran — 81 ms de médiane contre 40, et 57 % d'images en retard + contre 37, tout étant rendu énorme. Avec la densité 240 : 38 ms, 32 %, + et le 99e centile tombe de 950 ms à 250.""" + self.assertIn("qemu.sf.lcd_density=240", self.cmd) + + def test_a_killed_emulator_does_not_block_the_next_start(self): + """Ce menu propose lui-même de tuer l'émulateur par pkill. Sans + « -no-snapshot-save », le lancement suivant meurt sur « A snapshot + operation is pending and timeout has expired » — vécu, et le message ne + dit pas quoi faire.""" + self.assertIn("-no-snapshot-save", self.cmd) def test_the_printed_command_compresses_the_display(self): """« -XC » plutôt que « -X » sur un écran distant.""" diff --git a/test/test_qemu_emulator_menu.py b/test/test_qemu_emulator_menu.py index 7d8fc37..8386dc0 100644 --- a/test/test_qemu_emulator_menu.py +++ b/test/test_qemu_emulator_menu.py @@ -155,8 +155,16 @@ class _MenuCase(unittest.TestCase): self.todo._qemu_self_address = staticmethod(lambda: ("10.0.0.2", True)) self.calls = [] - def _play(self, answers, running=0, start_rc=0, port_taken=False, - probe="", running_after=1, log="rien"): + def _play( + self, + answers, + running=0, + start_rc=0, + port_taken=False, + probe="", + running_after=1, + log="rien", + ): """Joue le menu avec des réponses données ; rend (sortie, commandes). « running » est le compte AVANT le démarrage, « running_after » celui @@ -254,6 +262,28 @@ class TestEmulatorMenu(_MenuCase): self.assertIn("setsid -f", cmd) self.assertIn("/tmp/erplibre-emulator.log", cmd) + def test_the_start_carries_the_shared_flags(self): + """Une seule autorité pour ces drapeaux : le lancement du menu, la + commande fenêtrée et celle de l'étape AVD doivent dire la même chose. + La densité va avec la résolution — 540x1140 en densité 420 est mesuré + PIRE que le plein écran.""" + _, calls = self._play(["1", "1", "n"]) + cmd = self._started(calls)[0][-1] + for flag in ( + "-skin 540x1140", + "qemu.sf.lcd_density=240", + "-no-snapshot-save", + "-gpu swangle", + ): + self.assertIn(flag, cmd, flag) + + def test_the_windowed_command_carries_them_too(self): + """Qui ouvre la fenêtre depuis son poste doit avoir le même écran que + qui passe par scrcpy — sinon la comparaison ne veut rien dire.""" + out, _ = self._play(["1", "2"]) + self.assertIn("-skin 540x1140", out) + self.assertIn("qemu.sf.lcd_density=240", out) + def test_the_start_command_is_valid_shell(self): """Une apostrophe ou un guillemet de trop, et la VM répond par une erreur de syntaxe — déjà rencontré dans ce même fichier.""" @@ -272,7 +302,8 @@ class TestEmulatorMenu(_MenuCase): def test_a_vm_without_the_sdk_is_diagnosed_before_anything_else(self): """Une VM déployée sans cocher l'outil est le cas NORMAL. Le menu le dit avant même de demander la fenêtre — mesuré sur une VM de migration, - où le démarrage détaché rendait 0 et le journal disait « not found ».""" + où le démarrage détaché rendait 0 et le journal disait « not found ». + """ out, calls = self._play(["1"], probe="NO_SDK\n") self.assertIn("SDK", out) self.assertNotIn("[1]", out.split("VM locale")[-1])