From 0cdebd76f628d713157220b1810aab2ce7eccb3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Sat, 6 Apr 2024 17:20:47 +0200 Subject: [PATCH 1/5] Fix propagation not working 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. Signed-off-by: Tin Švagelj --- src/display-x11.cc | 2 + src/x11.cc | 120 ++++++++++++++++++++++++++++++++++----------- src/x11.h | 43 ++++++++++++++-- 3 files changed, 134 insertions(+), 31 deletions(-) diff --git a/src/display-x11.cc b/src/display-x11.cc index 3d60e19b0..4cff779db 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 f76c3ee79..dc07e089d 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1352,35 +1352,74 @@ 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; + { + 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; }); + std::remove_if(below.begin(), below.end(), + [](Window w) { return w == window.window; }); + if (!below.empty()) { i_ev->common.window = below.back(); } + // drop below vector } + + /* forward the event to the window below conky (e.g. caja) or desktop */ + i_ev->common.x = i_ev->common.x_root; + i_ev->common.y = i_ev->common.y_root; + + XUngrabPointer(display, i_ev->common.time); + + // 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, i_ev->common.window, RevertToPointerRoot, time); + // } + + 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; @@ -1399,7 +1438,7 @@ inline Window last_descendant(Display *display, Window parent) { 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; @@ -1409,12 +1448,37 @@ Window query_x11_window_at_pos(Display *display, int x, int y) { &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) { + Window _ignored, *children; + std::uint32_t count; + + std::vector result; + std::vector queue = {DefaultRootWindow(display)}; + + while (!queue.empty()) { + Window current = queue.back(); + queue.pop_back(); + if (XQueryTree(display, current, &_ignored, &_ignored, &children, &count) && + count != 0) { + for (size_t i = 0; i < count; i++) { + queue.push_back(children[i]); + + XWindowAttributes attr; + XGetWindowAttributes(display, current, &attr); + if (attr.x <= x && attr.y <= y && attr.x + attr.width >= x && + attr.y + attr.height >= y && predicate(attr)) { + result.push_back(current); + } + } + XFree(children); + } + } + + return result; +} diff --git a/src/x11.h b/src/x11.h index c5bb60680..3b8d118ef 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,39 @@ union InputEvent { InputEvent *xev_as_input_event(XEvent &ev); void propagate_x11_event(XEvent &ev); -#ifdef BUILD_MOUSE_EVENTS +/// @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); From 942e8a325df4617902d1b31d1e7b963c31856623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Sat, 6 Apr 2024 22:01:48 +0200 Subject: [PATCH 2/5] Use atoms for querrying windows by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Atoms should be faster than graph traversal, and also don't include decorations (windows) inserted by WM/DEs, nor special 1x1 windows. Signed-off-by: Tin Švagelj --- src/x11.cc | 120 +++++++++++++++++++++++++++++++++++++++++------------ src/x11.h | 15 +++++++ 2 files changed, 109 insertions(+), 26 deletions(-) diff --git a/src/x11.cc b/src/x11.cc index dc07e089d..1bb982462 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1389,8 +1389,9 @@ void propagate_x11_event(XEvent &ev) { 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; }); - std::remove_if(below.begin(), below.end(), - [](Window w) { return w == window.window; }); + 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(); } // drop below vector } @@ -1425,9 +1426,9 @@ Window query_x11_last_descendant(Display *display, Window parent) { 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); } @@ -1435,6 +1436,80 @@ Window query_x11_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); @@ -1447,7 +1522,6 @@ 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 root; return last; } @@ -1455,28 +1529,22 @@ Window query_x11_window_at_pos(Display *display, int x, int y) { std::vector query_x11_windows_at_pos( Display *display, int x, int y, std::function predicate) { - Window _ignored, *children; - std::uint32_t count; - std::vector result; - std::vector queue = {DefaultRootWindow(display)}; - - while (!queue.empty()) { - Window current = queue.back(); - queue.pop_back(); - if (XQueryTree(display, current, &_ignored, &_ignored, &children, &count) && - count != 0) { - for (size_t i = 0; i < count; i++) { - queue.push_back(children[i]); - XWindowAttributes attr; - XGetWindowAttributes(display, current, &attr); - if (attr.x <= x && attr.y <= y && attr.x + attr.width >= x && - attr.y + attr.height >= y && predicate(attr)) { - result.push_back(current); - } - } - XFree(children); + 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); } } diff --git a/src/x11.h b/src/x11.h index 3b8d118ef..48ccad425 100644 --- a/src/x11.h +++ b/src/x11.h @@ -159,6 +159,21 @@ union InputEvent { InputEvent *xev_as_input_event(XEvent &ev); void propagate_x11_event(XEvent &ev); +/// @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 From 13bdaa63871924da48d7455a4c3703f91248f3ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Mon, 8 Apr 2024 12:38:54 +0200 Subject: [PATCH 3/5] Set event time to CurrentTime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tin Švagelj --- src/x11.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/x11.cc b/src/x11.cc index 1bb982462..c37290fd7 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1399,6 +1399,7 @@ void propagate_x11_event(XEvent &ev) { /* forward the event to the window below conky (e.g. caja) or desktop */ i_ev->common.x = i_ev->common.x_root; i_ev->common.y = i_ev->common.y_root; + i_ev->common.time = CurrentTime; XUngrabPointer(display, i_ev->common.time); @@ -1406,9 +1407,8 @@ void propagate_x11_event(XEvent &ev) { // 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, i_ev->common.window, RevertToPointerRoot, time); + // XSetInputFocus(display, i_ev->common.window, RevertToPointerRoot, + // i_ev->common.time); // } XSendEvent(display, i_ev->common.window, False, ev_to_mask(i_ev->type), &ev); From 94f04b20c3c0ec9226aef082d9be23a86f4d9ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Mon, 8 Apr 2024 18:44:21 +0200 Subject: [PATCH 4/5] Don't change input focus on propagation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Causes input focus flickering, especially with WMs where focus follows pointer. This looks weird (carret flashing) and effectively acheives nothing. So I'm, leaving it up to WMs to manage focus. Signed-off-by: Tin Švagelj --- src/x11.cc | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/x11.cc b/src/x11.cc index c37290fd7..a079b08d7 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1401,16 +1401,7 @@ void propagate_x11_event(XEvent &ev) { i_ev->common.y = i_ev->common.y_root; i_ev->common.time = CurrentTime; - XUngrabPointer(display, i_ev->common.time); - - // int _revert_to; - // Window focused; - // XGetInputFocus(display, &focused, &_revert_to); - // if (focused == window.window) { - // XSetInputFocus(display, i_ev->common.window, RevertToPointerRoot, - // i_ev->common.time); - // } - + XUngrabPointer(display, CurrentTime); XSendEvent(display, i_ev->common.window, False, ev_to_mask(i_ev->type), &ev); } From 2bfe8bbe4b619c6b57da38f7dc0293263c4439de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Mon, 8 Apr 2024 18:55:15 +0200 Subject: [PATCH 5/5] Update event coordinates in offset windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tin Švagelj --- src/x11.cc | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/x11.cc b/src/x11.cc index a079b08d7..9acdfe3c5 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1385,6 +1385,11 @@ void propagate_x11_event(XEvent &ev) { } 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, @@ -1392,15 +1397,18 @@ void propagate_x11_event(XEvent &ev) { 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(); } + 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 } - /* forward the event to the window below conky (e.g. caja) or desktop */ - i_ev->common.x = i_ev->common.x_root; - i_ev->common.y = i_ev->common.y_root; - i_ev->common.time = CurrentTime; - XUngrabPointer(display, CurrentTime); XSendEvent(display, i_ev->common.window, False, ev_to_mask(i_ev->type), &ev); }