From 1776aa251ababb371ea6cf3acf59875000e952d5 Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Mon, 1 Jun 2026 03:13:59 +0200 Subject: [PATCH] fix call xseterrorhandler while gdk error trap is up --- src/main/java/com/gregor/jprototerm/Main.java | 23 +------ .../com/gregor/jprototerm/X11Pointer.java | 66 +++++++++++++++++++ 2 files changed, 69 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/gregor/jprototerm/X11Pointer.java diff --git a/src/main/java/com/gregor/jprototerm/Main.java b/src/main/java/com/gregor/jprototerm/Main.java index 6362cec..ed6d313 100644 --- a/src/main/java/com/gregor/jprototerm/Main.java +++ b/src/main/java/com/gregor/jprototerm/Main.java @@ -24,10 +24,6 @@ import java.nio.file.Path; import java.util.List; public final class Main extends Application { - // Mouse pointer location captured in main() before JavaFX loads GTK; used to pick the - // startup monitor. Reading it later (after GTK) makes AWT's X11 init clash with GDK. - private static java.awt.Point startupPointer; - private Compositor compositor; private TerminalMetrics metrics; private AppConfig config; @@ -75,10 +71,10 @@ public final class Main extends Application { } private static Screen activeScreen() { - java.awt.Point at = startupPointer; + int[] at = X11Pointer.query(); if (at != null) { - // AWT and JavaFX share a coordinate space on the X11 virtual screen. - List screens = Screen.getScreensForRectangle(at.x, at.y, 1.0, 1.0); + // libX11 and JavaFX share a coordinate space on the X11 virtual screen. + List screens = Screen.getScreensForRectangle(at[0], at[1], 1.0, 1.0); if (!screens.isEmpty()) { return screens.get(0); } @@ -230,19 +226,6 @@ public final class Main extends Application { public static void main(String[] args) { System.setProperty("prism.order", System.getProperty("prism.order", "es2,sw")); - // Initialise AWT and read the pointer here, before launch() loads GTK. Done afterwards, - // AWT's X11 init calls XSetErrorHandler while GDK has an error trap pushed and warns. - startupPointer = readPointerLocation(); launch(Main.class, args); } - - private static java.awt.Point readPointerLocation() { - try { - java.awt.PointerInfo pointer = java.awt.MouseInfo.getPointerInfo(); - return pointer != null ? pointer.getLocation() : null; - } catch (Throwable ignored) { - // Headless or AWT unavailable — the startup monitor falls back to the primary screen. - return null; - } - } } diff --git a/src/main/java/com/gregor/jprototerm/X11Pointer.java b/src/main/java/com/gregor/jprototerm/X11Pointer.java new file mode 100644 index 0000000..ec16077 --- /dev/null +++ b/src/main/java/com/gregor/jprototerm/X11Pointer.java @@ -0,0 +1,66 @@ +package com.gregor.jprototerm; + +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.invoke.MethodHandle; + +import static java.lang.foreign.ValueLayout.ADDRESS; +import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.JAVA_LONG; + +/** + * Reads the X11 pointer location directly via libX11 ({@code XQueryPointer}). Unlike AWT's + * {@code MouseInfo}, this never calls {@code XSetErrorHandler}, so it doesn't trip GDK's + * "XSetErrorHandler called with a GDK error trap pushed" warning when JavaFX's GTK backend is + * already up. Returns {@code null} when not on X11 or libX11 can't be loaded. + */ +final class X11Pointer { + private X11Pointer() { + } + + /** {@code {x, y}} of the pointer in X root-window (virtual screen) space, or {@code null}. */ + static int[] query() { + try (Arena arena = Arena.ofConfined()) { + Linker linker = Linker.nativeLinker(); + SymbolLookup x11 = SymbolLookup.libraryLookup("libX11.so.6", arena); + MethodHandle openDisplay = linker.downcallHandle(x11.find("XOpenDisplay").orElseThrow(), + FunctionDescriptor.of(ADDRESS, ADDRESS)); + MethodHandle defaultRootWindow = linker.downcallHandle(x11.find("XDefaultRootWindow").orElseThrow(), + FunctionDescriptor.of(JAVA_LONG, ADDRESS)); + MethodHandle queryPointer = linker.downcallHandle(x11.find("XQueryPointer").orElseThrow(), + FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_LONG, + ADDRESS, ADDRESS, ADDRESS, ADDRESS, ADDRESS, ADDRESS, ADDRESS)); + MethodHandle closeDisplay = linker.downcallHandle(x11.find("XCloseDisplay").orElseThrow(), + FunctionDescriptor.of(JAVA_INT, ADDRESS)); + + MemorySegment display = (MemorySegment) openDisplay.invoke(MemorySegment.NULL); + if (display.address() == 0) { + return null; + } + try { + long root = (long) defaultRootWindow.invoke(display); + MemorySegment rootReturn = arena.allocate(JAVA_LONG); + MemorySegment childReturn = arena.allocate(JAVA_LONG); + MemorySegment rootX = arena.allocate(JAVA_INT); + MemorySegment rootY = arena.allocate(JAVA_INT); + MemorySegment winX = arena.allocate(JAVA_INT); + MemorySegment winY = arena.allocate(JAVA_INT); + MemorySegment mask = arena.allocate(JAVA_INT); + int onSameScreen = (int) queryPointer.invoke(display, root, + rootReturn, childReturn, rootX, rootY, winX, winY, mask); + if (onSameScreen == 0) { + return null; + } + return new int[] { rootX.get(JAVA_INT, 0), rootY.get(JAVA_INT, 0) }; + } finally { + closeDisplay.invoke(display); + } + } catch (Throwable ignored) { + // Not X11, libX11 missing, or the call failed — caller falls back to the primary screen. + return null; + } + } +}