Search code examples
javax11jna

X11 JNA Bad Window (invalid Window parameter) causes JVM exit


The following piece of JNA using JNA 4.2.2 crashes - JVM exit with following if you run it whilst minimizing and maximizing windows. Happens on Ubuntu 16.04, but more often on Redhat 6u2.

X error of failed request: BadWindow (invalid Window parameter)
Major opcode of failed request: 20 (X_GetProperty)
...

I'm guessing the Window IDs are valid when they are returned from XQueryTree but become invalid before call to XGetWMName. The problem is this causes JVM exit, not just an Exception which I could handle nicely. How do I avoid this issue?

public class X11WindowFinder {

    private X11 x11;

    public X11WindowFinder() {
        x11 = X11.INSTANCE;
    }

    public List<Window> find(Pattern title) {
        Display display = x11.XOpenDisplay(null);
        Window root = x11.XDefaultRootWindow(display);
        List<Window> windows = recurse(x11, display, root, title);
        x11.XCloseDisplay(display);
        return windows;
    }

    private synchronized List<Window> recurse(X11 x11, Display display, Window root, Pattern pattern) {
        List<Window> windows = new ArrayList<>(1);
        X11.WindowByReference windowRef = new X11.WindowByReference();
        X11.WindowByReference parentRef = new X11.WindowByReference();
        PointerByReference childrenRef = new PointerByReference();
        IntByReference childCountRef = new IntByReference();

        x11.XQueryTree(display, root, windowRef, parentRef, childrenRef, childCountRef);
        if (childrenRef.getValue() == null) {
            return Collections.emptyList();
        }


        long[] ids = {};

        if (Native.LONG_SIZE == Long.BYTES) {
            ids = childrenRef.getValue().getLongArray(0, childCountRef.getValue());
        } else if (Native.LONG_SIZE == Integer.BYTES) {
            int[] intIds = childrenRef.getValue().getIntArray(0, childCountRef.getValue());
            ids = new long[intIds.length];
            for (int i = 0; i < intIds.length; i++) {
                ids[i] = intIds[i];
            }
        }

        for (long id : ids) {
            Window child = new Window(id);
            X11.XTextProperty name = new X11.XTextProperty();
            x11.XGetWMName(display, child, name);
            String value = name.value;
            if (value != null) {
                System.out.println(String.format("Found window %s free %s", value, name));
            }
            if (value != null && pattern.matcher(value).matches()) {
                windows.add(child);
            }
            windows.addAll(recurse(x11, display, child, pattern));
        }
        return windows;
    }

    public static void main(String[] args) {
        X11WindowFinder finder = new X11WindowFinder();
        while (true) {
            finder.find(Pattern.compile(".*Firefox.*"));
        }
    }
}

Solution

  • Looks like the default error handler causes exit, but can be overridden. For example

    x11.XSetErrorHandler(new XErrorHandler() {
    
        @Override
        public int apply(Display display, XErrorEvent errorEvent) {
            System.err.println("bad fish " + errorEvent);
            return 0;
        }
    });