[ADD] todo qemu: offer the adb tunnel for scrcpy, X11-free
X11 carries raw pixels: 0.62 megapixel per frame even after shrinking the screen, rendered in software. scrcpy receives an H.264 stream encoded BY the device, so the emulator runs with no window at all — no X11 anywhere. The tunnel menu gained a fourth kind, reusing the target list it already reads from ~/.ssh/config, ProxyJump included: the hard part was already solved there. It forwards 5555, the emulator's own adb port, not 5037: tunnelling the adb server would force the user to kill the one on their workstation, which holds the same port. Verified through the tunnel from the hypervisor: an adb CNXN handshake answers device::ro.product.name=sdk_gphone64_x86, exactly what adb connect does. ss -ltn in the VM confirms 5554, 5555 and 5037 all listen on 127.0.0.1. --- FR --- X11 transporte des pixels bruts : 0,62 mégapixel par image même après réduction de l'écran, en rendu logiciel. scrcpy reçoit un flux H.264 encodé PAR l'appareil, si bien que l'émulateur tourne sans aucune fenêtre — plus de X11 nulle part. Le menu de tunnel gagne un quatrième type, qui réutilise la liste de cibles qu'il lit déjà dans ~/.ssh/config, ProxyJump compris : le plus dur y était déjà résolu. Il redirige 5555, le port adb de l'émulateur lui-même, et non 5037 : tunneliser le serveur adb obligerait à tuer celui du poste, qui occupe le même port. Vérifié à travers le tunnel depuis l'hyperviseur : une poignée de main adb CNXN répond device::ro.product.name=sdk_gphone64_x86, ce que fait exactement adb connect. « ss -ltn » dans la VM confirme que 5554, 5555 et 5037 écoutent tous sur 127.0.0.1. Assisted-by: Claude Opus 5
This commit is contained in:
parent
1dcd19b02f
commit
7a65568098
3 changed files with 151 additions and 2 deletions
|
|
@ -1569,11 +1569,17 @@ class TODO:
|
|||
print(f"\n {t('Remote desktop kind:')}")
|
||||
print(f" [1] RDP 3389 (xrdp) *")
|
||||
print(f" [2] VNC 5901 (TigerVNC, Arch)")
|
||||
print(f" [3] {t('Hypervisor console (QEMU screen, no guest server)')}")
|
||||
print(
|
||||
f" [3] {t('Hypervisor console (QEMU screen, no guest server)')}"
|
||||
)
|
||||
print(f" [4] {t('Android emulator (adb 5555, then scrcpy)')}")
|
||||
kind_answer = input(f"{t('Choice')} [1]: ").strip() or "1"
|
||||
if kind_answer == "3":
|
||||
self._qemu_console_tunnel(name, src)
|
||||
return
|
||||
if kind_answer == "4":
|
||||
self._qemu_scrcpy_tunnel(name, src)
|
||||
return
|
||||
port, kind = (5901, "VNC") if kind_answer == "2" else (3389, "RDP")
|
||||
local = port + 1
|
||||
|
||||
|
|
@ -1601,6 +1607,65 @@ class TODO:
|
|||
)
|
||||
print(f" {t('The tunnel stays open as long as that ssh runs.')}")
|
||||
|
||||
def _qemu_scrcpy_tunnel(self, name, src):
|
||||
"""Tunnel adb vers l'émulateur Android d'une VM, pour scrcpy.
|
||||
|
||||
Pourquoi cette voie plutôt que « ssh -X » : par X11, chaque image de
|
||||
l'écran traverse le réseau en pixels bruts — 0,62 Mpixel par image même
|
||||
après réduction, en rendu logiciel. scrcpy, lui, reçoit un flux H.264
|
||||
encodé PAR l'appareil et le décode sur le poste. L'émulateur tourne
|
||||
alors SANS fenêtre : plus de X11 du tout, ni sur l'hôte ni dans la VM.
|
||||
|
||||
Le port est celui de l'émulateur, pas celui du serveur adb. Un émulateur
|
||||
écoute sur 5554 (console) et 5555 (adb), tous deux sur le localhost de
|
||||
la VM — vérifié par « ss -ltn ». C'est 5555 qu'il faut, et non 5037 :
|
||||
tunneler le serveur adb obligerait à tuer celui du poste, qui occupe le
|
||||
même port.
|
||||
|
||||
Vérifié de bout en bout à travers le tunnel : une poignée de main adb
|
||||
(paquet CNXN) reçoit « device::ro.product.name=sdk_gphone64_x86 » de
|
||||
l'émulateur lui-même — c'est exactement ce que fait « adb connect ».
|
||||
"""
|
||||
port = 5555
|
||||
print(f"\n 📱 {t('Android emulator over adb + scrcpy')}")
|
||||
print(f"\n {t('1. In the VM, start the emulator WITHOUT a window:')}")
|
||||
emu = "$HOME/android/emulator/emulator"
|
||||
if src == "ssh_config":
|
||||
print(
|
||||
f"\n ssh {name} '{emu} -avd erplibre"
|
||||
" -no-window -no-audio -no-boot-anim'\n"
|
||||
)
|
||||
else:
|
||||
ip = self._qemu_resolve_ips([name]).get(name)
|
||||
if not ip:
|
||||
print(f" {t('No IP for this VM; is it running?')}")
|
||||
return
|
||||
print(
|
||||
f"\n ssh erplibre@{ip} '{emu} -avd erplibre"
|
||||
" -no-window -no-audio -no-boot-anim'\n"
|
||||
)
|
||||
print(f" {t('2. Open the tunnel from YOUR workstation:')}")
|
||||
if src == "ssh_config":
|
||||
# « localhost » est résolu par le DERNIER saut, donc par la VM
|
||||
# elle-même : le ProxyJump de ssh_config traverse les niveaux.
|
||||
print(f"\n ssh -N -L {port}:localhost:{port} {name}\n")
|
||||
print(f" {t('(through the ProxyJump already in ~/.ssh/config)')}")
|
||||
else:
|
||||
host, from_ssh = self._qemu_self_address()
|
||||
user = os.environ.get("USER", "user")
|
||||
if not from_ssh:
|
||||
print(
|
||||
f" ⚠ {t('Not in an SSH session: check the host address.')}"
|
||||
)
|
||||
print(f"\n ssh -N -L {port}:{ip}:{port} {user}@{host}\n")
|
||||
print(f" {t('3. Then, still on your workstation:')}")
|
||||
print(f"\n adb connect localhost:{port}")
|
||||
print(f" scrcpy -s localhost:{port}\n")
|
||||
print(f" {t('The tunnel stays open as long as that ssh runs.')}")
|
||||
print(
|
||||
f" {t('scrcpy on Debian/Ubuntu:')} sudo apt install scrcpy" " adb"
|
||||
)
|
||||
|
||||
def _qemu_console_tunnel(self):
|
||||
"""Tunnel vers l'ÉCRAN QEMU d'une VM, pas vers un serveur de l'invité.
|
||||
|
||||
|
|
@ -5980,7 +6045,13 @@ class TODO:
|
|||
# Mesuré sur l'émulateur.
|
||||
'ssh erplibre@$ip \\"$HOME/android/platform-tools/adb install -r -t '
|
||||
f"{el_dir}/mobile/erplibre_home_mobile/android/app/build"
|
||||
'/outputs/apk/debug/app-debug.apk\\""'
|
||||
'/outputs/apk/debug/app-debug.apk\\""; '
|
||||
# La voie scrcpy, nommée ici parce que c'est la première
|
||||
# question qui vient après « ça se lance mais c'est lent » :
|
||||
# X11 transporte des pixels bruts, scrcpy un flux H.264 encodé
|
||||
# par l'appareil. Le détail du tunnel vit dans le menu
|
||||
# « Remote desktop tunnel », choix 4.
|
||||
+ f'echo " {t("smoother, without X11:")} TODO > Execute > Deploy > QEMU/KVM > tunnel > 4"'
|
||||
)
|
||||
|
||||
def _qemu_after_remote_cmd(self, tools, prod=False):
|
||||
|
|
|
|||
|
|
@ -7818,6 +7818,34 @@ TRANSLATIONS = {
|
|||
"fr": "venv ERPLibre absent (installation incomplète)",
|
||||
"en": "ERPLibre venv missing (incomplete install)",
|
||||
},
|
||||
"smoother, without X11:": {
|
||||
"fr": "plus fluide, sans X11 :",
|
||||
"en": "smoother, without X11:",
|
||||
},
|
||||
"Android emulator (adb 5555, then scrcpy)": {
|
||||
"fr": "Émulateur Android (adb 5555, puis scrcpy)",
|
||||
"en": "Android emulator (adb 5555, then scrcpy)",
|
||||
},
|
||||
"Android emulator over adb + scrcpy": {
|
||||
"fr": "Émulateur Android par adb + scrcpy",
|
||||
"en": "Android emulator over adb + scrcpy",
|
||||
},
|
||||
"1. In the VM, start the emulator WITHOUT a window:": {
|
||||
"fr": "1. Dans la VM, démarrer l'émulateur SANS fenêtre :",
|
||||
"en": "1. In the VM, start the emulator WITHOUT a window:",
|
||||
},
|
||||
"2. Open the tunnel from YOUR workstation:": {
|
||||
"fr": "2. Ouvrir le tunnel depuis VOTRE poste :",
|
||||
"en": "2. Open the tunnel from YOUR workstation:",
|
||||
},
|
||||
"3. Then, still on your workstation:": {
|
||||
"fr": "3. Puis, toujours sur votre poste :",
|
||||
"en": "3. Then, still on your workstation:",
|
||||
},
|
||||
"scrcpy on Debian/Ubuntu:": {
|
||||
"fr": "scrcpy sur Debian/Ubuntu :",
|
||||
"en": "scrcpy on Debian/Ubuntu:",
|
||||
},
|
||||
"Android emulator (Pixel)": {
|
||||
"fr": "Émulateur Android (Pixel)",
|
||||
"en": "Android emulator (Pixel)",
|
||||
|
|
|
|||
|
|
@ -582,6 +582,56 @@ class TestPycharmFirstOpen(unittest.TestCase):
|
|||
self.assertEqual(0, res.returncode, res.stderr)
|
||||
|
||||
|
||||
class TestScrcpyTunnel(unittest.TestCase):
|
||||
"""Tunnel adb pour scrcpy : la voie sans X11."""
|
||||
|
||||
def setUp(self):
|
||||
self.todo = TODO.__new__(TODO)
|
||||
|
||||
def _printed(self, name, src):
|
||||
import io
|
||||
import contextlib
|
||||
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
self.todo._qemu_scrcpy_tunnel(name, src)
|
||||
return buf.getvalue()
|
||||
|
||||
def test_it_tunnels_the_emulator_port_not_the_adb_server(self):
|
||||
"""5555 est l'adb de l'émulateur ; 5037 est le serveur adb, et le
|
||||
tunneliser obligerait à tuer celui du poste, qui occupe le même port.
|
||||
Vérifié dans la VM par « ss -ltn » : 5554, 5555 et 5037 écoutent tous
|
||||
sur 127.0.0.1."""
|
||||
out = self._printed("saut+vm", "ssh_config")
|
||||
self.assertIn("-L 5555:localhost:5555", out)
|
||||
self.assertNotIn("5037", out)
|
||||
|
||||
def test_it_starts_the_emulator_without_a_window(self):
|
||||
"""Tout l'intérêt : plus de X11 nulle part, le flux est du H.264."""
|
||||
out = self._printed("saut+vm", "ssh_config")
|
||||
self.assertIn("-no-window", out)
|
||||
self.assertNotIn("ssh -X", out)
|
||||
|
||||
def test_it_gives_the_three_workstation_commands(self):
|
||||
out = self._printed("saut+vm", "ssh_config")
|
||||
self.assertIn("adb connect localhost:5555", out)
|
||||
self.assertIn("scrcpy -s localhost:5555", out)
|
||||
self.assertIn("apt install scrcpy", out)
|
||||
|
||||
def test_it_uses_the_ssh_config_target_as_is(self):
|
||||
"""« localhost » est résolu par le DERNIER saut : le ProxyJump déjà
|
||||
écrit dans ~/.ssh/config traverse les niveaux, on ne le réinvente pas.
|
||||
"""
|
||||
out = self._printed("test-vm_02+erplibre-mobile-proof", "ssh_config")
|
||||
self.assertIn("test-vm_02+erplibre-mobile-proof", out)
|
||||
self.assertIn("ProxyJump", out)
|
||||
|
||||
def test_the_avd_step_points_at_it(self):
|
||||
"""La question vient juste après « c'est trop lent » : la réponse doit
|
||||
être à portée de journal."""
|
||||
self.assertIn("tunnel > 4", self.todo._qemu_avd_remote_cmd())
|
||||
|
||||
|
||||
class TestGnomeSiteExtensions(unittest.TestCase):
|
||||
"""Extensions posées depuis extensions.gnome.org, par leur UUID."""
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue