From 9424ab8ca54a5ad1501125e45432a14815e4341b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Tue, 9 Apr 2024 12:28:26 +0000 Subject: [PATCH] Fix event propagation on Openbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Events now get correctly propagated to a window (or root if none) behind conky. This was a necessary change to handle cases such as MATE+caja where caja is used between conky and background to show icons and desktop menu. * Don't change input focus on propagation Could cause input focus flickering, so I'm leaving it up to WMs to manage focus. Signed-off-by: Tin Å vagelj --- src/display-x11.cc | 2 + src/x11.cc | 193 +++++++++++++++++++++++++++++++++++++-------- src/x11.h | 58 +++++++++++++- 3 files changed, 219 insertions(+), 34 deletions(-) diff --git a/src/display-x11.cc b/src/display-x11.cc index 3d60e19b..4cff779d 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -410,6 +410,8 @@ bool display_output_x11::main_loop_wait(double t) { if (data->evtype == XI_Motion && is_cursor_move) { Window query_result = query_x11_window_at_pos(display, data->root_x, data->root_y); + // query_result is not window.window in some cases. + query_result = query_x11_last_descendant(display, query_result); static bool cursor_inside = false; diff --git a/src/x11.cc b/src/x11.cc index f76c3ee7..9acdfe3c 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1352,43 +1352,82 @@ InputEvent *xev_as_input_event(XEvent &ev) { } } +/// @brief Returns a mask for the event_type +/// @param event_type Xlib event type +/// @return Xlib event mask +int ev_to_mask(int event_type) { + switch (event_type) { + case KeyPress: + return KeyPressMask; + case KeyRelease: + return KeyReleaseMask; + case ButtonPress: + return ButtonPressMask; + case ButtonRelease: + return ButtonReleaseMask; + case EnterNotify: + return EnterWindowMask; + case LeaveNotify: + return LeaveWindowMask; + case MotionNotify: + return PointerMotionMask; + default: + return NoEventMask; + } +} + void propagate_x11_event(XEvent &ev) { InputEvent *i_ev = xev_as_input_event(ev); - /* forward the event to the desktop window */ - if (i_ev != nullptr) { - i_ev->common.window = window.desktop; - i_ev->common.x = i_ev->common.x_root; - i_ev->common.y = i_ev->common.y_root; - } else { + if (i_ev == nullptr) { // Not a known input event; blindly propagating them causes loops and all // sorts of other evil. return; } - DBGP2("Propagating event: { type: %d; serial: %d }", i_ev->type, i_ev->common.serial); - XSendEvent(display, window.desktop, False, window.event_mask, &ev); - int _revert_to; - Window focused; - XGetInputFocus(display, &focused, &_revert_to); - if (focused == window.window) { - Time time = CurrentTime; - if (i_ev != nullptr) { time = i_ev->common.time; } - XSetInputFocus(display, window.desktop, RevertToPointerRoot, time); + i_ev->common.window = window.desktop; + i_ev->common.x = i_ev->common.x_root; + i_ev->common.y = i_ev->common.y_root; + i_ev->common.time = CurrentTime; + + /* forward the event to the window below conky (e.g. caja) or desktop */ + { + std::vector below = query_x11_windows_at_pos( + display, i_ev->common.x_root, i_ev->common.y_root, + [](XWindowAttributes &a) { return a.map_state == IsViewable; }); + auto it = std::remove_if(below.begin(), below.end(), + [](Window w) { return w == window.window; }); + below.erase(it, below.end()); + if (!below.empty()) { + i_ev->common.window = below.back(); + + Window _ignore; + // Update event x and y coordinates to be target window relative + XTranslateCoordinates(display, window.root, i_ev->common.window, + i_ev->common.x_root, i_ev->common.y_root, + &i_ev->common.x, &i_ev->common.y, &_ignore); + } + // drop below vector } + + XUngrabPointer(display, CurrentTime); + XSendEvent(display, i_ev->common.window, False, ev_to_mask(i_ev->type), &ev); } -#ifdef BUILD_MOUSE_EVENTS -// Assuming parent has a simple linear stack of descendants, this function -// returns the last leaf on the graph. -inline Window last_descendant(Display *display, Window parent) { +/// @brief This function returns the last descendant of a window (leaf) on the +/// graph. +/// +/// This function assumes the window stack below `parent` is linear. If it +/// isn't, it's only guaranteed that _some_ descendant of `parent` will be +/// returned. If provided `parent` has no descendants, the `parent` is returned. +Window query_x11_last_descendant(Display *display, Window parent) { Window _ignored, *children; std::uint32_t count; Window current = parent; - while ( - XQueryTree(display, current, &_ignored, &_ignored, &children, &count) && - count != 0) { + while (XQueryTree(display, current, &_ignored, &_ignored, &children, + &count) == Success && + count != 0) { current = children[count - 1]; XFree(children); } @@ -1396,10 +1435,84 @@ inline Window last_descendant(Display *display, Window parent) { return current; } +std::vector query_x11_windows(Display *display) { + // _NET_CLIENT_LIST_STACKING + Window root = DefaultRootWindow(display); + + Atom clients_atom = XInternAtom(display, "_NET_CLIENT_LIST_STACKING", 0); + + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + unsigned char *data = nullptr; + + // try retrieving ordered windows first: + if (XGetWindowProperty(display, root, clients_atom, 0, 0, False, XA_WINDOW, + &actual_type, &actual_format, &nitems, &bytes_after, + &data) == Success) { + free(data); + size_t count = bytes_after / 4; + + if (XGetWindowProperty(display, root, clients_atom, 0, bytes_after / 4, + False, XA_WINDOW, &actual_type, &actual_format, + &nitems, &bytes_after, &data) == Success) { + Window *wdata = reinterpret_cast(data); + std::vector result(wdata, wdata + nitems); + free(data); + return result; + } + } + + clients_atom = XInternAtom(display, "_NET_CLIENT_LIST", 0); + if (XGetWindowProperty(display, root, clients_atom, 0, 0, False, XA_WINDOW, + &actual_type, &actual_format, &nitems, &bytes_after, + &data) == Success) { + free(data); + size_t count = bytes_after / 4; + + if (XGetWindowProperty(display, root, clients_atom, 0, count, False, + XA_WINDOW, &actual_type, &actual_format, &nitems, + &bytes_after, &data) == Success) { + Window *wdata = reinterpret_cast(data); + std::vector result(wdata, wdata + nitems); + free(data); + return result; + } + } + + // slowest method that also returns inaccurate results: + + // TODO: How do we remove window decorations and other unwanted WM/DE junk + // from this? + + std::vector result; + std::vector queue = {root}; + + Window _ignored, *children; + std::uint32_t count; + + while (!queue.empty()) { + Window current = queue.back(); + queue.pop_back(); + if (XQueryTree(display, current, &_ignored, &_ignored, &children, &count) == + Success && + count != 0) { + for (size_t i = 0; i < count; i++) { + queue.push_back(children[i]); + result.push_back(current); + } + XFree(children); + } + } + + return result; +} + Window query_x11_window_at_pos(Display *display, int x, int y) { Window root = DefaultRootWindow(display); - // these values are ignored but NULL can't be passed + // these values are ignored but NULL can't be passed to XQueryPointer. Window root_return; int root_x_return, root_y_return, win_x_return, win_y_return; unsigned int mask_return; @@ -1408,13 +1521,31 @@ Window query_x11_window_at_pos(Display *display, int x, int y) { XQueryPointer(display, window.root, &root_return, &last, &root_x_return, &root_y_return, &win_x_return, &win_y_return, &mask_return); - // If root, last descendant will be wrong - if (last == 0) return 0; - - // X11 correctly returns a window which covers conky area, but returned - // window is not window.window, but instead a parent node in some cases and - // the window.window we want to check for is a 1x1 child of that window. - return last_descendant(display, last); + if (last == 0) return root; + return last; } -#endif /* BUILD_MOUSE_EVENTS */ +std::vector query_x11_windows_at_pos( + Display *display, int x, int y, + std::function predicate) { + std::vector result; + + Window root = DefaultRootWindow(display); + XWindowAttributes attr; + + for (Window current : query_x11_windows(display)) { + int pos_x, pos_y; + Window _ignore; + // Doesn't account for decorations. There's no sane way to do that. + XTranslateCoordinates(display, current, root, 0, 0, &pos_x, &pos_y, + &_ignore); + XGetWindowAttributes(display, current, &attr); + + if (pos_x <= x && pos_y <= y && pos_x + attr.width >= x && + pos_y + attr.height >= y && predicate(attr)) { + result.push_back(current); + } + } + + return result; +} diff --git a/src/x11.h b/src/x11.h index c5bb6068..48ccad42 100644 --- a/src/x11.h +++ b/src/x11.h @@ -40,6 +40,8 @@ #endif #include +#include +#include #ifdef BUILD_ARGB /* true if use_argb_visual=true and argb visual was found*/ @@ -74,7 +76,12 @@ enum window_hints { extern Display *display; struct conky_x11_window { - Window root, window, desktop; + /// XID of x11 root window + Window root; + /// XID of Conky window + Window window; + /// XID of DE desktop window (or root if none) + Window desktop; Drawable drawable; Visual *visual; Colormap colourmap; @@ -152,9 +159,54 @@ union InputEvent { InputEvent *xev_as_input_event(XEvent &ev); void propagate_x11_event(XEvent &ev); -#ifdef BUILD_MOUSE_EVENTS +/// @brief Tries getting a list of windows ordered from bottom to top. +/// +/// Whether the list is correctly ordered depends on WM/DE providing the +/// `_NET_CLIENT_LIST_STACKING` atom. If only `_NET_CLIENT_LIST` is defined, +/// this function assumes the WM/DE is a tiling one without stacking order. +/// +/// If neither of the atoms are provided, this function tries traversing the +/// window graph in order to collect windows. In this case, map state of windows +/// is ignored. This also produces a lot of noise for some WM/DEs due to +/// inserted window decorations. +/// +/// @param display which display to query for windows @return a (likely) ordered +/// list of windows +std::vector query_x11_windows(Display *display); + +/// @brief Finds the last descendant of a window (leaf) on the graph. +/// +/// This function assumes the window stack below `parent` is linear. If it +/// isn't, it's only guaranteed that _some_ descendant of `parent` will be +/// returned. If provided `parent` has no descendants, the `parent` is returned. +/// +/// @param display display of parent +/// @return a descendant window +Window query_x11_last_descendant(Display *display, Window parent); + +/// @brief Returns the top-most window overlapping provided screen coordinates. +/// +/// @param display display of parent +/// @param x screen X position contained by window +/// @param y screen Y position contained by window +/// @return a top-most window at provided screen coordinates, or root Window query_x11_window_at_pos(Display *display, int x, int y); -#endif /* BUILD_MOUSE_EVENTS */ + +/// @brief Returns a list of windows overlapping provided screen coordinates. +/// +/// Vector returned by this function will never contain root because it's +/// assumed to always cover the entire display. +/// +/// @param display display of parent +/// @param x screen X position contained by window +/// @param y screen Y position contained by window +/// @param predicate any additional predicates to apply for XWindowAttributes +/// (besides bounds testing). +/// @return a vector of windows at provided screen coordinates +std::vector query_x11_windows_at_pos( + Display *display, int x, int y, + std::function predicate = + [](XWindowAttributes &a) { return true; }); #ifdef BUILD_XDBE void xdbe_swap_buffers(void);