diff --git a/src/display-x11.cc b/src/display-x11.cc index d4b0152ac..045566368 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -477,22 +477,22 @@ bool handle_event( } device_info = device_info::from_xi_id(data->deviceid, data->display); - Window event_window; - modifier_state_t mods; - if (data->evtype == XI_Motion || data->evtype == XI_ButtonPress || - data->evtype == XI_ButtonRelease) { - event_window = query_x11_window_at_pos(display, data->root_x, data->root_y); - // query_result is not window.window in some cases. - event_window = query_x11_last_descendant(display, event_window); - mods = x11_modifier_state(data->mods.effective); + if (!(data->evtype == XI_Motion || data->evtype == XI_ButtonPress || + data->evtype == XI_ButtonRelease)) { + return true; } - bool cursor_over_conky = - (event_window == window.window || - window.window == 0L && - (event_window == window.root || event_window == window.desktop)) && - (data->root_x >= window.x && data->root_x < (window.x + window.width) && - data->root_y >= window.y && data->root_y < (window.y + window.height)); + Window event_window = + query_x11_window_at_pos(display, data->root_x, data->root_y); + // query_result is not window.window in some cases. + modifier_state_t mods = x11_modifier_state(data->mods.effective); + + bool same_window = query_x11_top_level(display, event_window) == + query_x11_top_level(display, window.window); + bool cursor_over_conky = same_window && data->root_x >= window.x && + data->root_x < (window.x + window.width) && + data->root_y >= window.y && + data->root_y < (window.y + window.height); // XInput reports events twice on some hardware (even by 'xinput --test-xi2') auto hash = std::make_tuple(data->serial, data->evtype, data->event); diff --git a/src/mouse-events.cc b/src/mouse-events.cc index 864b1081a..1dd66e7ca 100644 --- a/src/mouse-events.cc +++ b/src/mouse-events.cc @@ -275,22 +275,25 @@ size_t fixed_valuator_index(Display *display, XIDeviceInfo *device, int format_return; unsigned long num_items; unsigned long bytes_after; - if (XIGetProperty(display, device->deviceid, override_atom, 0, 1, False, - XA_INTEGER, &type_return, &format_return, &num_items, - &bytes_after, - reinterpret_cast(&value)) == Success) { - if (type_return != XA_INTEGER || num_items > 1) { - NORM_ERR( - "invalid '%s' option value, expected a single integer; value will be " - "ignored", - atom_names[valuator]); + do { + if (XIGetProperty(display, device->deviceid, override_atom, 0, 1, False, + XA_INTEGER, &type_return, &format_return, &num_items, + &bytes_after, + reinterpret_cast(&value)) == Success) { + if (num_items == 0) break; + if (type_return != XA_INTEGER) { + NORM_ERR( + "invalid '%s' option value, expected a single integer; value will " + "be ignored", + atom_names[valuator]); + XFree(value); + break; + } + uint32_t result = *reinterpret_cast(value); XFree(value); - return valuator; + return static_cast(result); } - uint32_t result = *reinterpret_cast(value); - XFree(value); - return static_cast(result); - } + } while (true); return valuator; } @@ -300,46 +303,55 @@ bool fixed_valuator_relative(Display *display, XIDeviceInfo *device, valuator_t valuator, XIValuatorClassInfo *class_info) { const std::array atom_names = { - "ConkyValuatorMoveType", - "ConkyValuatorScrollType", + "ConkyValuatorMoveMode", + "ConkyValuatorScrollMode", }; - Atom override_atom = XInternAtom(display, atom_names[valuator >> 1], False); - unsigned char *value; + + Atom override_atom = XInternAtom(display, atom_names[valuator >> 1], True); + unsigned char *value_return; Atom type_return; int format_return; unsigned long num_items; unsigned long bytes_after; - if (XIGetProperty(display, device->deviceid, override_atom, 0, 9, False, - XA_STRING, &type_return, &format_return, &num_items, - &bytes_after, - reinterpret_cast(&value)) == Success) { - if (type_return != XA_STRING) { - NORM_ERR( - "invalid '%s' option value, expected a string; value will be " - "ignored", - atom_names[valuator >> 1]); - XFree(value); - return class_info->type == XIModeRelative; - } - // lowercase value - for (auto c = value; *c; ++c) *c = tolower(*c); - - bool relative = false; - if (strcmp(reinterpret_cast(value), "relative") == 0) { - relative = true; - } else if (strcmp(reinterpret_cast(value), "absolute") != 0) { - NORM_ERR( - "unknown '%s' option value: '%s', expected 'absolute' or 'relative'; " - "value will be ignored", - atom_names[static_cast(valuator) >> 1]); + do { + if (XIGetProperty( + display, device->deviceid, override_atom, 0, 9, False, XA_ATOM, + &type_return, &format_return, &num_items, &bytes_after, + reinterpret_cast(&value_return)) == Success) { + if (num_items == 0) break; + if (type_return != XA_ATOM) { + NORM_ERR( + "invalid '%s' option value, expected an atom (string); value will " + "be ignored", + atom_names[valuator >> 1]); + XFree(value_return); + break; + } + Atom return_atom = *reinterpret_cast(value_return); + XFree(value_return); + char *value = XGetAtomName(display, return_atom); + + // lowercase value + for (auto c = value; *c; ++c) *c = tolower(*c); + + bool relative = false; + if (strcmp(reinterpret_cast(value), "relative") == 0) { + relative = true; + } else if (strcmp(reinterpret_cast(value), "absolute") != 0) { + NORM_ERR( + "unknown '%s' option value: '%s', expected 'absolute' or " + "'relative'; " + "value will be ignored", + atom_names[valuator >> 1]); + XFree(value); + break; + } XFree(value); - return class_info->type == XIModeRelative; + return relative; } - XFree(value); - return relative; - } - return class_info->type == XIModeRelative; + } while (true); + return class_info->mode == XIModeRelative; } void device_info::init_xi_device( @@ -386,7 +398,6 @@ void device_info::init_xi_device( }; this->valuators[valuator] = info; - DBGP2("SToRING: %s %d", name.c_str(), info.index); } if (std::holds_alternative(source)) { @@ -440,9 +451,31 @@ xi_event_data *xi_event_data::read_cookie(Display *display, .valuators = valuators, .mods = source->mods, .group = source->group, + .valuators_relative = {0.0, 0.0, 0.0, 0.0}, }; XFreeEventData(display, cookie); + // Precompute relative values if they're absolute + auto device = device_info::from_xi_id(result->deviceid, result->display); + if (device == nullptr) return result; // shouldn't happen + for (size_t v = 0; v < valuator_t::VALUATOR_COUNT; v++) { + valuator_t valuator = static_cast(v); + auto valuator_info = device->valuator(valuator); + + if (result->valuators.count(valuator_info.index) == 0) { continue; } + auto current = result->valuators[valuator_info.index]; + + if (valuator_info.relative) { + result->valuators_relative[v] = current; + } else { + // XXX these doubles come from int values and might wrap around though + // it's hard to tell what int type is the source as it depends on the + // device/driver. + result->valuators_relative[v] = current - valuator_info.value; + } + valuator_info.value = current; + } + return result; } @@ -466,20 +499,7 @@ std::optional xi_event_data::valuator_value(valuator_t valuator) const { std::optional xi_event_data::valuator_relative_value( valuator_t valuator) const { - auto current = this->valuator_value(valuator); - if (!current.has_value()) return std::nullopt; - - auto valuator_info = this->valuator_info(valuator); - if (valuator_info == nullptr) return std::nullopt; - - if (valuator_info->relative) { - return current.value(); - } else { - // XXX these doubles come from int values and might wrap around though it's - // hard to tell what int type is the source as it depends on the - // device/driver. - return current.value() - valuator_info->value; - } + return this->valuators_relative.at(valuator); } std::vector> xi_event_data::generate_events( diff --git a/src/mouse-events.h b/src/mouse-events.h index 0af245c35..3c55358cc 100644 --- a/src/mouse-events.h +++ b/src/mouse-events.h @@ -241,7 +241,6 @@ struct mouse_crossing_event : public mouse_positioned_event { }; #ifdef BUILD_XINPUT - typedef int xi_device_id; typedef int xi_event_type; @@ -282,8 +281,6 @@ struct xi_event_data { Time time; xi_device_id deviceid; int sourceid; - /// Primary event detail. Meaning depends on `evtype` value: - /// XI_ButtonPress - Mouse button int detail; Window root; Window event; @@ -299,6 +296,11 @@ struct xi_event_data { XIModifierState mods; XIGroupState group; + // Extra data + + /// Precomputed relative values + std::array valuators_relative; + static xi_event_data *read_cookie(Display *display, XGenericEventCookie *cookie); diff --git a/src/x11.cc b/src/x11.cc index 68ac7ae3d..184a8b430 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -311,6 +311,52 @@ __attribute__((noreturn)) static int x11_ioerror_handler(Display *d) { CRIT_ERR("X IO Error: Display %lx\n", reinterpret_cast(d)); } +/// @brief Function to get virtual root windows of screen. +/// +/// Some WMs (swm, tvtwm, amiwm, enlightenment, etc.) use virtual roots to +/// manage workspaces. These are direct descendants of root and WMs reparent all +/// children to them. +/// +/// @param screen screen to get the (current) virtual root of @return the +/// virtual root window of the screen +static Window VRootWindowOfScreen(Screen *screen) { + Window root = screen->root; + Display *dpy = screen->display; + + Window rootReturn, parentReturn, *children; + unsigned int numChildren; + Atom actual_type; + int actual_format; + unsigned long nitems, bytesafter; + + /* go look for a virtual root */ + Atom __SWM_VROOT = ATOM(__SWM_VROOT); + if (XQueryTree(dpy, root, &rootReturn, &parentReturn, &children, + &numChildren)) { + for (int i = 0; i < numChildren; i++) { + Window *newRoot = None; + + if (XGetWindowProperty( + dpy, children[i], __SWM_VROOT, 0, 1, False, XA_WINDOW, + &actual_type, &actual_format, &nitems, &bytesafter, + reinterpret_cast(&newRoot)) == Success && + newRoot != None) { + root = *newRoot; + break; + } + } + if (children) XFree((char *)children); + } + + return root; +} +inline Window VRootWindow(Display *display, int screen) { + return VRootWindowOfScreen(ScreenOfDisplay(display, screen)); +} +inline Window DefaultVRootWindow(Display *display) { + return VRootWindowOfScreen(DefaultScreenOfDisplay(display)); +} + /* X11 initializer */ static void init_x11() { DBGP("enter init_x11()"); @@ -420,75 +466,21 @@ static void update_workarea() { * Return desktop window on success, * and set root and desktop byref return values. * Return 0 on failure. */ -static Window find_desktop_window(Window *p_root, Window *p_desktop) { - Atom type; - int format, i; - unsigned long nitems, bytes; - unsigned int n; - if (!display) return 0; - Window root = RootWindow(display, screen); - Window win; - Window troot, parent, *children; - unsigned char *buf = nullptr; - - if ((p_root == nullptr) || (p_desktop == nullptr)) { return 0; } - - /* some window managers set __SWM_VROOT to some child of root window */ - - XQueryTree(display, root, &troot, &parent, &children, &n); - for (i = 0; i < static_cast(n); i++) { - if (XGetWindowProperty(display, children[i], ATOM(__SWM_VROOT), 0, 1, False, - XA_WINDOW, &type, &format, &nitems, &bytes, - &buf) == Success && - type == XA_WINDOW) { - win = *reinterpret_cast(buf); - XFree(buf); - XFree(children); - fprintf(stderr, - PACKAGE_NAME - ": desktop window (%lx) found from __SWM_VROOT property\n", - win); - fflush(stderr); - *p_root = win; - *p_desktop = win; - return win; - } - - if (buf != nullptr) { - XFree(buf); - buf = nullptr; - } - } - XFree(children); +static Window find_desktop_window(Window root) { + Window desktop = root; /* get subwindows from root */ - win = find_subwindow(root, -1, -1); - + desktop = find_subwindow(root, -1, -1); update_workarea(); + desktop = find_subwindow(desktop, workarea[2], workarea[3]); - win = find_subwindow(win, workarea[2], workarea[3]); - - if (buf != nullptr) { - XFree(buf); - buf = nullptr; - } - - if (win != root) { - fprintf(stderr, - PACKAGE_NAME - ": desktop window (%lx) is subwindow of root window (%lx)\n", - win, root); + if (desktop != root) { + DBGP2("desktop window (0x%lx) is subwindow of root window (0x%lx)", desktop, + root); } else { - fprintf(stderr, PACKAGE_NAME ": desktop window (%lx) is root window\n", - win); + DBGP2("desktop window (0x%lx) is root window", desktop); } - - fflush(stderr); - - *p_root = root; - *p_desktop = win; - - return win; + return desktop; } #ifdef OWN_WINDOW @@ -583,18 +575,21 @@ void x11_init_window(lua::state &l, bool own) { // own is unused if OWN_WINDOW is not defined (void)own; + window.root = VRootWindow(display, screen); + if (window.root == None) { + DBGP2("no desktop window found"); + return; + } + window.desktop = find_desktop_window(window.root); + + window.visual = DefaultVisual(display, screen); + window.colourmap = DefaultColormap(display, screen); + #ifdef OWN_WINDOW if (own) { int depth = 0, flags = CWOverrideRedirect | CWBackingStore; Visual *visual = nullptr; - if (find_desktop_window(&window.root, &window.desktop) == 0U) { - DBGP2("no desktop window found"); - return; - } - - window.visual = DefaultVisual(display, screen); - window.colourmap = DefaultColormap(display, screen); depth = CopyFromParent; visual = CopyFromParent; #ifdef BUILD_ARGB @@ -913,16 +908,7 @@ void x11_init_window(lua::state &l, bool own) { { XWindowAttributes attrs; - if (window.window == 0u) { - window.window = find_desktop_window(&window.root, &window.desktop); - } - if (window.window == 0u) { - DBGP2("no root window found"); - return; - } - - window.visual = DefaultVisual(display, screen); - window.colourmap = DefaultColormap(display, screen); + if (window.window == None) { window.window = window.desktop; } if (XGetWindowAttributes(display, window.window, &attrs) != 0) { window.width = attrs.width; @@ -943,11 +929,6 @@ void x11_init_window(lua::state &l, bool own) { input_mask |= StructureNotifyMask | ButtonPressMask | ButtonReleaseMask; } #ifdef BUILD_MOUSE_EVENTS - /* it's not recommended to add event masks to special windows in X; causes a - * crash */ - if (own && own_window_type.get(l) != TYPE_DESKTOP) { - input_mask |= PointerMotionMask | ButtonPressMask | ButtonReleaseMask; - } bool xinput_ok = false; #ifdef BUILD_XINPUT // not a loop; substitutes goto with break - if checks fail @@ -997,9 +978,11 @@ void x11_init_window(lua::state &l, bool own) { xinput_ok = true; } while (false); #endif /* BUILD_XINPUT */ - // fallback to basic X11 enter/leave events if xinput fails to init + // Fallback to basic X11 enter/leave events if xinput fails to init. + // It's not recommended to add event masks to special windows in X; causes a + // crash (thus own_window_type != TYPE_DESKTOP) if (!xinput_ok && own && own_window_type.get(l) != TYPE_DESKTOP) { - input_mask |= EnterWindowMask | LeaveWindowMask; + input_mask |= PointerMotionMask | EnterWindowMask | LeaveWindowMask; } #endif /* BUILD_MOUSE_EVENTS */ #endif /* OWN_WINDOW */ @@ -1366,16 +1349,6 @@ void print_mouse_speed(struct text_object *obj, char *p, snprintf(p, p_max_size, "%d%%", (110 - threshold)); } -InputEvent *xev_as_input_event(XEvent &ev) { - if (ev.type == KeyPress || ev.type == KeyRelease || ev.type == ButtonPress || - ev.type == ButtonRelease || ev.type == MotionNotify || - ev.type == EnterNotify || ev.type == LeaveNotify) { - return reinterpret_cast(&ev); - } else { - return nullptr; - } -} - /// @brief Returns a mask for the event_type /// @param event_type Xlib event type /// @return Xlib event mask @@ -1465,60 +1438,70 @@ void propagate_x11_event(XEvent &ev, const void *cookie) { } #endif - InputEvent *i_ev = xev_as_input_event(ev); - if (i_ev == nullptr) { + if (!(ev.type == KeyPress || ev.type == KeyRelease || + ev.type == ButtonPress || ev.type == ButtonRelease || + ev.type == MotionNotify || ev.type == EnterNotify || + ev.type == LeaveNotify)) { // Not a known input event; blindly propagating them causes loops and all // sorts of other evil. return; } + // Note that using ev.xbutton is the same as using any of the above events. + // It's only important we don't access fields that are not common to all of + // them. - 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; + ev.xbutton.window = window.desktop; + ev.xbutton.x = ev.xbutton.x_root; + ev.xbutton.y = ev.xbutton.y_root; + ev.xbutton.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, + display, ev.xbutton.x_root, ev.xbutton.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(); + ev.xbutton.window = below.back(); Window _ignore; // Update event x and y coordinates to be target window relative - XTranslateCoordinates(display, window.desktop, i_ev->common.window, - i_ev->common.x_root, i_ev->common.y_root, - &i_ev->common.x, &i_ev->common.y, &_ignore); + XTranslateCoordinates(display, window.root, ev.xbutton.window, + ev.xbutton.x_root, ev.xbutton.y_root, &ev.xbutton.x, + &ev.xbutton.y, &_ignore); } // drop below vector } + int mask = + ev_to_mask(ev.type, ev.type == ButtonRelease ? ev.xbutton.button : 0); XUngrabPointer(display, CurrentTime); - XSendEvent(display, i_ev->common.window, True, - ev_to_mask(i_ev->type, - ev.type == ButtonRelease ? i_ev->xbutton.button : 0), - &ev); + XSendEvent(display, ev.xbutton.window, True, mask, &ev); if (focus) { - XSetInputFocus(display, i_ev->common.window, RevertToParent, CurrentTime); + XSetInputFocus(display, ev.xbutton.window, RevertToParent, CurrentTime); } } -Window query_x11_last_descendant(Display *display, Window parent) { - Window _ignored, *children; - std::uint32_t count; +Window query_x11_top_level(Display *display, Window child) { + if (child == None) return child; + Window root = DefaultVRootWindow(display); - Window current = parent; + Window ret_root, parent, *children; + std::uint32_t child_count; - while (XQueryTree(display, current, &_ignored, &_ignored, &children, - &count) == Success && - count != 0) { - current = children[count - 1]; - XFree(children); - } + Window current = child; + int i; + do { + if (XQueryTree(display, current, &ret_root, &parent, &children, + &child_count) == 0) { + break; + } + if (child_count != 0) XFree(children); + if (parent == root) break; + current = parent; + } while (true); return current; } @@ -1531,15 +1514,10 @@ std::vector x11_atom_window_list(Display *display, Window window, unsigned long bytes_after; unsigned char *data = nullptr; - if (XGetWindowProperty(display, window, atom, 0, 0, False, XA_WINDOW, + if (XGetWindowProperty(display, window, atom, 0, (~0L), False, XA_WINDOW, &actual_type, &actual_format, &nitems, &bytes_after, &data) == Success) { - XFree(data); - size_t count = bytes_after / 4; - - if (XGetWindowProperty(display, window, atom, 0, bytes_after / 4, False, - XA_WINDOW, &actual_type, &actual_format, &nitems, - &bytes_after, &data) == Success) { + if (actual_format == XA_WINDOW && nitems > 0) { Window *wdata = reinterpret_cast(data); std::vector result(wdata, wdata + nitems); XFree(data); @@ -1553,36 +1531,36 @@ std::vector x11_atom_window_list(Display *display, Window window, std::vector query_x11_windows(Display *display) { Window root = DefaultRootWindow(display); - Atom clients_atom = XInternAtom(display, "_NET_CLIENT_LIST_STACKING", 0); + Atom clients_atom = ATOM(_NET_CLIENT_LIST_STACKING); std::vector result = x11_atom_window_list(display, root, clients_atom); if (result.empty()) { return result; } - clients_atom = XInternAtom(display, "_NET_CLIENT_LIST", 0); + clients_atom = ATOM(_NET_CLIENT_LIST); result = x11_atom_window_list(display, root, clients_atom); if (result.empty()) { return result; } - // slowest method that also returns inaccurate results: - - // TODO: How do we remove window decorations and other unwanted WM/DE junk - // from this? + // slowest method - std::vector queue = {root}; + std::vector queue = {DefaultVRootWindow(display)}; Window _ignored, *children; std::uint32_t count; + const auto has_wm_hints = [&](Window window) { + auto hints = XGetWMHints(display, window); + bool result = hints != NULL; + if (result) XFree(hints); + return result; + }; + 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); + if (XQueryTree(display, current, &_ignored, &_ignored, &children, &count)) { + for (size_t i = 0; i < count; i++) queue.push_back(children[i]); + if (has_wm_hints(current)) result.push_back(current); + if (count > 0) XFree(children); } } @@ -1590,7 +1568,7 @@ std::vector query_x11_windows(Display *display) { } Window query_x11_window_at_pos(Display *display, int x, int y) { - Window root = DefaultRootWindow(display); + Window root = DefaultVRootWindow(display); // these values are ignored but NULL can't be passed to XQueryPointer. Window root_return; @@ -1610,7 +1588,7 @@ std::vector query_x11_windows_at_pos( std::function predicate) { std::vector result; - Window root = DefaultRootWindow(display); + Window root = DefaultVRootWindow(display); XWindowAttributes attr; for (Window current : query_x11_windows(display)) { diff --git a/src/x11.h b/src/x11.h index d9347d816..7afca0efe 100644 --- a/src/x11.h +++ b/src/x11.h @@ -24,6 +24,7 @@ #pragma once +#include "config.h" #include "setting.hh" #include @@ -41,7 +42,6 @@ #include #include -#include #include #ifdef BUILD_ARGB @@ -123,42 +123,14 @@ void set_struts(int); void x11_init_window(lua::state &l, bool own); void deinit_x11(); -// Fields common to all X11 input events -struct InputEventCommon { - int type; /* event type */ - uint64_t serial; /* # of last request processed by server */ - Bool send_event; /* true if this came from a SendEvent request */ - Display *display; /* Display the event was read from */ - Window window; /* "event" window reported relative to */ - Window root; /* root window that the event occurred on */ - Window subwindow; /* child window */ - Time time; /* milliseconds */ - int32_t x, y; /* pointer x, y coordinates in event window */ - int32_t x_root, y_root; /* coordinates relative to root */ - uint32_t state; /* key or button mask */ -}; - -union InputEvent { - int type; // event type - - InputEventCommon common; - - // Discrete interfaces - XAnyEvent xany; // common event interface - XKeyEvent xkey; // KeyPress & KeyRelease events - XButtonEvent xbutton; // ButtonPress & ButtonRelease events - XMotionEvent xmotion; // MotionNotify event - XCrossingEvent xcrossing; // EnterNotify & LeaveNotify events - - // Ensures InputEvent matches memory layout of XEvent. - // Accessing base variant is as code smell. - XEvent base; -}; - -// Returns InputEvent pointer to provided XEvent is an input event; nullptr -// otherwise. -InputEvent *xev_as_input_event(XEvent &ev); -void propagate_x11_event(XEvent &ev, const void *cookie); +/// @brief Forwards argument event to the top-most window at event positon that +/// isn't conky. +/// +/// Calling this function is time sensitive as it will query window at event +/// position **at invocation time**. +/// @param event event to forward +/// @param cookie optional cookie data +void propagate_x11_event(XEvent &event, const void *cookie = nullptr); /// @brief Returns a list of window values for the given atom. /// @param display display with which the atom is associated @@ -183,15 +155,15 @@ std::vector x11_atom_window_list(Display *display, Window window, /// list of windows std::vector query_x11_windows(Display *display); -/// @brief Finds the last descendant of a window (leaf) on the graph. +/// @brief Finds the last ascendant of a window (trunk) before root. /// -/// 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. +/// If provided `child` is root or has no windows between root and itself, the +/// `child` is returned. /// /// @param display display of parent -/// @return a descendant window -Window query_x11_last_descendant(Display *display, Window parent); +/// @param child window whose parents to query +/// @return the top level ascendant window +Window query_x11_top_level(Display *display, Window child); /// @brief Returns the top-most window overlapping provided screen coordinates. ///