From 77c6ef79883cad1f3977e423b2f8a7fd2ac2a3ff Mon Sep 17 00:00:00 2001 From: Xnick417x Date: Thu, 2 Jul 2026 20:05:31 +0000 Subject: [PATCH] Fix: restore mouse and controls after background-session reattach On reattach the guest keeps running but never resends its init handshake, so the fresh WinHandler dropped all mouse/keyboard input; mark it initialized to adopt the guest's live state. Free the old handler's socket without tearing down its fake-input writers/rings (stopForReattach), and skip deleting the guest's live event0..3 nodes so gamepad/on-screen controls survive. --- .../display/XServerDisplayActivity.java | 22 ++++++-- .../display/winhandler/WinHandler.java | 51 +++++++++++++++---- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 7dd41587c..8ea5d0a41 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -1075,11 +1075,17 @@ public void handleOnBackPressed() { GuestProgramLauncherComponent.ensureImageFsNativeLibrary(this, imageFs, "libfakeinput.so"); GuestProgramLauncherComponent.ensureImageFsNativeLibrary(this, imageFs, "libandroid-sysvshm.so"); File devInputDir = new File(imageFs.getRootDir(), "dev/input"); + boolean reattachingLiveSession = SessionKeepAliveService.isSessionActive() + && SessionKeepAliveService.getActiveEnvironment() != null + && SessionKeepAliveService.getActiveXServer() != null; if (devInputDir.exists() || devInputDir.mkdirs()) { - for (int i = 0; i < 4; i++) { - File eventFile = new File(devInputDir, "event" + i); - if (eventFile.exists()) { - eventFile.delete(); + // On reattach the running guest still holds these nodes; deleting them breaks its live input mapping. + if (!reattachingLiveSession) { + for (int i = 0; i < 4; i++) { + File eventFile = new File(devInputDir, "event" + i); + if (eventFile.exists()) { + eventFile.delete(); + } } } } @@ -6420,8 +6426,14 @@ private void setupXEnvironment() throws PackageManager.NameNotFoundException { this.environment.setContext(this); this.reusingSession = true; + // Free the old handler's socket (keep its writers/rings) and mark the fresh one initialized so the guest's input keeps flowing. + WinHandler previousWinHandler = this.xServer.getWinHandler(); + if (previousWinHandler != null && previousWinHandler != winHandler) { + previousWinHandler.stopForReattach(); + } this.xServer.setWinHandler(winHandler); - + winHandler.markSessionInitialized(); + this.guestProgramLauncherComponent = environment.getComponent(GuestProgramLauncherComponent.class); return; } diff --git a/app/src/main/runtime/display/winhandler/WinHandler.java b/app/src/main/runtime/display/winhandler/WinHandler.java index 48711cd73..81b58d665 100644 --- a/app/src/main/runtime/display/winhandler/WinHandler.java +++ b/app/src/main/runtime/display/winhandler/WinHandler.java @@ -547,18 +547,51 @@ public void stop() { if (this.vibrationExecutor != null) this.vibrationExecutor.shutdownNow(); } + // Hand the socket/threads to a replacement handler while keeping the fake-input writers/rings the running guest is mapped to (unlike stop()). + public void stopForReattach() { + synchronized (this.actions) { + this.running = false; + this.actions.clear(); + this.actions.notifyAll(); + } + this.vibrationRunning = false; + if (this.socket != null) { + this.socket.close(); + this.socket = null; + } + if (this.vibrationServer != null) { + try { + this.vibrationServer.close(); + } catch (IOException ignored) { + } + this.vibrationServer = null; + } + if (this.sendExecutor != null) this.sendExecutor.shutdownNow(); + if (this.receiveExecutor != null) this.receiveExecutor.shutdownNow(); + if (this.vibrationExecutor != null) this.vibrationExecutor.shutdownNow(); + } + + private void applyInitHandshake() { + this.initReceived = true; + this.preferences = + PreferenceManager.getDefaultSharedPreferences(this.activity.getBaseContext()); + if (!this.xinputDisabledInitialized) { + this.xinputDisabled = this.preferences.getBoolean("xinput_toggle", false); + } + synchronized (this.actions) { + this.actions.notifyAll(); + } + } + + // Reused session: the guest already handshook and won't repeat it, so adopt init state or mouse/keyboard input stays gated off. + public void markSessionInitialized() { + applyInitHandshake(); + } + private void handleRequest(byte requestCode, int port) { switch (requestCode) { case 1: - this.initReceived = true; - this.preferences = - PreferenceManager.getDefaultSharedPreferences(this.activity.getBaseContext()); - if (!this.xinputDisabledInitialized) { - this.xinputDisabled = this.preferences.getBoolean("xinput_toggle", false); - } - synchronized (this.actions) { - this.actions.notifyAll(); - } + applyInitHandshake(); return; case 5: if (this.onGetProcessInfoListener != null) {