From b2222c9da47b278a3addef48250513420df405ac Mon Sep 17 00:00:00 2001 From: Alexis Murzeau Date: Tue, 15 Aug 2023 19:18:23 +0200 Subject: [PATCH] update class name and intercept maximize and window activation events Use "spotify" class name instead of "Spotify" to match the name used afterwards. Spotify now also send requests to maximize and activate focus to the window. Filter these requests so rules to minize spotify are correctly applied. --- spotifywm.cpp | 144 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 19 deletions(-) diff --git a/spotifywm.cpp b/spotifywm.cpp index 3766e59..13efd44 100644 --- a/spotifywm.cpp +++ b/spotifywm.cpp @@ -12,6 +12,10 @@ #include #include #include +#include + +#include +#include #define STR_(x) # x #define STR(x) STR_(x) @@ -20,12 +24,17 @@ extern "C" { extern char * program_invocation_short_name; // provided by glibc } +static uint32_t X11_ATOM_NET_WM_STATE; +static uint32_t X11_ATOM_NET_WM_STATE_MAXIMIZED_VERT; +static uint32_t X11_ATOM_NET_WM_STATE_MAXIMIZED_HORZ; +static uint32_t X11_ATOM_NET_ACTIVE_WINDOW; + void spotifywm_init(void) __attribute__((constructor)); void spotifywm_init(void) { // Prevent spotifywm.so from being attached to processes started by steam const char *envname = "LD_PRELOAD"; const char *oldenv = getenv(envname); - if (oldenv && false) { + if (oldenv) { char *env = strdup(oldenv); char *pos = strstr(env, STR(SONAME)); if (pos) { @@ -40,6 +49,49 @@ void spotifywm_init(void) { fprintf(stderr, "[spotifywm] attached to spotify\n"); } +static void init_atoms() { + if(X11_ATOM_NET_WM_STATE == 0) { + Display* dpy = XOpenDisplay(":0"); + X11_ATOM_NET_WM_STATE = XInternAtom(dpy, "_NET_WM_STATE", 1); + X11_ATOM_NET_WM_STATE_MAXIMIZED_VERT = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_VERT", 1); + X11_ATOM_NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", 1); + X11_ATOM_NET_ACTIVE_WINDOW = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", 1); + XCloseDisplay(dpy); + } +} + +#ifdef DEBUG +static std::string get_atom_name(uint32_t atom) { + Display* dpy = XOpenDisplay(":0"); + char* result = XGetAtomName(dpy, atom); + std::string ret; + if(result) { + ret = result; + } + XCloseDisplay(dpy); + + return ret; +} +#endif + +template +static const T* concat_iovec_buffers(struct iovec *vector, size_t count) { + if(count == 0) { + return nullptr; + } + + if(count == 1) { + if(vector[0].iov_len >= sizeof(T)) + return (const T*) vector[0].iov_base; + else + return nullptr; + } + + fprintf(stderr, "can't convert buffer into pointer of %zu bytes as it uses %zu iovec buffers\n", sizeof(T), count); + + return nullptr; +} + #define BASE_NAME(SymbolName) base_ ## SymbolName #define TYPE_NAME(SymbolName) SymbolName ## _t #define INTERCEPT(ReturnType, SymbolName, ...) \ @@ -71,25 +123,79 @@ INTERCEPT(unsigned int, xcb_send_request, struct iovec *vector, const xcb_protocol_request_t *request ) { - // Check if we are sending a MapWindow request - // This code is called from chromium's source code: - // ui/gfx/x/generated_protos/xproto.cc in XProto::MapWindow - if(request->count >= 1 && vector[0].iov_len >= 8 && ((uint8_t*)vector[0].iov_base)[0] == 8) { - uint32_t window_id = ((uint32_t*)vector[0].iov_base)[1]; - - fprintf(stderr, "[spotifywm] spotify window %x found\n", window_id); - - // Don't use the same XCB connection as spotify is checking the returned number - Display* dpy = XOpenDisplay(NULL); - XClassHint* classHint = XAllocClassHint(); - if (classHint) { - classHint->res_name = (char*)"spotify"; - classHint->res_class = (char*)"Spotify"; - XSetClassHint(dpy, window_id, classHint); - XFree(classHint); + struct __attribute__((packed)) RequestHeader { + uint8_t major_opcode; + uint8_t minor_opcode; + uint16_t length; + uint32_t data[0]; + }; + struct RequestHeader* header; + + if(request->count == 0) + goto forward; + + if(vector[0].iov_len < sizeof(struct RequestHeader)) + goto forward; + + header = (struct RequestHeader*) vector[0].iov_base; + + switch(header->major_opcode) { + case XCB_MAP_WINDOW: { + const struct xcb_map_window_request_t* req = concat_iovec_buffers(vector, request->count); + if(!req) + break; + + fprintf(stderr, "[spotifywm] spotify MapWindow %x found\n", req->window); + + xcb_flush(c); + + // Don't use the same XCB connection as spotify is checking the returned number + Display* dpy = XOpenDisplay(":0"); + XClassHint* classHint = XAllocClassHint(); + if (classHint) { + classHint->res_name = (char*)"spotify"; + classHint->res_class = (char*)"spotify"; + int ret = XSetClassHint(dpy, req->window, classHint); + fprintf(stderr, "XSetClassHint = %d\n", ret); + XFree(classHint); + } + XSync(dpy, 1); + XCloseDisplay(dpy); + break; + } + + case XCB_SEND_EVENT: { + const struct xcb_send_event_request_t* req = concat_iovec_buffers(vector, request->count); + if(!req) + break; + struct xcb_client_message_event_t* event = (struct xcb_client_message_event_t*) req->event; + +#ifdef DEBUG + fprintf(stderr, "[spotifywm] spotify SendEvent %d (%s) {%d (%s), %d (%s), %d (%s), %d (%s), %d (%s)}\n", + event->type, get_atom_name(event->type).c_str(), + event->data.data32[0], get_atom_name(event->data.data32[0]).c_str(), + event->data.data32[1], get_atom_name(event->data.data32[1]).c_str(), + event->data.data32[2], get_atom_name(event->data.data32[2]).c_str(), + event->data.data32[3], get_atom_name(event->data.data32[3]).c_str(), + event->data.data32[4], get_atom_name(event->data.data32[4]).c_str()); +#endif + + init_atoms(); + if(event->type == X11_ATOM_NET_WM_STATE && event->format == 32 && event->data.data32[0] == 1) { + // Ignore requests to maximize window + for(size_t i = 1; i < 3; i++) { + if(event->data.data32[i] == X11_ATOM_NET_WM_STATE_MAXIMIZED_HORZ || event->data.data32[i] == X11_ATOM_NET_WM_STATE_MAXIMIZED_VERT) { + event->data.data32[i] = XCB_ATOM_NONE; + } + } + } else if(event->type == X11_ATOM_NET_ACTIVE_WINDOW) { + // Ignore request to make focus on window, replace request with no operation + header->major_opcode = XCB_NO_OPERATION; + } + break; } - XCloseDisplay(dpy); } - + +forward: return BASE(xcb_send_request)(c, flags, vector, request); }