diff --git a/README.md b/README.md index 7b4c146..b4243de 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ The service can be stopped with `systemctl --user stop dex.service`. Interaction with Stream Deck devices depends on github.com/sstallion/go-hid. This package makes use of [non-Go dependencies](https://github.com/libusb/hidapi/blob/master/BUILD.md#prerequisites). -The `watcher` module depends on libxss and libxres on linux (libxss-dev and libxres-dev in deb-based distributions). Testing the `watcher` module uses gioui.org, and so [its dependencies](https://gioui.org/doc/install) must be provided if testing the module. +The `watcher` module depends on Xlib, libXss and libXRes on linux (libx11-dev, libxss1/libxss-dev and libxres1/libxres-dev in deb-based distributions). Xlib headers are required during compilation, but the libraries may be absent with reduced or absent functionality. Testing the `watcher` module uses gioui.org, and so [its dependencies](https://gioui.org/doc/install) must be provided if testing the module. ## Linux udev rules diff --git a/cmd/watcher/XRes.h b/cmd/watcher/XRes.h new file mode 100644 index 0000000..45a3f0a --- /dev/null +++ b/cmd/watcher/XRes.h @@ -0,0 +1,156 @@ +/* + Copyright (c) 2002 XFree86 Inc +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is fur- +nished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- +NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- +NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the XFree86 Project shall not +be used in advertising or otherwise to promote the sale, use or other deal- +ings in this Software without prior written authorization from the XFree86 +Project. +*/ + +#ifndef _XRES_H +#define _XRES_H + +#include + +/* v1.0 */ + +typedef struct { + XID resource_base; + XID resource_mask; +} XResClient; + +typedef struct { + Atom resource_type; + unsigned int count; +} XResType; + +/* v1.2 */ + +typedef enum { + XRES_CLIENT_ID_XID, + XRES_CLIENT_ID_PID, + XRES_CLIENT_ID_NR +} XResClientIdType; + +typedef enum { + XRES_CLIENT_ID_XID_MASK = 1 << XRES_CLIENT_ID_XID, + XRES_CLIENT_ID_PID_MASK = 1 << XRES_CLIENT_ID_PID +} XResClientIdMask; + +typedef struct { + XID client; + unsigned int mask; +} XResClientIdSpec; + +typedef struct { + XResClientIdSpec spec; + long length; + void *value; +} XResClientIdValue; + +typedef struct { + XID resource; + Atom type; +} XResResourceIdSpec; + +typedef struct { + XResResourceIdSpec spec; + long bytes; + long ref_count; + long use_count; +} XResResourceSizeSpec; + +typedef struct { + XResResourceSizeSpec size; + long num_cross_references; + XResResourceSizeSpec *cross_references; +} XResResourceSizeValue; + +_XFUNCPROTOBEGIN + +/* v1.0 */ + +Bool XResQueryExtension ( + Display *dpy, + int *event_base_return, + int *error_base_return +); + +Status XResQueryVersion ( + Display *dpy, + int *major_version_return, + int *minor_version_return +); + +Status XResQueryClients ( + Display *dpy, + int *num_clients, + XResClient **clients +); + +Status XResQueryClientResources ( + Display *dpy, + XID xid, + int *num_types, + XResType **types +); + +Status XResQueryClientPixmapBytes ( + Display *dpy, + XID xid, + unsigned long *bytes +); + +/* v1.2 */ + +Status XResQueryClientIds ( + Display *dpy, + long num_specs, + XResClientIdSpec *client_specs, /* in */ + long *num_ids, /* out */ + XResClientIdValue **client_ids /* out */ +); + +XResClientIdType XResGetClientIdType(XResClientIdValue* value); + +/* return -1 if no pid associated to the value */ +pid_t XResGetClientPid(XResClientIdValue* value); + +void XResClientIdsDestroy ( + long num_ids, + XResClientIdValue *client_ids +); + +Status XResQueryResourceBytes ( + Display *dpy, + XID client, + long num_specs, + XResResourceIdSpec *resource_specs, /* in */ + long *num_sizes, /* out */ + XResResourceSizeValue **sizes /* out */ +); + +void XResResourceSizeValuesDestroy ( + long num_sizes, + XResResourceSizeValue *sizes +); + +_XFUNCPROTOEND + +#endif /* _XRES_H */ diff --git a/cmd/watcher/dl/dlopen.go b/cmd/watcher/dl/dlopen.go new file mode 100644 index 0000000..e5222d0 --- /dev/null +++ b/cmd/watcher/dl/dlopen.go @@ -0,0 +1,27 @@ +// Copyright ©2024 Dan Kortschak. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package dl implements dlopen and related functionality. +package dl + +import ( + "errors" + "unsafe" +) + +var ErrNotFound = errors.New("so lib not found") + +// Lib represents an open handle to a dynamically loaded library. +type Lib struct { + handle unsafe.Pointer + name string +} + +// Name returns the resolved name of the library. +func (l *Lib) Name() string { return l.name } + +// Error is a dlerror error message. +type Error string + +func (e Error) Error() string { return string(e) } diff --git a/cmd/watcher/dl/dlopen_other.go b/cmd/watcher/dl/dlopen_other.go new file mode 100644 index 0000000..ef8d62b --- /dev/null +++ b/cmd/watcher/dl/dlopen_other.go @@ -0,0 +1,39 @@ +// Copyright ©2024 Dan Kortschak. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !unix + +package dl + +import ( + "errors" + "unsafe" +) + +const ( + RTLD_LAZY = 0 + RTLD_NOW = 0 + RTLD_GLOBAL = 0 + RTLD_LOCAL = 0 + RTLD_NODELETE = 0 + RTLD_NOLOAD = 0 + RTLD_DEEPBIND = 0 +) + +var errNotImplemented = errors.New("not implemented") + +// Open is not implemented on this platform. +func Open(_ int, _ ...string) (*Lib, error) { + return nil, errNotImplemented +} + +// Symbol is not implemented on this platform. +func (l *Lib) Symbol(name string) (unsafe.Pointer, error) { + return nil, errNotImplemented +} + +// Close is not implemented on this platform. +func (l *Lib) Close() error { + return errNotImplemented +} diff --git a/cmd/watcher/dl/dlopen_unix.go b/cmd/watcher/dl/dlopen_unix.go new file mode 100644 index 0000000..48f5fa1 --- /dev/null +++ b/cmd/watcher/dl/dlopen_unix.go @@ -0,0 +1,77 @@ +// Copyright ©2024 Dan Kortschak. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unix + +package dl + +/* +#cgo LDFLAGS: -ldl +#include +#include +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +const ( + RTLD_LAZY = int(C.RTLD_LAZY) + RTLD_NOW = int(C.RTLD_NOW) + RTLD_GLOBAL = int(C.RTLD_GLOBAL) + RTLD_LOCAL = int(C.RTLD_LOCAL) + RTLD_NODELETE = int(C.RTLD_NODELETE) + RTLD_NOLOAD = int(C.RTLD_NOLOAD) + RTLD_DEEPBIND = int(C.RTLD_DEEPBIND) +) + +// Open opens the dynamic library corresponding to the first found library name +// in names. See man 3 dlopen for details. +func Open(flags int, names ...string) (*Lib, error) { + for _, n := range names { + filename := C.CString(n) + h := C.dlopen(filename, C.int(flags)) + C.free(unsafe.Pointer(filename)) + if h != nil { + return &Lib{ + handle: h, + name: n, + }, nil + } + } + return nil, fmt.Errorf("%w: %s", ErrNotFound, names) +} + +// Symbol takes a symbol name and returns a pointer to the symbol. +func (l *Lib) Symbol(name string) (unsafe.Pointer, error) { + C.dlerror() + + sym := C.CString(name) + s := C.dlsym(l.handle, sym) + C.free(unsafe.Pointer(sym)) + dlErrMsg := C.dlerror() + if dlErrMsg != nil { + return nil, fmt.Errorf("could not find %s: %w", name, Error(C.GoString(dlErrMsg))) + } + return s, nil +} + +// Close closes the receiver, unloading the library. Symbols must not be used +// after Close has been called. +func (l *Lib) Close() error { + if l == nil || l.handle == nil { + return nil + } + C.dlerror() + + C.dlclose(l.handle) + l.handle = nil + dlErrMsg := C.dlerror() + if dlErrMsg != nil { + return fmt.Errorf("error closing: %w", Error(C.GoString(dlErrMsg))) + } + return nil +} diff --git a/cmd/watcher/handle_xerror_linux.c b/cmd/watcher/handle_xerror_linux.c index e22eea1..f8e31ce 100644 --- a/cmd/watcher/handle_xerror_linux.c +++ b/cmd/watcher/handle_xerror_linux.c @@ -29,8 +29,29 @@ from The Open Group. #include #include #include +#include + +typedef int (*xGetErrorText) ( + Display *display, + int code, + char *buffer_return, + int length +); +typedef int (*xGetErrorDatabaseText) ( + Display *display, + _Xconst char *name, + _Xconst char *message, + _Xconst char *default_string, + char *buffer_return, + int length +); +struct X11Lib { + xGetErrorText XGetErrorText; + xGetErrorDatabaseText XGetErrorDatabaseText; +}; static int _XPrintDefaultError( + struct X11Lib *lib, Display *dpy, XErrorEvent *event, FILE *fp) @@ -41,15 +62,15 @@ static int _XPrintDefaultError( const char *mtype = "XlibMessage"; register _XExtension *ext = (_XExtension *)NULL; _XExtension *bext = (_XExtension *)NULL; - XGetErrorText(dpy, event->error_code, buffer, BUFSIZ); - XGetErrorDatabaseText(dpy, mtype, "XError", "X Error", mesg, BUFSIZ); + lib->XGetErrorText(dpy, event->error_code, buffer, BUFSIZ); + lib->XGetErrorDatabaseText(dpy, mtype, "XError", "X Error", mesg, BUFSIZ); (void) fprintf(fp, "%s: %s\n ", mesg, buffer); - XGetErrorDatabaseText(dpy, mtype, "MajorCode", "Request Major code %d", + lib->XGetErrorDatabaseText(dpy, mtype, "MajorCode", "Request Major code %d", mesg, BUFSIZ); (void) fprintf(fp, mesg, event->request_code); if (event->request_code < 128) { snprintf(number, sizeof(number), "%d", event->request_code); - XGetErrorDatabaseText(dpy, "XRequest", number, "", buffer, BUFSIZ); + lib->XGetErrorDatabaseText(dpy, "XRequest", number, "", buffer, BUFSIZ); } else { for (ext = dpy->ext_procs; ext && (ext->codes.major_opcode != event->request_code); @@ -63,13 +84,13 @@ static int _XPrintDefaultError( } (void) fprintf(fp, " (%s)\n", buffer); if (event->request_code >= 128) { - XGetErrorDatabaseText(dpy, mtype, "MinorCode", "Request Minor code %d", + lib->XGetErrorDatabaseText(dpy, mtype, "MinorCode", "Request Minor code %d", mesg, BUFSIZ); fputs(" ", fp); (void) fprintf(fp, mesg, event->minor_code); if (ext) { snprintf(mesg, sizeof(mesg), "%s.%d", ext->name, event->minor_code); - XGetErrorDatabaseText(dpy, "XRequest", mesg, "", buffer, BUFSIZ); + lib->XGetErrorDatabaseText(dpy, "XRequest", mesg, "", buffer, BUFSIZ); (void) fprintf(fp, " (%s)", buffer); } fputs("\n", fp); @@ -95,7 +116,7 @@ static int _XPrintDefaultError( event->error_code - bext->codes.first_error); else strcpy(buffer, "Value"); - XGetErrorDatabaseText(dpy, mtype, buffer, "", mesg, BUFSIZ); + lib->XGetErrorDatabaseText(dpy, mtype, buffer, "", mesg, BUFSIZ); if (mesg[0]) { fputs(" ", fp); (void) fprintf(fp, mesg, event->resourceid); @@ -117,23 +138,23 @@ static int _XPrintDefaultError( (event->error_code == BadValue) || (event->error_code == BadAtom)) { if (event->error_code == BadValue) - XGetErrorDatabaseText(dpy, mtype, "Value", "Value 0x%x", + lib->XGetErrorDatabaseText(dpy, mtype, "Value", "Value 0x%x", mesg, BUFSIZ); else if (event->error_code == BadAtom) - XGetErrorDatabaseText(dpy, mtype, "AtomID", "AtomID 0x%x", + lib->XGetErrorDatabaseText(dpy, mtype, "AtomID", "AtomID 0x%x", mesg, BUFSIZ); else - XGetErrorDatabaseText(dpy, mtype, "ResourceID", "ResourceID 0x%x", + lib->XGetErrorDatabaseText(dpy, mtype, "ResourceID", "ResourceID 0x%x", mesg, BUFSIZ); fputs(" ", fp); (void) fprintf(fp, mesg, event->resourceid); fputs("\n", fp); } - XGetErrorDatabaseText(dpy, mtype, "ErrorSerial", "Error Serial #%d", + lib->XGetErrorDatabaseText(dpy, mtype, "ErrorSerial", "Error Serial #%d", mesg, BUFSIZ); fputs(" ", fp); (void) fprintf(fp, mesg, event->serial); - XGetErrorDatabaseText(dpy, mtype, "CurrentSerial", "Current Serial #%lld", + lib->XGetErrorDatabaseText(dpy, mtype, "CurrentSerial", "Current Serial #%lld", mesg, BUFSIZ); fputs("\n ", fp); (void) fprintf(fp, mesg, (unsigned long long)(X_DPY_GET_REQUEST(dpy))); @@ -144,9 +165,37 @@ static int _XPrintDefaultError( /* handleXError is _XDefaultError from libx11/src/XlibInt.c stripped of its -exit(1) call. +exit(1) call and with dynamic XGetErrorText and XGetErrorDatabaseText added. */ int handleXError(Display *dpy, XErrorEvent *event) { - _XPrintDefaultError (dpy, event, stderr); - return 0; + dlerror(); + void *h = dlopen("libX11.so", RTLD_LAZY); + if (h == NULL) { + (void) fprintf(stderr, "failed to open libX11: %s\n", dlerror()); + (void) fprintf(stderr, "X error: resourceid=%ld serial=%ld error_code=%d request_code=%d minor_code=%d\n", + event->resourceid, event->serial, event->error_code, event->request_code, event->minor_code); + return 0; + } + struct X11Lib lib = { + .XGetErrorText = dlsym(h, "XGetErrorText"), + .XGetErrorDatabaseText = dlsym(h, "xGetErrorDatabaseText") + }; + if (lib.XGetErrorText != NULL && lib.XGetErrorDatabaseText != NULL) { + _XPrintDefaultError(&lib, dpy, event, stderr); + } else { + if (lib.XGetErrorText == NULL) { + (void) fprintf(stderr, "failed to get XGetErrorText: %s\n", dlerror()); + } + if (lib.XGetErrorDatabaseText == NULL) { + (void) fprintf(stderr, "failed to get XGetErrorDatabaseText: %s\n", dlerror()); + } + (void) fprintf(stderr, "X error: resourceid=%ld serial=%ld error_code=%d request_code=%d minor_code=%d\n", + event->resourceid, event->serial, event->error_code, event->request_code, event->minor_code); + } + dlclose(h); + char *e = dlerror(); + if (e != NULL) { + (void) fprintf(stderr, "failed to close libX11: %s\n", e); + } + return 0; } diff --git a/cmd/watcher/poll_xorg_linux.go b/cmd/watcher/poll_xorg_linux.go index 8dcefb7..82d20b6 100644 --- a/cmd/watcher/poll_xorg_linux.go +++ b/cmd/watcher/poll_xorg_linux.go @@ -5,21 +5,22 @@ package main import ( + "errors" "fmt" "strconv" "strings" "time" watcher "github.com/kortschak/dex/cmd/watcher/api" + "github.com/kortschak/dex/cmd/watcher/dl" ) /* -#cgo LDFLAGS: -lX11 -lXss -lXRes #include #include #include -#include -#include +#include +#include struct details { int wid; @@ -31,19 +32,62 @@ struct details { int saver_state; }; +typedef Display* (*xOpenDisplay) (_Xconst char *display_name); +typedef int (*xCloseDisplay) (Display *display); +typedef XErrorHandler (*xSetErrorHandler) (XErrorHandler handler); +typedef int (*xGetInputFocus) (Display *display, Window *focus_return, int *revert_to_return ); +typedef Window (*xRootWindow) (Display* display, int screen_number); +typedef Atom (*xInternAtom) (Display *display, _Xconst char *atom_name, Bool only_if_exists); +typedef Status (*xQueryTree) (Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, unsigned int *nchildren_return); +typedef Status (*xGetClassHint) (Display* display, Window w, XClassHint* class_hints_return); +typedef int (*xGetWindowProperty) (Display *display, Window w, Atom property, long long_offset, long long_length, Bool delete, Atom req_type, Atom *actual_type_return, int *actual_format_return, unsigned long *nitems_return, unsigned long *bytes_after_return, unsigned char **prop_return); +typedef void (*xFree) (void *data); +struct X11Lib { + xOpenDisplay XOpenDisplay; + xCloseDisplay XCloseDisplay; + xSetErrorHandler XSetErrorHandler; + xGetInputFocus XGetInputFocus; + xRootWindow XRootWindow; + xInternAtom XInternAtom; + xQueryTree XQueryTree; + xGetClassHint XGetClassHint; + xGetWindowProperty XGetWindowProperty; + xFree XFree; +}; + +typedef Bool (*xScreenSaverQueryExtension) (Display *display, int *event_base, int *error_base); +typedef Status (*xScreenSaverQueryInfo) (Display *display, Drawable drawable, XScreenSaverInfo *info); +struct XssLib { + xScreenSaverQueryExtension XScreenSaverQueryExtension; + xScreenSaverQueryInfo XScreenSaverQueryInfo; +}; + +typedef Status (*xResQueryClientIds) (Display *dpy, long num_specs, XResClientIdSpec *client_specs, long *num_ids, XResClientIdValue **client_ids); +typedef pid_t (*xResGetClientPid) (XResClientIdValue* value); +typedef void (*xResClientIdsDestroy) (long num_ids, XResClientIdValue *client_ids); +struct XResLib { + xResQueryClientIds XResQueryClientIds; + xResGetClientPid XResGetClientPid; + xResClientIdsDestroy XResClientIdsDestroy; +}; + #define enodisplay 1 -#define enoscreensaver 2 + +#define fdisplay 1 +#define fclassname 2 +#define fwindowname 4 +#define fscreensaver 8 int handleXError(Display *dpy, XErrorEvent *event); -pid_t get_window_pid(Display *display, Window window); -Status get_focused_window(Display *display, Window *window_return); -Status get_window_classname(Display *display, Window window, char **class_ret, char **name_ret); -Status get_window_name(Display *display, Window window, char **name_ret); -Status get_screen_saver_info(Display *display, XScreenSaverInfo *saver_info); -char *get_window_property_by_atom(Display *display, Window window, Atom atom, long *nitems, Atom *type, int *size); +pid_t get_window_pid(struct XResLib *lib, Display *display, Window window); +Status get_focused_window(struct X11Lib *lib, Display *display, Window *window_return); +Status get_window_classname(struct X11Lib *lib, Display *display, Window window, char **class_ret, char **name_ret); +Status get_window_name(struct X11Lib *lib, Display *display, Window window, char **name_ret); +Status get_screen_saver_info(struct X11Lib *x11Lib, struct XssLib *xssLib, Display *display, XScreenSaverInfo *saver_info); +char *get_window_property_by_atom(struct X11Lib *lib, Display *display, Window window, Atom atom, long *nitems, Atom *type, int *size); -int activeWindow(struct details *d) { +int activeWindow(struct details *d, struct X11Lib *lib, struct XResLib *xResLib, struct XssLib *xssLib) { if (d == NULL) { return 0; } @@ -52,59 +96,61 @@ int activeWindow(struct details *d) { Window window; int flags = 0; - XErrorHandler oldHandler = XSetErrorHandler(handleXError); + XErrorHandler oldHandler = lib->XSetErrorHandler(handleXError); - Display *display = XOpenDisplay(NULL); + Display *display = lib->XOpenDisplay(NULL); if (display == NULL) { - XSetErrorHandler(oldHandler); + lib->XSetErrorHandler(oldHandler); return -enodisplay; } XScreenSaverInfo saver_info; - ok = get_screen_saver_info(display, &saver_info); - if (!ok) { - XCloseDisplay(display); - XSetErrorHandler(oldHandler); - return -enoscreensaver; + ok = get_screen_saver_info(lib, xssLib, display, &saver_info); + if (ok) { + d->idle = saver_info.idle; + d->saver_state = saver_info.state; + flags |= fscreensaver; } - d->idle = saver_info.idle; - d->saver_state = saver_info.state; - ok = get_focused_window(display, &window); + ok = get_focused_window(lib, display, &window); if (!ok) { - XCloseDisplay(display); - XSetErrorHandler(oldHandler); + lib->XCloseDisplay(display); + lib->XSetErrorHandler(oldHandler); return flags; } - flags = 1; + flags |= fdisplay; d->wid = window; - d->pid = get_window_pid(display, window); - ok = get_window_classname(display, window, &(d->class), &(d->name)); + d->pid = get_window_pid(xResLib, display, window); + ok = get_window_classname(lib, display, window, &(d->class), &(d->name)); if (ok) { - flags |= 2; + flags |= fclassname; } - ok = get_window_name(display, window, &(d->window)); + ok = get_window_name(lib, display, window, &(d->window)); if (ok) { - flags |= 4; + flags |= fwindowname; } - XCloseDisplay(display); - XSetErrorHandler(oldHandler); + lib->XCloseDisplay(display); + lib->XSetErrorHandler(oldHandler); return flags; } -void freeDetails(struct details *d) { +void freeDetails(struct X11Lib *lib, struct details *d) { if (d->class) { - XFree((void*)d->class); + lib->XFree((void*)d->class); } if (d->name) { - XFree((void*)d->name); + lib->XFree((void*)d->name); } if (d->window) { - XFree((void*)d->window); + lib->XFree((void*)d->window); } } -pid_t get_window_pid(Display *display, Window window) { +pid_t get_window_pid(struct XResLib *lib, Display *display, Window window) { pid_t pid = -1; + if (lib == NULL) { + return pid; + } + XResClientIdSpec spec = { .client = window, .mask = XRES_CLIENT_ID_PID_MASK, @@ -112,22 +158,22 @@ pid_t get_window_pid(Display *display, Window window) { long num_ids; XResClientIdValue *client_ids; - XResQueryClientIds(display, 1, &spec, &num_ids, &client_ids); + lib->XResQueryClientIds(display, 1, &spec, &num_ids, &client_ids); for (int i = 0; i < num_ids; i++) { if (client_ids[i].spec.mask == XRES_CLIENT_ID_PID_MASK) { - pid = XResGetClientPid(&client_ids[i]); + pid = lib->XResGetClientPid(&client_ids[i]); break; } } - XResClientIdsDestroy(num_ids, client_ids); + lib->XResClientIdsDestroy(num_ids, client_ids); return pid; } -Status get_focused_window(Display *display, Window *window_return) { +Status get_focused_window(struct X11Lib *lib, Display *display, Window *window_return) { int unused_revert_ret; Status ok; - ok = XGetInputFocus(display, window_return, &unused_revert_ret); + ok = lib->XGetInputFocus(display, window_return, &unused_revert_ret); if (!ok) { return ok; } @@ -135,7 +181,7 @@ Status get_focused_window(Display *display, Window *window_return) { Window window = *window_return; Window unused_root_return, parent, *children = NULL; unsigned int nchildren; - Atom atom_wmstate = XInternAtom(display, "WM_STATE", False); + Atom atom_wmstate = lib->XInternAtom(display, "WM_STATE", False); int done = False; while (!done) { @@ -144,12 +190,12 @@ Status get_focused_window(Display *display, Window *window_return) { } long items; - get_window_property_by_atom(display, window, atom_wmstate, &items, NULL, NULL); + get_window_property_by_atom(lib, display, window, atom_wmstate, &items, NULL, NULL); if (items == 0) { - XQueryTree(display, window, &unused_root_return, &parent, &children, &nchildren); + lib->XQueryTree(display, window, &unused_root_return, &parent, &children, &nchildren); if (children != NULL) { - XFree(children); + lib->XFree(children); } window = parent; } else { @@ -160,14 +206,17 @@ Status get_focused_window(Display *display, Window *window_return) { return 1; } -Status get_screen_saver_info(Display *display, XScreenSaverInfo *saver_info) { +Status get_screen_saver_info(struct X11Lib *x11Lib, struct XssLib *xssLib, Display *display, XScreenSaverInfo *saver_info) { + if (xssLib == NULL) { + return 0; + } int event_base, error_base = 0; - Bool ok = XScreenSaverQueryExtension(display, &event_base, &error_base); + Bool ok = xssLib->XScreenSaverQueryExtension(display, &event_base, &error_base); if (!ok) { return 0; } - Window root = XRootWindow(display, 0); - return XScreenSaverQueryInfo(display, root, saver_info); + Window root = x11Lib->XRootWindow(display, 0); + return xssLib->XScreenSaverQueryInfo(display, root, saver_info); } static Atom atom_NET_WM_NAME = -1; @@ -175,33 +224,33 @@ static Atom atom_WM_NAME = -1; static Atom atom_STRING = -1; static Atom atom_UTF8_STRING = -1; -Status get_window_name(Display *display, Window window, char **name_ret) { +Status get_window_name(struct X11Lib *lib, Display *display, Window window, char **name_ret) { if (atom_NET_WM_NAME == (Atom)-1) { - atom_NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", False); + atom_NET_WM_NAME = lib->XInternAtom(display, "_NET_WM_NAME", False); } if (atom_WM_NAME == (Atom)-1) { - atom_WM_NAME = XInternAtom(display, "WM_NAME", False); + atom_WM_NAME = lib->XInternAtom(display, "WM_NAME", False); } if (atom_STRING == (Atom)-1) { - atom_STRING = XInternAtom(display, "STRING", False); + atom_STRING = lib->XInternAtom(display, "STRING", False); } if (atom_UTF8_STRING == (Atom)-1) { - atom_UTF8_STRING = XInternAtom(display, "UTF8_STRING", False); + atom_UTF8_STRING = lib->XInternAtom(display, "UTF8_STRING", False); } Atom type; int size; long nitems; - *name_ret = get_window_property_by_atom(display, window, atom_NET_WM_NAME, &nitems, &type, &size); + *name_ret = get_window_property_by_atom(lib, display, window, atom_NET_WM_NAME, &nitems, &type, &size); if (nitems == 0) { - *name_ret = get_window_property_by_atom(display, window, atom_WM_NAME, &nitems, &type, &size); + *name_ret = get_window_property_by_atom(lib, display, window, atom_WM_NAME, &nitems, &type, &size); } return 1; } -Status get_window_classname(Display *display, Window window, char **class_ret, char **name_ret) { +Status get_window_classname(struct X11Lib *lib, Display *display, Window window, char **class_ret, char **name_ret) { XClassHint classhint; - Status status = XGetClassHint(display, window, &classhint); + Status status = lib->XGetClassHint(display, window, &classhint); if (status) { *class_ret = (char*)classhint.res_class; *name_ret = (char*)classhint.res_name; @@ -211,14 +260,14 @@ Status get_window_classname(Display *display, Window window, char **class_ret, c return status; } -char *get_window_property_by_atom(Display *display, Window window, Atom atom, long *nitems, Atom *type, int *size) { +char *get_window_property_by_atom(struct X11Lib *lib, Display *display, Window window, Atom atom, long *nitems, Atom *type, int *size) { Atom actual_type_return; int actual_format_return; unsigned long nitems_return; unsigned long unused_bytes_after; unsigned char *prop_return; - Status status = XGetWindowProperty(display, window, atom, 0, (~0L), + Status status = lib->XGetWindowProperty(display, window, atom, 0, (~0L), False, AnyPropertyType, &actual_type_return, &actual_format_return, &nitems_return, &unused_bytes_after, &prop_return); @@ -244,18 +293,182 @@ func init() { } func newXOrgDetailer() (detailer, error) { - return &xOrgDetailer{last: time.Now()}, nil + var ( + d xOrgDetailer + err error + ) + d.x11, d.x11Lib, err = openX11Lib() + if err != nil { + return noDetails{}, err + } + d.xRes, d.xResLib = openXResLib() + d.xss, d.xssLib = openXssLib() + if d.xss != nil { + d.last = time.Now() + } + return &d, nil +} + +func openX11Lib() (*dl.Lib, *C.struct_X11Lib, error) { + x11, err := dl.Open(dl.RTLD_LAZY, "libX11.so", "libX11.so.6") + if err != nil { + return nil, nil, err + } + var x11Lib C.struct_X11Lib + + sym, err := x11.Symbol("XOpenDisplay") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XOpenDisplay = C.xOpenDisplay(sym) + + sym, err = x11.Symbol("XCloseDisplay") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XCloseDisplay = C.xCloseDisplay(sym) + + sym, err = x11.Symbol("XSetErrorHandler") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XSetErrorHandler = C.xSetErrorHandler(sym) + + sym, err = x11.Symbol("XGetInputFocus") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XGetInputFocus = C.xGetInputFocus(sym) + + sym, err = x11.Symbol("XRootWindow") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XRootWindow = C.xRootWindow(sym) + + sym, err = x11.Symbol("XInternAtom") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XInternAtom = C.xInternAtom(sym) + + sym, err = x11.Symbol("XQueryTree") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XQueryTree = C.xQueryTree(sym) + + sym, err = x11.Symbol("XGetClassHint") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XGetClassHint = C.xGetClassHint(sym) + + sym, err = x11.Symbol("XGetWindowProperty") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XGetWindowProperty = C.xGetWindowProperty(sym) + + sym, err = x11.Symbol("XFree") + if err != nil { + x11.Close() + return nil, nil, err + } + x11Lib.XFree = C.xFree(sym) + + return x11, &x11Lib, nil +} + +func openXssLib() (*dl.Lib, *C.struct_XssLib) { + xss, err := dl.Open(dl.RTLD_LAZY, "libXss.so", "libXss.so.1") + if err != nil { + return nil, nil + } + var xssLib C.struct_XssLib + + sym, err := xss.Symbol("XScreenSaverQueryExtension") + if err != nil { + xss.Close() + return nil, nil + } + xssLib.XScreenSaverQueryExtension = C.xScreenSaverQueryExtension(sym) + + sym, err = xss.Symbol("XScreenSaverQueryInfo") + if err != nil { + xss.Close() + return nil, nil + } + xssLib.XScreenSaverQueryInfo = C.xScreenSaverQueryInfo(sym) + + return xss, &xssLib +} + +func openXResLib() (*dl.Lib, *C.struct_XResLib) { + xRes, err := dl.Open(dl.RTLD_LAZY, "libXRes.so", "libXRes.so.1") + if err != nil { + return nil, nil + } + var xResLib C.struct_XResLib + + sym, err := xRes.Symbol("XResQueryClientIds") + if err != nil { + xRes.Close() + return nil, nil + } + xResLib.XResQueryClientIds = C.xResQueryClientIds(sym) + + sym, err = xRes.Symbol("XResGetClientPid") + if err != nil { + xRes.Close() + return nil, nil + } + xResLib.XResGetClientPid = C.xResGetClientPid(sym) + + sym, err = xRes.Symbol("XResClientIdsDestroy") + if err != nil { + xRes.Close() + return nil, nil + } + xResLib.XResClientIdsDestroy = C.xResClientIdsDestroy(sym) + + return xRes, &xResLib } type xOrgDetailer struct { last time.Time + + x11 *dl.Lib + x11Lib *C.struct_X11Lib + + xRes *dl.Lib + xResLib *C.struct_XResLib + + xss *dl.Lib + xssLib *C.struct_XssLib } func (*xOrgDetailer) strategy() string { return "xorg" } +func (d *xOrgDetailer) Close() error { + d.x11Lib = nil + d.xResLib = nil + d.xssLib = nil + return errors.Join(d.x11.Close(), d.xRes.Close(), d.xss.Close()) +} + func (d *xOrgDetailer) details() (watcher.Details, error) { var det C.struct_details - flags := C.activeWindow(&det) + flags := C.activeWindow(&det, d.x11Lib, d.xResLib, d.xssLib) if flags < 0 { return watcher.Details{}, xOrgDetailError(-flags) } @@ -271,8 +484,9 @@ func (d *xOrgDetailer) details() (watcher.Details, error) { LastInput: d.last, Locked: det.saver_state == C.ScreenSaverOn, } - C.freeDetails(&det) - if flags != 0b111 && !active.Locked { + C.freeDetails(d.x11Lib, &det) + const allWindowDetails = C.fdisplay | C.fclassname | C.fwindowname + if flags&C.fscreensaver == 0 || (!active.Locked && flags&allWindowDetails != allWindowDetails) { return active, warning{fmt.Errorf("failed to obtain some details: missing %s", missing(flags))} } return active, nil @@ -288,8 +502,7 @@ func (e xOrgDetailError) Error() string { } var failureReason = [...]string{ - C.enodisplay: "no display", - C.enoscreensaver: "no screensaver info", + C.enodisplay: "no display", } // warning is a warn-only error. @@ -299,14 +512,17 @@ type warning struct { func missing(flags C.int) string { m := make([]string, 0, 3) - if flags&0b001 == 0 { + if flags&C.fdisplay == 0 { m = append(m, "window id") } - if flags&0b010 == 0 { + if flags&C.fclassname == 0 { m = append(m, "class name") } - if flags&0b100 == 0 { + if flags&C.fwindowname == 0 { m = append(m, "window name") } + if flags&C.fscreensaver == 0 { + m = append(m, "screensaver info") + } return strings.Join(m, ", ") } diff --git a/cmd/watcher/scrnsaver.h b/cmd/watcher/scrnsaver.h new file mode 100644 index 0000000..654aef6 --- /dev/null +++ b/cmd/watcher/scrnsaver.h @@ -0,0 +1,134 @@ +/* + * +Copyright (c) 1992 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + * + * Author: Keith Packard, MIT X Consortium + */ + +#ifndef _SCRNSAVER_H_ +#define _SCRNSAVER_H_ + +#include +#include +#include + +typedef struct { + int type; /* of event */ + unsigned long serial; /* # of last request processed by server */ + Bool send_event; /* true if this came frome a SendEvent request */ + Display *display; /* Display the event was read from */ + Window window; /* screen saver window */ + Window root; /* root window of event screen */ + int state; /* ScreenSaverOff, ScreenSaverOn, ScreenSaverCycle*/ + int kind; /* ScreenSaverBlanked, ...Internal, ...External */ + Bool forced; /* extents of new region */ + Time time; /* event timestamp */ +} XScreenSaverNotifyEvent; + +typedef struct { + Window window; /* screen saver window - may not exist */ + int state; /* ScreenSaverOff, ScreenSaverOn, ScreenSaverDisabled*/ + int kind; /* ScreenSaverBlanked, ...Internal, ...External */ + unsigned long til_or_since; /* time til or since screen saver */ + unsigned long idle; /* total time since last user input */ + unsigned long eventMask; /* currently selected events for this client */ +} XScreenSaverInfo; + +_XFUNCPROTOBEGIN + +extern Bool XScreenSaverQueryExtension ( + Display* /* display */, + int* /* event_base */, + int* /* error_base */ +); + +extern Status XScreenSaverQueryVersion ( + Display* /* display */, + int* /* major_version */, + int* /* minor_version */ +); + +extern XScreenSaverInfo *XScreenSaverAllocInfo ( + void +); + +extern Status XScreenSaverQueryInfo ( + Display* /* display */, + Drawable /* drawable */, + XScreenSaverInfo* /* info */ +); + +extern void XScreenSaverSelectInput ( + Display* /* display */, + Drawable /* drawable */, + unsigned long /* eventMask */ +); + +extern void XScreenSaverSetAttributes ( + Display* /* display */, + Drawable /* drawable */, + int /* x */, + int /* y */, + unsigned int /* width */, + unsigned int /* height */, + unsigned int /* border_width */, + int /* depth */, + unsigned int /* class */, + Visual * /* visual */, + unsigned long /* valuemask */, + XSetWindowAttributes * /* attributes */ +); + +extern void XScreenSaverUnsetAttributes ( + Display* /* display */, + Drawable /* drawable */ +); + +extern Status XScreenSaverRegister ( + Display* /* display */, + int /* screen */, + XID /* xid */, + Atom /* type */ +); + +extern Status XScreenSaverUnregister ( + Display* /* display */, + int /* screen */ +); + +extern Status XScreenSaverGetRegistered ( + Display* /* display */, + int /* screen */, + XID* /* xid */, + Atom* /* type */ +); + +extern void XScreenSaverSuspend ( + Display* /* display */, + Bool /* suspend */ +); + +_XFUNCPROTOEND + +#endif /* _SCRNSAVER_H_ */