From 897a5c2752d83dee7d877221fbdc973e38297765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Mon, 8 Apr 2024 22:22:29 +0200 Subject: [PATCH] Add most missing XInput event code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `BUILD_XINPUT` feature should use xinput instead of old X11 input events whenever possible. It's simpler to write now that it no longer makes main_loop_wait 100LoC larger. Also added proper valuator resolution by name, these values are mostly the same, but some devices might have unexpected indices. Ideally, names should also be configurable at runtime. Signed-off-by: Tin Å vagelj --- src/display-x11.cc | 158 +++++++++++++++++++++++++++++++++++++-------- src/x11.cc | 63 ++++++++++-------- src/x11.h | 4 ++ 3 files changed, 169 insertions(+), 56 deletions(-) diff --git a/src/display-x11.cc b/src/display-x11.cc index 78a9144e6..f2310a378 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -53,6 +53,7 @@ #include #include +#include #include #include #include @@ -428,7 +429,53 @@ bool display_output_x11::main_loop_wait(double t) { #ifdef OWN_WINDOW #ifdef BUILD_MOUSE_EVENTS #ifdef BUILD_XINPUT +typedef std::map XIDeviceInfoMap; +static XIDeviceInfoMap xi_device_info_cache{}; +XIDeviceInfo *xi_device_info(int device_id) { + if (xi_device_info_cache.count(device_id)) { + return xi_device_info_cache[device_id]; + } + + int num_devices; + XIDeviceInfo *info = XIQueryDevice(display, device_id, &num_devices); + if (num_devices == 0) { return nullptr; } + + xi_device_info_cache[device_id] = info; + return xi_device_info_cache[device_id]; +} + +int valuator_index(int device_id, char *name) { + char *name = name; + if (name == nullptr || strlen(name) == 0) { name = "None"; } + + XIDeviceInfo *device = xi_device_info(device_id); + + for (int i = 0; i < device->num_classes; i++) { + if (device->classes[i]->type != XIValuatorClass) continue; + + XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)device->classes[i]; + + auto label = x11_atom_string(display, window.root, class_info->label); + if (strcmp(label.c_str(), name)) return i; + } + + return -1; +} + EV_HANDLER(mouse_input) { + if (ev.type == ButtonPress || ev.type == ButtonRelease || + ev.type == MotionNotify) { + // We'll pass basic X11 events through, and instead deal with XInput events. + + // FIXME: This is wrong. lua hooks dictate whether events should be + // consumed. So each XInput event has a corresponding X11 event we need to + // also block based on the return value. + //*consumed = false; + // We could also fabricate a basic event from XInput one as XInput contains + // all the needed data... + return true; + } + if (ev.type != GenericEvent || ev.xcookie.extension != window.xi_opcode) return false; @@ -439,52 +486,107 @@ EV_HANDLER(mouse_input) { auto *data = reinterpret_cast(ev.xcookie.data); + if (data->evtype == XI_DeviceChanged) { + int device_id = data->sourceid; + + // update cached device info + if (xi_device_info_cache.count(device_id)) { + XIFreeDeviceInfo(xi_device_info_cache[device_id]); + int num_devices; + xi_device_info_cache[device_id] = + XIQueryDevice(display, device_id, &num_devices); + if (num_devices == 0) { xi_device_info_cache.erase(device_id); } + } + return true; + } + + // TODO: Make valuator_index names configurable? + int hor_move = valuator_index(data->deviceid, "Rel X"); // Almost always 0 + int vert_move = valuator_index(data->deviceid, "Rel Y"); // Almost always 1 + // the only way to differentiate between a scroll and move event is // though valuators - move has first 2 set, other axis movements have // other. + // FIXME: Use dynamic valuator indices bool is_cursor_move = data->valuators.mask_len >= 1 && (data->valuators.mask[0] & 3) == data->valuators.mask[0]; - for (std::size_t i = 1; i < data->valuators.mask_len; i++) { + for (std::size_t i = 0; i < data->valuators.mask_len; i++) { if (data->valuators.mask[i] != 0) { is_cursor_move = false; break; } } - if (data->evtype == XI_Motion && is_cursor_move) { - Window query_result = - query_x11_window_at_pos(display, data->root_x, data->root_y); + 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. - query_result = query_x11_last_descendant(display, query_result); - - static bool cursor_inside = false; - - // - over conky window - // - conky has now window, over desktop and within conky region - bool cursor_over_conky = - query_result == window.window && - (window.window != 0u || (data->root_x >= window.x && - data->root_x < (window.x + window.width) && - data->root_y >= window.y && - data->root_y < (window.y + window.height))); - if (cursor_over_conky) { - if (!cursor_inside) { + event_window = query_x11_last_descendant(display, event_window); + mods = x11_modifier_state(data->mods.effective); + } + + bool cursor_over_conky = + event_window == window.window && + (window.window != NULL || + (data->root_x >= window.x && data->root_x < (window.x + window.width) && + data->root_y >= window.y && data->root_y < (window.y + window.height))); + + if (data->evtype == XI_Motion) { + if (is_cursor_move) { + static bool cursor_inside = false; + + if (cursor_over_conky) { + if (!cursor_inside) { + *consumed = llua_mouse_hook(mouse_crossing_event( + mouse_event_t::AREA_ENTER, data->root_x - window.x, + data->root_y - window.x, data->root_x, data->root_y)); + } + cursor_inside = true; + } else if (cursor_inside) { *consumed = llua_mouse_hook(mouse_crossing_event( - mouse_event_t::AREA_ENTER, data->root_x - window.x, + mouse_event_t::AREA_LEAVE, data->root_x - window.x, data->root_y - window.x, data->root_x, data->root_y)); + cursor_inside = false; } - cursor_inside = true; - } else if (cursor_inside) { - *consumed = llua_mouse_hook(mouse_crossing_event( - mouse_event_t::AREA_LEAVE, data->root_x - window.x, - data->root_y - window.x, data->root_x, data->root_y)); - cursor_inside = false; + } else { + // up neg 2 + // down pos 2 + // left neg 3 + // right pos 3 + scroll_direction_t direction = + scroll_direction_t::SCROLL_DOWN; // FIXME: Detect from valuators + *consumed = llua_mouse_hook( + mouse_scroll_event(data->event_x, data->event_y, data->root_x, + data->root_y, direction, mods)); } + } else if (cursor_over_conky && (data->evtype == XI_ButtonPress || + data->evtype == XI_ButtonRelease)) { + if (data->detail >= 4 && data->detail <= 7) { + if (data->evtype == XI_ButtonRelease) return true; + + scroll_direction_t direction = x11_scroll_direction(data->detail); + *consumed = llua_mouse_hook( + mouse_scroll_event(data->event_x, data->event_y, data->root_x, + data->root_y, direction, mods)); + } + + mouse_event_t type = mouse_event_t::MOUSE_PRESS; + if (data->evtype == XI_ButtonRelease) { + type = mouse_event_t::MOUSE_RELEASE; + } + + mouse_button_t button = x11_mouse_button_code(data->detail); + *consumed = llua_mouse_hook(mouse_button_event(type, data->event_x, + data->event_y, data->root_x, + data->root_y, button, mods)); + + if (data->evtype == XI_ButtonPress) { *focus = *consumed; } } - // TODO: Handle other events - XFreeEventData(display, &ev.xcookie); + XFreeEventData(display, &ev.xcookie); // TODO: return true; } #else /* BUILD_XINPUT */ @@ -733,7 +835,7 @@ void process_surface_events(conky::display_output_x11 *surface, */ bool consumed = true; bool focus = false; - process_event(surface, display, ev, &consumed, &focus); + bool _handled = process_event(surface, display, ev, &consumed, &focus); if (!consumed) { propagate_x11_event(ev); diff --git a/src/x11.cc b/src/x11.cc index 9acdfe3c5..d7f9da8b3 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1413,12 +1413,6 @@ void propagate_x11_event(XEvent &ev) { XSendEvent(display, i_ev->common.window, False, ev_to_mask(i_ev->type), &ev); } -/// @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; @@ -1435,52 +1429,65 @@ 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); - +std::string x11_atom_string(Display *display, Window window, Atom atom) { 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, + if (XGetWindowProperty(display, window, atom, 0, 1024, False, AnyPropertyType, &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); + auto result = std::string(reinterpret_cast(data)); + XFree(data); + if (actual_type == XA_STRING && actual_format == 8) { return result; + } else { + return std::string(); } } +} - clients_atom = XInternAtom(display, "_NET_CLIENT_LIST", 0); - if (XGetWindowProperty(display, root, clients_atom, 0, 0, False, XA_WINDOW, +std::vector x11_atom_window_list(Display *display, Window window, + Atom atom) { + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + unsigned char *data = nullptr; + + if (XGetWindowProperty(display, window, atom, 0, 0, False, XA_WINDOW, &actual_type, &actual_format, &nitems, &bytes_after, &data) == Success) { - free(data); + XFree(data); size_t count = bytes_after / 4; - if (XGetWindowProperty(display, root, clients_atom, 0, count, False, + if (XGetWindowProperty(display, window, 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); + XFree(data); return result; } } + return std::vector{}; +} + +std::vector query_x11_windows(Display *display) { + Window root = DefaultRootWindow(display); + + Atom clients_atom = XInternAtom(display, "_NET_CLIENT_LIST_STACKING", 0); + std::vector result = + x11_atom_window_list(display, root, clients_atom); + if (result.empty()) { return result; } + + clients_atom = XInternAtom(display, "_NET_CLIENT_LIST", 0); + 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 diff --git a/src/x11.h b/src/x11.h index 48ccad425..fb14ec608 100644 --- a/src/x11.h +++ b/src/x11.h @@ -159,6 +159,10 @@ union InputEvent { InputEvent *xev_as_input_event(XEvent &ev); void propagate_x11_event(XEvent &ev); +std::string x11_atom_string(Display *display, Window window, Atom atom); +std::vector x11_atom_window_list(Display *display, Window window, + Atom atom); + /// @brief Tries getting a list of windows ordered from bottom to top. /// /// Whether the list is correctly ordered depends on WM/DE providing the