From e976fc28853dfbc0bcb66413aaa05d626637a798 Mon Sep 17 00:00:00 2001 From: Xnick417x Date: Tue, 14 Jul 2026 21:34:03 +0000 Subject: [PATCH] Fix touch crash after X client disconnect A client's XInput2 raw-event selections were never removed on disconnect, so pointer injection kept writing raw motion to its released output stream and hit a null buffer NPE on the main thread. Remove the client's XI2 selections in freeResources, release the output buffer under the stream write lock so an in-flight event write can never race the free, and discard writes on a released stream. --- .../display/connector/XOutputStream.java | 22 +++++++++++++++++-- .../main/runtime/display/xserver/XClient.java | 4 ++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/src/main/runtime/display/connector/XOutputStream.java b/app/src/main/runtime/display/connector/XOutputStream.java index ee110d642..be89eef16 100644 --- a/app/src/main/runtime/display/connector/XOutputStream.java +++ b/app/src/main/runtime/display/connector/XOutputStream.java @@ -32,21 +32,25 @@ public void setAncillaryFd(int ancillaryFd) { } public void writeByte(byte value) { + if (buffer == null) return; ensureSpaceIsAvailable(1); buffer.put(value); } public void writeShort(short value) { + if (buffer == null) return; ensureSpaceIsAvailable(2); buffer.putShort(value); } public void writeInt(int value) { + if (buffer == null) return; ensureSpaceIsAvailable(4); buffer.putInt(value); } public void writeLong(long value) { + if (buffer == null) return; ensureSpaceIsAvailable(8); buffer.putLong(value); } @@ -57,6 +61,7 @@ public void writeFP3232(double value) { } public void writeString8(String str) { + if (buffer == null) return; byte[] bytes = str.getBytes(XServer.LATIN1_CHARSET); int length = -bytes.length & 3; ensureSpaceIsAvailable(bytes.length + length); @@ -69,11 +74,13 @@ public void write(byte[] data) { } public void write(byte[] data, int offset, int length) { + if (buffer == null) return; ensureSpaceIsAvailable(length); buffer.put(data, offset, length); } public void write(ByteBuffer data) { + if (buffer == null) return; ensureSpaceIsAvailable(data.remaining()); buffer.put(data); } @@ -83,6 +90,7 @@ public void writePad(int length) { } private void flush() throws IOException { + if (buffer == null) return; if (buffer.position() != 0) { buffer.flip(); @@ -124,9 +132,19 @@ public void close() throws IOException { } } + // Writers hold `lock` for the whole event write; releasing under the same lock + // means the buffer can never be freed mid-write, and later writes see null and + // discard (dead connection). public void release() { - XInputStream.freeDirectBuffer(buffer); - buffer = null; + lock.lock(); + try { + if (buffer != null) { + XInputStream.freeDirectBuffer(buffer); + buffer = null; + } + } finally { + lock.unlock(); + } } public void writeSuccessReply(int sequenceNumber, int replyLength) throws IOException { diff --git a/app/src/main/runtime/display/xserver/XClient.java b/app/src/main/runtime/display/xserver/XClient.java index af7b8fbf7..46b79506b 100644 --- a/app/src/main/runtime/display/xserver/XClient.java +++ b/app/src/main/runtime/display/xserver/XClient.java @@ -4,6 +4,7 @@ import com.winlator.cmod.runtime.display.connector.XInputStream; import com.winlator.cmod.runtime.display.connector.XOutputStream; import com.winlator.cmod.runtime.display.xserver.events.Event; +import com.winlator.cmod.runtime.display.xserver.extensions.XInput2Extension; import com.winlator.cmod.shared.android.RefreshRateUtils; import java.io.IOException; import java.util.ArrayList; @@ -92,6 +93,9 @@ public void freeResources() { eventListeners.keyAt(i).removeEventListener(eventListeners.removeAt(i)); } + XInput2Extension xInput2Extension = xServer.getExtension(XInput2Extension.MAJOR_OPCODE); + if (xInput2Extension != null) xInput2Extension.onClientDisconnected(this); + xServer.windowManager.removeOnResourceLifecycleListener(this); xServer.pixmapManager.removeOnResourceLifecycleListener(this); xServer.graphicsContextManager.removeOnResourceLifecycleListener(this);