使用JNA(Java)如何获取活动窗口的X11 WM_CLASS?

5

我使用以下代码来获取 Linux 中当前(在焦点中)的窗口:

X11 x11 = X11.INSTANCE;
X11.Display display = x11.XOpenDisplay(null);
X11.Window window = x11.XDefaultRootWindow(display);

现在我想获取这个窗口的WM_CLASS属性。
设置和读取WM_CLASS属性中,有以下内容显示如何操作:

要读取窗口的WM_CLASS属性,请使用XGetClassHint()。

XClassHint包含:

typedef struct {

char *res_name;

char *res_class;

} XClassHint;

然而,当我们查看X11.XWMHints (JNA API)时,可以看到没有这样一个属性res_class(即WM_CLASS)。
那么,如何获取当前窗口的WM_CLASS属性呢?

你尝试过使用 jnacontrib 中的 String wc = new X.Window(new X.Display(display), window).getWindowClass(); 吗? https://github.com/java-native-access/jna/blob/master/contrib/x11/src/jnacontrib/x11/api/X.java#L591 它从当前窗口读取 WM_CLASS 属性。结果字符串应包含两个字符串,X.Display 类将 \0 字符替换为点 (.) https://github.com/java-native-access/jna/blob/master/contrib/x11/src/jnacontrib/x11/api/X.java#L932 - Kirill
是的,它确实尝试过。它运行良好。但是,我想知道如何在不使用该代码的情况下手动完成它。据我所知,它使用了一些已由JNA实现的函数,例如您提到的字符串转换。 - menteith
1个回答

4
您可以使用jnacontrib包中的X.Window类的getWindowClass() 方法:它从提供的窗口中读取WC_CLASS 属性,并将空终止符(\0)替换为点号(.)。Javadoc表示:

返回每个'\0'字符被替换为'.'的UTF8字符串的属性值。

X11 x11 = X11.INSTANCE;
X11.Display display = x11.XOpenDisplay(null);
X11.Window window = x11.XDefaultRootWindow(display);
String wmClass = new X.Window(new X.Display(display), window).getWindowClass();

另一种获取该值的方法是手动读取它(从contrib包中复制了一些部分):
    static class XClassHint {
        public final String resName;
        public final String resClass;

        public XClassHint(final String name, final String cls) {
            this.resName = name;
            this.resClass = cls;
        }

        @Override
        public String toString() {
            return String.format("%s:%s", this.resName, this.resClass);
        }
    }

    private static XClassHint wmClass(X11 x11, X11.Display display, X11.Window window) throws UnsupportedEncodingException {
        final X11.Atom xa_prop_type = X11.XA_STRING;
        final X11.Atom xa_prop_name = X11.XA_WM_CLASS;
        final int MAX_PROPERTY_VALUE_LEN = 4096;

        X11.AtomByReference xa_ret_type_ref = new X11.AtomByReference();
        IntByReference ret_format_ref = new IntByReference();
        NativeLongByReference ret_nitems_ref = new NativeLongByReference();
        NativeLongByReference ret_bytes_after_ref = new NativeLongByReference();
        PointerByReference ret_prop_ref = new PointerByReference();

        NativeLong long_offset = new NativeLong(0);
        NativeLong long_length = new NativeLong(MAX_PROPERTY_VALUE_LEN / 4);

        if (x11.XGetWindowProperty(display, window, xa_prop_name, long_offset, long_length, false,
            xa_prop_type, xa_ret_type_ref, ret_format_ref,
            ret_nitems_ref, ret_bytes_after_ref, ret_prop_ref) != X11.Success) {
            String prop_name = x11.XGetAtomName(display, xa_prop_name);
            throw new RuntimeException("Cannot get " + prop_name + " property.");
        }

        X11.Atom xa_ret_type = xa_ret_type_ref.getValue();
        Pointer ret_prop = ret_prop_ref.getValue();

        if (xa_ret_type == null) {
            //the specified property does not exist for the specified window
            return null;
        }

        if (xa_prop_type == null ||
            !xa_ret_type.toNative().equals(xa_prop_type.toNative())) {
            x11.XFree(ret_prop);
            String prop_name = x11.XGetAtomName(display, xa_prop_name);
            throw new RuntimeException("Invalid type of " + prop_name + " property");
        }

        int ret_format = ret_format_ref.getValue();
        long ret_nitems = ret_nitems_ref.getValue().longValue();

        // null terminate the result to make string handling easier
        int nbytes;
        if (ret_format == 32)
            nbytes = Native.LONG_SIZE;
        else if (ret_format == 16)
            nbytes = Native.LONG_SIZE / 2;
        else if (ret_format == 8)
            nbytes = 1;
        else if (ret_format == 0)
            nbytes = 0;
        else
            throw new RuntimeException("Invalid return format");
        int length = Math.min((int) ret_nitems * nbytes, MAX_PROPERTY_VALUE_LEN);

        byte[] ret = ret_prop.getByteArray(0, length);

        x11.XFree(ret_prop);
        if( ret == null ){
            return null;
        }

        // search for '\0'
        int i;
        for (i = 0; i < ret.length; i++) {
            if (ret[i] == '\0') {
                ret[i] = '.';
            }
        }

        String wcSrc = new String(ret, "UTF8");
        final String[] parts = wcSrc.split("\\.");
        return new XClassHint(parts[0], parts[1]);
    }



谢谢您。不过,有没有不使用这个类的方法呢? - menteith
@menteith 当然,你可以创建一个类来反映 WM_CLASS 结构并手动读取属性(请参见更新)。 - Kirill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接