1
0
mirror of https://github.com/Llewellynvdm/conky.git synced 2024-11-17 02:25:09 +00:00

Fix event propagation on Openbox

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 <tin.svagelj@live.com>
This commit is contained in:
Tin Švagelj 2024-04-09 12:28:26 +00:00 committed by GitHub
parent 0419e01fe4
commit 9424ab8ca5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 219 additions and 34 deletions

View File

@ -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;

View File

@ -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<Window> 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<Window> 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<Window *>(data);
std::vector<Window> 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<Window *>(data);
std::vector<Window> 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<Window> result;
std::vector<Window> 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<Window> query_x11_windows_at_pos(
Display *display, int x, int y,
std::function<bool(XWindowAttributes &)> predicate) {
std::vector<Window> 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;
}

View File

@ -40,6 +40,8 @@
#endif
#include <cstdint>
#include <functional>
#include <vector>
#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<Window> 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<Window> query_x11_windows_at_pos(
Display *display, int x, int y,
std::function<bool(XWindowAttributes &)> predicate =
[](XWindowAttributes &a) { return true; });
#ifdef BUILD_XDBE
void xdbe_swap_buffers(void);