Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix event propagation on Openbox #1802

Merged
merged 5 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/display-x11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
195 changes: 163 additions & 32 deletions src/x11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1352,54 +1352,167 @@ 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;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bunch more, but we're not forwarding those events so I didn't implement all of them.

}

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);
}

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;
Expand All @@ -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;
}
58 changes: 55 additions & 3 deletions src/x11.h
Original file line number Diff line number Diff line change
Expand Up @@ -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*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down