From 03e0f0715195400e50885696b0fdd758214171bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Sat, 13 Apr 2024 01:59:28 +0200 Subject: [PATCH 1/8] Add XInput event handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tin Švagelj --- src/display-x11.cc | 199 ++++++++++++++++++++++++++---------- src/mouse-events.cc | 244 ++++++++++++++++++++++++++++++++++++++++++++ src/mouse-events.h | 79 ++++++++++++++ src/x11.cc | 158 ++++++++++++++++++++-------- src/x11.h | 9 ++ 5 files changed, 594 insertions(+), 95 deletions(-) diff --git a/src/display-x11.cc b/src/display-x11.cc index d5111d32d..04714a7ad 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -53,7 +53,9 @@ #include #include +#include #include +#include #include #include @@ -442,72 +444,163 @@ bool handle_event(conky::display_output_x11 *surface, Display *display, } #ifdef OWN_WINDOW -#ifdef BUILD_XINPUT template <> -bool handle_event( +bool handle_event( conky::display_output_x11 *surface, Display *display, XEvent &ev, bool *consumed, void **cookie) { - if (ev.type != GenericEvent || ev.xcookie.extension != window.xi_opcode) +#ifdef BUILD_XINPUT + if (ev.type == ButtonPress || ev.type == ButtonRelease || + ev.type == MotionNotify) { + // destroy basic X11 events; and manufacture them later when trying to + // propagate XInput ones - this is required because there's no (simple) way + // of making sure the lua hook controls both when it only handles XInput + // ones. + *consumed = true; + return true; + } + + if (ev.type != GenericEvent || ev.xgeneric.extension != window.xi_opcode) return false; - if (!XGetEventData(display, &ev.xcookie)) { + auto *data = xi_event_data::read_cookie(display, &ev.xcookie); + if (data == nullptr) { NORM_ERR("unable to get XInput event data"); return false; } + *cookie = data; - auto *data = reinterpret_cast(ev.xcookie.data); - - // the only way to differentiate between a scroll and move event is - // though valuators - move has first 2 set, other axis movements have - // other. - 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++) { - if (data->valuators.mask[i] != 0) { - is_cursor_move = false; - break; + if (data->evtype == XI_DeviceChanged) { + int device_id = data->sourceid; + + // update cached device info + if (xi_device_info_cache.count(device_id)) { + xi_device_info_cache.erase(device_id); + conky_device_info::from_xi_id(display, device_id); } + return true; } - 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) { - llua_mouse_hook(mouse_crossing_event( - mouse_event_t::AREA_ENTER, data->root_x - window.x, + 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 == 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)); + + // XInput reports events twice on some hardware (even by 'xinput --test-xi2') + auto hash = std::make_tuple(data->serial, data->evtype, data->event); + typedef std::map MouseEventDebounceMap; + static MouseEventDebounceMap debounce{}; + + Time now = data->time; + bool already_handled = debounce.count(hash) > 0; + debounce[hash] = now; + + // clear stale entries + for (auto iter = debounce.begin(); iter != debounce.end();) { + if (data->time - iter->second > 1000) { + iter = debounce.erase(iter); + } else { + ++iter; + } + } + + if (already_handled) { + *consumed = true; + return true; + } + + if (data->evtype == XI_Motion) { + auto device_info = conky_device_info::from_xi_id(display, data->deviceid); + // TODO: Make valuator_index names configurable? + + // Note that these are absolute (not relative) values in some cases + int hor_move_v = device_info->valuators["Rel X"].index; // Almost always 0 + int vert_move_v = device_info->valuators["Rel Y"].index; // Almost always 1 + int hor_scroll_v = + device_info->valuators["Rel Horiz Scroll"].index; // Almost always 2 + int vert_scroll_v = + device_info->valuators["Rel Vert Scroll"].index; // Almost always 3 + + bool is_move = + data->test_valuator(hor_move_v) || data->test_valuator(vert_move_v); + bool is_scroll = + data->test_valuator(hor_scroll_v) || data->test_valuator(vert_scroll_v); + + if (is_move) { + static bool cursor_inside = false; + + // generate crossing events + 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_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) { - 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; + + // generate movement events + if (cursor_over_conky) { + *consumed = llua_mouse_hook(mouse_move_event( + data->event_x, data->event_y, data->root_x, data->root_y, mods)); + } + } + if (is_scroll && cursor_over_conky) { + // FIXME: Turn into relative values so direction works + auto horizontal = data->valuator_value(hor_scroll_v); + if (horizontal.value_or(0.0) != 0.0) { + scroll_direction_t direction = horizontal.value() > 0.0 + ? scroll_direction_t::SCROLL_LEFT + : scroll_direction_t::SCROLL_RIGHT; + *consumed = llua_mouse_hook( + mouse_scroll_event(data->event_x, data->event_y, data->root_x, + data->root_y, direction, mods)); + } + auto vertical = data->valuator_value(vert_scroll_v); + if (vertical.value_or(0.0) != 0.0) { + scroll_direction_t direction = vertical.value() > 0.0 + ? scroll_direction_t::SCROLL_DOWN + : scroll_direction_t::SCROLL_UP; + *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) { + // Handled via motion event valuators, ignoring "backward compatibility" + // ones. + return true; } + + 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)); } - XFreeEventData(display, &ev.xcookie); - return true; -} -#endif /* BUILD_XINPUT */ -template <> -bool handle_event( - conky::display_output_x11 *surface, Display *display, XEvent &ev, - bool *consumed, void **cookie) { +#else /* BUILD_XINPUT */ if (ev.type != ButtonPress && ev.type != ButtonRelease && ev.type != MotionNotify) return false; @@ -551,7 +644,7 @@ bool handle_event( // always propagate mouse input if not handling mouse events *consumed = false; #endif /* BUILD_MOUSE_EVENTS */ - +#endif /* BUILD_XINPUT */ if (!own_window.get(*state)) return true; switch (own_window_type.get(*state)) { case window_type::TYPE_NORMAL: @@ -751,9 +844,9 @@ void process_surface_events(conky::display_output_x11 *surface, XNextEvent(display, &ev); /* - indicates whether processed event was consumed; true by default so we don't - propagate handled events unless they explicitly state they haven't been - consumed. + indicates whether processed event was consumed; true by default so we + don't propagate handled events unless they explicitly state they haven't + been consumed. */ bool consumed = true; void *cookie = nullptr; diff --git a/src/mouse-events.cc b/src/mouse-events.cc index c7cfccc89..7ebb43eb1 100644 --- a/src/mouse-events.cc +++ b/src/mouse-events.cc @@ -27,8 +27,19 @@ #include "logging.h" +#ifdef BUILD_XINPUT +#include +#endif + extern "C" { #include + +#ifdef BUILD_XINPUT +#include +#endif + +#include +#include } namespace conky { @@ -206,4 +217,237 @@ void mouse_button_event::push_lua_data(lua_State *L) const { push_mods(L, this->mods); } +#ifdef BUILD_XINPUT +XIDeviceInfoMap xi_device_info_cache{}; + +conky_device_info *conky_device_info::from_xi_id(Display *display, + int device_id) { + if (xi_device_info_cache.count(device_id)) { + return &xi_device_info_cache[device_id]; + } + + int num_devices; + XIDeviceInfo *device = XIQueryDevice(display, device_id, &num_devices); + if (num_devices == 0) { return nullptr; } + + std::map valuators; + for (int i = 0; i < device->num_classes; i++) { + if (device->classes[i]->type != XIValuatorClass) continue; + + XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)device->classes[i]; + char *label = XGetAtomName(display, class_info->label); + if (label == nullptr) { + XFree(label); + continue; + } + + valuators[std::string(label)] = + xi_valuator_info{.index = static_cast(class_info->number), + .min = class_info->min, + .max = class_info->max}; + DBGP2("%s - %f %f %f", label, class_info->value, class_info->min, + class_info->max); + XFree(label); + } + + xi_device_info_cache[device_id] = + conky_device_info{.device_id = device_id, + .name = std::string(device->name), + .valuators = valuators}; + XIFreeDeviceInfo(device); + + return &xi_device_info_cache[device_id]; +} + +bool xi_event_data::test_valuator(size_t index) const { + return this->valuators.count(index) > 0; +} + +std::optional xi_event_data::valuator_value(size_t index) const { + if (this->valuators.count(index) == 0) return std::nullopt; + return std::optional(this->valuators.at(index)); +} + +xi_event_data *xi_event_data::read_cookie(Display *display, + XGenericEventCookie *cookie) { + if (!XGetEventData(display, cookie)) { + // already consumed + return nullptr; + } + auto *source = reinterpret_cast(cookie->data); + + uint32_t buttons = 0; + for (size_t bi = 1; bi <= source->buttons.mask_len; bi++) { + buttons |= source->buttons.mask[bi] << (source->buttons.mask_len - bi) * 8; + } + + std::map valuators{}; + size_t valuator_index = 0; + for (size_t vi = 0; vi < source->valuators.mask_len * 8; vi++) { + if (XIMaskIsSet(source->valuators.mask, vi)) { + valuators[vi] = source->valuators.values[valuator_index++]; + } + } + + auto result = new xi_event_data{ + .evtype = static_cast(source->evtype), + .serial = source->serial, + .send_event = source->send_event, + .display = source->display, + .extension = source->extension, + .time = source->time, + .deviceid = source->deviceid, + .sourceid = source->sourceid, + .detail = source->detail, + .root = source->root, + .event = source->event, + .child = source->child, + .root_x = source->root_x, + .root_y = source->root_y, + .event_x = source->event_x, + .event_y = source->event_y, + .flags = source->flags, + .buttons = std::bitset<32>(buttons), + .valuators = valuators, + .mods = source->mods, + .group = source->group, + }; + XFreeEventData(display, cookie); + + return result; +} + +std::vector> xi_event_data::generate_events( + Window target, Window child, double target_x, double target_y) const { + std::vector> result{}; + + if (this->evtype == XI_Motion) { + auto device_info = conky_device_info::from_xi_id(display, this->deviceid); + + // Note that these are absolute (not relative) values in some cases + int hor_move_v = device_info->valuators["Rel X"].index; // Almost always 0 + int vert_move_v = device_info->valuators["Rel Y"].index; // Almost always 1 + int hor_scroll_v = + device_info->valuators["Rel Horiz Scroll"].index; // Almost always 2 + int vert_scroll_v = + device_info->valuators["Rel Vert Scroll"].index; // Almost always 3 + + bool is_move = + this->test_valuator(hor_move_v) || this->test_valuator(vert_move_v); + bool is_scroll = + this->test_valuator(hor_scroll_v) || this->test_valuator(vert_scroll_v); + + if (is_move) { + XEvent *produced = new XEvent; + std::memset(produced, 0, sizeof(XEvent)); + + XMotionEvent *e = &produced->xmotion; + e->type = MotionNotify; + e->display = this->display; + e->root = this->root; + e->window = target; + e->subwindow = child; + e->time = CurrentTime; + e->x = static_cast(target_x); + e->y = static_cast(target_y); + e->x_root = static_cast(this->root_x); + e->y_root = static_cast(this->root_y); + e->state = this->mods.effective; + e->is_hint = NotifyNormal; + e->same_screen = True; + result.emplace_back(std::make_tuple(PointerMotionMask, produced)); + } + if (is_scroll) { + XEvent *produced = new XEvent; + std::memset(produced, 0, sizeof(XEvent)); + + uint scroll_direction = 4; + auto vertical = this->valuator_value(vert_scroll_v); + + // FIXME: Turn into relative values so direction works + if (vertical.value_or(0.0) != 0.0) { + scroll_direction = vertical.value() < 0.0 ? Button4 : Button5; + } else { + auto horizontal = this->valuator_value(hor_scroll_v); + if (horizontal.value_or(0.0) != 0.0) { + scroll_direction = horizontal.value() < 0.0 ? 6 : 7; + } + } + + XButtonEvent *e = &produced->xbutton; + e->display = display; + e->root = this->root; + e->window = target; + e->subwindow = child; + e->time = CurrentTime; + e->x = static_cast(target_x); + e->y = static_cast(target_y); + e->x_root = static_cast(this->root_x); + e->y_root = static_cast(this->root_y); + e->state = this->mods.effective; + e->button = scroll_direction; + e->same_screen = True; + + XEvent *press = new XEvent; + e->type = ButtonPress; + std::memcpy(press, produced, sizeof(XEvent)); + result.emplace_back(std::make_tuple(ButtonPressMask, press)); + + e->type = ButtonRelease; + result.emplace_back(std::make_tuple(ButtonReleaseMask, produced)); + } + } else { + XEvent *produced = new XEvent; + std::memset(produced, 0, sizeof(XEvent)); + + XButtonEvent *e = &produced->xbutton; + e->display = display; + e->root = this->root; + e->window = target; + e->subwindow = child; + e->time = CurrentTime; + e->x = static_cast(target_x); + e->y = static_cast(target_y); + e->x_root = static_cast(this->root_x); + e->y_root = static_cast(this->root_y); + e->state = this->mods.effective; + e->button = this->detail; + e->same_screen = True; + + long event_mask = NoEventMask; + switch (this->evtype) { + case XI_ButtonPress: + e->type = ButtonPress; + event_mask = ButtonPressMask; + break; + case XI_ButtonRelease: + e->type = ButtonRelease; + event_mask = ButtonReleaseMask; + switch (this->detail) { + case 1: + event_mask |= Button1MotionMask; + break; + case 2: + event_mask |= Button2MotionMask; + break; + case 3: + event_mask |= Button3MotionMask; + break; + case 4: + event_mask |= Button4MotionMask; + break; + case 5: + event_mask |= Button5MotionMask; + break; + } + break; + } + + result.emplace_back(std::make_tuple(event_mask, produced)); + } + + return result; +} +#endif /* BUILD_XINPUT */ + } // namespace conky \ No newline at end of file diff --git a/src/mouse-events.h b/src/mouse-events.h index ac95d26aa..0bb2f7dd6 100644 --- a/src/mouse-events.h +++ b/src/mouse-events.h @@ -28,9 +28,20 @@ #include "config.h" #include "logging.h" +#ifdef BUILD_XINPUT +#include +#include +#include +#include +#endif /* BUILD_XINPUT */ + extern "C" { #ifdef BUILD_X11 #include + +#ifdef BUILD_XINPUT +#include +#endif /* BUILD_XINPUT */ #endif /* BUILD_X11 */ #include @@ -227,6 +238,74 @@ struct mouse_crossing_event : public mouse_positioned_event { : mouse_positioned_event{type, x, y, x_abs, y_abs} {}; }; +#ifdef BUILD_XINPUT + +typedef int xi_device_id; +typedef int xi_event_type; + +struct xi_valuator_info { + size_t index; + double min; + double max; +}; + +struct conky_device_info { + xi_device_id device_id; + std::string name; + std::map valuators; + + static conky_device_info *from_xi_id(Display *display, xi_device_id id); +}; + +typedef std::map XIDeviceInfoMap; +extern XIDeviceInfoMap xi_device_info_cache; + +int xi_valuator_index(Display *display, xi_device_id device_id, + const char *valuator); + +/// Almost an exact copy of `XIDeviceEvent`, except it owns all data. +struct xi_event_data { + xi_event_type evtype; + unsigned long serial; + Bool send_event; + Display *display; + /// XI extension offset + // TODO: Check whether this is consistent between different clients by + // printing. + int extension; + 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; + Window child; + double root_x; + double root_y; + double event_x; + double event_y; + int flags; + /// pressed button mask + std::bitset<32> buttons; + std::map valuators; + XIModifierState mods; + XIGroupState group; + + static xi_event_data *read_cookie(Display *display, + XGenericEventCookie *cookie); + + bool test_valuator(size_t index) const; + std::optional valuator_value(size_t index) const; + + std::vector> generate_events(Window target, + Window child, + double target_x, + double target_y) const; +}; + +#endif /* BUILD_XINPUT */ } // namespace conky #endif /* MOUSE_EVENTS_H */ diff --git a/src/x11.cc b/src/x11.cc index ff37cb6f0..987bbe8b5 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -37,6 +37,10 @@ #include "gui.h" #include "logging.h" +#ifdef BUILD_XINPUT +#include "mouse-events.h" +#endif + #include #include #include @@ -946,7 +950,8 @@ void x11_init_window(lua::state &l, bool own) { } bool xinput_ok = false; #ifdef BUILD_XINPUT - do { // not loop + // not a loop; substitutes goto with break - if checks fail + do { int _ignored; // segfault if NULL if (!XQueryExtension(display, "XInputExtension", &window.xi_opcode, &_ignored, &_ignored)) { @@ -955,8 +960,8 @@ void x11_init_window(lua::state &l, bool own) { break; } - int32_t major = 2, minor = 0; - uint32_t retval = XIQueryVersion(display, &major, &minor); + int major = 2, minor = 0; + int retval = XIQueryVersion(display, &major, &minor); if (retval != Success) { NORM_ERR("Error: XInput 2.0 is not supported!"); break; @@ -965,15 +970,33 @@ void x11_init_window(lua::state &l, bool own) { const std::size_t mask_size = (XI_LASTEVENT + 7) / 8; unsigned char mask_bytes[mask_size] = {0}; /* must be zeroed! */ XISetMask(mask_bytes, XI_Motion); + // Capture click events for "override" window type + if (!own) { + XISetMask(mask_bytes, XI_ButtonPress); + XISetMask(mask_bytes, XI_ButtonRelease); + } XIEventMask ev_masks[1]; ev_masks[0].deviceid = XIAllDevices; ev_masks[0].mask_len = sizeof(mask_bytes); ev_masks[0].mask = mask_bytes; XISelectEvents(display, window.root, ev_masks, 1); + + if (own) { + XIClearMask(mask_bytes, XI_Motion); + XISetMask(mask_bytes, XI_ButtonPress); + XISetMask(mask_bytes, XI_ButtonRelease); + + ev_masks[0].deviceid = XIAllDevices; + ev_masks[0].mask_len = sizeof(mask_bytes); + ev_masks[0].mask = mask_bytes; + XISelectEvents(display, window.window, ev_masks, 1); + } + xinput_ok = true; } while (false); #endif /* BUILD_XINPUT */ + // fallback to basic X11 enter/leave events if xinput fails to init if (!xinput_ok && own && own_window_type.get(l) != TYPE_DESKTOP) { input_mask |= EnterWindowMask | LeaveWindowMask; } @@ -1355,7 +1378,7 @@ 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) { +int ev_to_mask(int event_type, int button) { switch (event_type) { case KeyPress: return KeyPressMask; @@ -1364,7 +1387,20 @@ int ev_to_mask(int event_type) { case ButtonPress: return ButtonPressMask; case ButtonRelease: - return ButtonReleaseMask; + switch (button) { + case 1: + return ButtonReleaseMask | Button1MotionMask; + case 2: + return ButtonReleaseMask | Button2MotionMask; + case 3: + return ButtonReleaseMask | Button3MotionMask; + case 4: + return ButtonReleaseMask | Button4MotionMask; + case 5: + return ButtonReleaseMask | Button5MotionMask; + default: + return ButtonReleaseMask; + } case EnterNotify: return EnterWindowMask; case LeaveNotify: @@ -1376,9 +1412,58 @@ int ev_to_mask(int event_type) { } } +#ifdef BUILD_XINPUT +void propagate_xinput_event(const conky::xi_event_data *ev) { + if (ev->evtype != XI_Motion && ev->evtype != XI_ButtonPress && + ev->evtype != XI_ButtonRelease) { + return; + } + + Window target = window.root; + Window child = None; + int target_x = ev->event_x; + int target_y = ev->event_y; + { + std::vector below = query_x11_windows_at_pos( + display, ev->root_x, ev->root_y, + [](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()) { + target = below.back(); + + // Update event x and y coordinates to be target window relative + XTranslateCoordinates(display, window.root, ev->event, ev->root_x, + ev->root_y, &target_x, &target_y, &child); + } + } + + auto events = ev->generate_events(target, child, target_x, target_y); + + XUngrabPointer(display, CurrentTime); + for (auto it : events) { + auto ev = std::get<1>(it); + XSendEvent(display, target, True, std::get<0>(it), ev); + free(ev); + } + + XFlush(display); +} +#endif + void propagate_x11_event(XEvent &ev, const void *cookie) { bool focus = ev.type == ButtonPress; + // cookie must be allocated before propagation, and freed after +#ifdef BUILD_XINPUT + if (ev.type == GenericEvent && ev.xgeneric.extension == window.xi_opcode) { + if (cookie == nullptr) { return; } + return propagate_xinput_event( + reinterpret_cast(cookie)); + } +#endif + InputEvent *i_ev = xev_as_input_event(ev); if (i_ev == nullptr) { // Not a known input event; blindly propagating them causes loops and all @@ -1404,7 +1489,7 @@ void propagate_x11_event(XEvent &ev, const void *cookie) { Window _ignore; // Update event x and y coordinates to be target window relative - XTranslateCoordinates(display, window.root, i_ev->common.window, + 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); } @@ -1412,18 +1497,15 @@ void propagate_x11_event(XEvent &ev, const void *cookie) { } XUngrabPointer(display, CurrentTime); - XSendEvent(display, i_ev->common.window, True, ev_to_mask(i_ev->type), &ev); + XSendEvent(display, i_ev->common.window, True, + ev_to_mask(i_ev->type, + ev.type == ButtonRelease ? i_ev->xbutton.button : 0), + &ev); if (focus) { XSetInputFocus(display, i_ev->common.window, RevertToParent, CurrentTime); } } -/// @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; @@ -1440,58 +1522,50 @@ 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::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; - // 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, + 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 // from this? - std::vector result; std::vector queue = {root}; Window _ignored, *children; diff --git a/src/x11.h b/src/x11.h index 62314e3a6..d9347d816 100644 --- a/src/x11.h +++ b/src/x11.h @@ -41,6 +41,7 @@ #include #include +#include #include #ifdef BUILD_ARGB @@ -159,6 +160,14 @@ union InputEvent { InputEvent *xev_as_input_event(XEvent &ev); void propagate_x11_event(XEvent &ev, const void *cookie); +/// @brief Returns a list of window values for the given atom. +/// @param display display with which the atom is associated +/// @param window window to query for the atom value +/// @param atom atom to query for +/// @return a list of window values for the given 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 From ee4eb0f735c91a56331fbc09bf5ab5d16962f571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Sun, 14 Apr 2024 14:50:06 +0200 Subject: [PATCH 2/8] Improve ergonomics of the pointer API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WIP commit - I broke something and events no longer get reported. On a flip side, valuators can be referenced by either a name string or number id now, use of which is easy to optimize by the compiler. Signed-off-by: Tin Švagelj --- src/display-x11.cc | 61 ++++++++------- src/mouse-events.cc | 187 +++++++++++++++++++++++++++++++++++--------- src/mouse-events.h | 35 ++++++--- src/x11.cc | 2 +- 4 files changed, 208 insertions(+), 77 deletions(-) diff --git a/src/display-x11.cc b/src/display-x11.cc index 04714a7ad..816cb88ef 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -469,16 +469,12 @@ bool handle_event( } *cookie = data; + auto device_info = conky_device_info::from_xi_id(data->deviceid); if (data->evtype == XI_DeviceChanged) { - int device_id = data->sourceid; - - // update cached device info - if (xi_device_info_cache.count(device_id)) { - xi_device_info_cache.erase(device_id); - conky_device_info::from_xi_id(display, device_id); - } + if (device_info != nullptr) { device_info->update(data->display); } return true; } + device_info = conky_device_info::from_xi_id(data->deviceid, data->display); Window event_window; modifier_state_t mods; @@ -521,16 +517,19 @@ bool handle_event( } if (data->evtype == XI_Motion) { - auto device_info = conky_device_info::from_xi_id(display, data->deviceid); // TODO: Make valuator_index names configurable? // Note that these are absolute (not relative) values in some cases - int hor_move_v = device_info->valuators["Rel X"].index; // Almost always 0 - int vert_move_v = device_info->valuators["Rel Y"].index; // Almost always 1 - int hor_scroll_v = - device_info->valuators["Rel Horiz Scroll"].index; // Almost always 2 - int vert_scroll_v = - device_info->valuators["Rel Vert Scroll"].index; // Almost always 3 + conky_valuator_id hor_move_v = + device_info->valuator_index("Rel X").value(); // Almost always 0 + conky_valuator_id vert_move_v = + device_info->valuator_index("Rel Y").value(); // Almost always 1 + conky_valuator_id hor_scroll_v = + device_info->valuator_index("Rel Horiz Scroll") + .value(); // Almost always 2 + conky_valuator_id vert_scroll_v = + device_info->valuator_index("Rel Vert Scroll") + .value(); // Almost always 3 bool is_move = data->test_valuator(hor_move_v) || data->test_valuator(vert_move_v); @@ -562,24 +561,28 @@ bool handle_event( } } if (is_scroll && cursor_over_conky) { - // FIXME: Turn into relative values so direction works - auto horizontal = data->valuator_value(hor_scroll_v); - if (horizontal.value_or(0.0) != 0.0) { - scroll_direction_t direction = horizontal.value() > 0.0 - ? scroll_direction_t::SCROLL_LEFT - : scroll_direction_t::SCROLL_RIGHT; - *consumed = llua_mouse_hook( - mouse_scroll_event(data->event_x, data->event_y, data->root_x, - data->root_y, direction, mods)); + scroll_direction_t scroll_direction; + auto vertical = data->valuator_relative_value(vert_scroll_v); + double vertical_value = vertical.value_or(0.0); + + if (vertical_value != 0.0) { + scroll_direction = vertical_value < 0.0 + ? scroll_direction_t::SCROLL_UP + : scroll_direction_t::SCROLL_DOWN; + } else { + auto horizontal = data->valuator_relative_value(hor_scroll_v); + double horizontal_value = horizontal.value_or(0.0); + if (horizontal_value != 0.0) { + scroll_direction = horizontal_value < 0.0 + ? scroll_direction_t::SCROLL_LEFT + : scroll_direction_t::SCROLL_RIGHT; + } } - auto vertical = data->valuator_value(vert_scroll_v); - if (vertical.value_or(0.0) != 0.0) { - scroll_direction_t direction = vertical.value() > 0.0 - ? scroll_direction_t::SCROLL_DOWN - : scroll_direction_t::SCROLL_UP; + + if (scroll_direction != scroll_direction_t::SCROLL_UNKNOWN) { *consumed = llua_mouse_hook( mouse_scroll_event(data->event_x, data->event_y, data->root_x, - data->root_y, direction, mods)); + data->root_y, scroll_direction, mods)); } } } else if (cursor_over_conky && (data->evtype == XI_ButtonPress || diff --git a/src/mouse-events.cc b/src/mouse-events.cc index 7ebb43eb1..766a2139e 100644 --- a/src/mouse-events.cc +++ b/src/mouse-events.cc @@ -218,19 +218,41 @@ void mouse_button_event::push_lua_data(lua_State *L) const { } #ifdef BUILD_XINPUT -XIDeviceInfoMap xi_device_info_cache{}; +conky_device_info *conky_device_info::from_xi_id(int device_id, + Display *display) { + using XIDeviceInfoMap = std::map; + static XIDeviceInfoMap xi_device_info_cache{}; -conky_device_info *conky_device_info::from_xi_id(Display *display, - int device_id) { if (xi_device_info_cache.count(device_id)) { return &xi_device_info_cache[device_id]; } + if (display == nullptr) return nullptr; int num_devices; XIDeviceInfo *device = XIQueryDevice(display, device_id, &num_devices); - if (num_devices == 0) { return nullptr; } + if (num_devices == 0) return nullptr; + + conky_device_info info = conky_device_info{ + .device_id = device_id, + .name = std::string(device->name), + .valuators = std::map(), + .valuator_names = std::map()}; + info.update(display, device); + xi_device_info_cache[device_id] = info; + return &xi_device_info_cache[device_id]; +} + +void conky_device_info::update(Display *display, XIDeviceInfo *device) { + this->valuators.clear(); + this->valuator_names.clear(); + this->valuator_indices.clear(); + + if (device == nullptr) { + int num_devices; + device = XIQueryDevice(display, device_id, &num_devices); + if (num_devices == 0) return; + } - std::map valuators; for (int i = 0; i < device->num_classes; i++) { if (device->classes[i]->type != XIValuatorClass) continue; @@ -240,32 +262,116 @@ conky_device_info *conky_device_info::from_xi_id(Display *display, XFree(label); continue; } - - valuators[std::string(label)] = - xi_valuator_info{.index = static_cast(class_info->number), - .min = class_info->min, - .max = class_info->max}; - DBGP2("%s - %f %f %f", label, class_info->value, class_info->min, - class_info->max); + auto name = std::string(label); XFree(label); - } - xi_device_info_cache[device_id] = - conky_device_info{.device_id = device_id, - .name = std::string(device->name), - .valuators = valuators}; + auto info = conky_valuator_info{ + .index = static_cast(class_info->number), + .name = name, + .min = class_info->min, + .max = class_info->max, + .value = class_info->value, + .relative = class_info->mode == XIModeRelative, + }; + + // mode can be wrong, depending on device, drivers and system setup + if (info.value < info.min || info.value > info.max) { + info.relative = false; + } + // also (probably) not relative + // a single value state doesn't make sense for a valuator, unless the min & + // max don't exist, and they should(?) exist for relative valuator + if (info.min == info.max) { info.relative = false; } + + this->valuator_names[static_cast(class_info->number)] = name; + this->valuator_indices[name] = static_cast(class_info->number); + this->valuators[name] = info; + DBGP2("SToRING: %s %d", name.c_str(), info.index); + } XIFreeDeviceInfo(device); +} - return &xi_device_info_cache[device_id]; +const std::string *conky_device_info::valuator_name( + const conky_valuator_id &id) const { + if (std::holds_alternative(id)) { + return &std::get(id); + } else { + size_t index = std::get(id); + if (this->valuator_names.count(index) == 0) return nullptr; + return &this->valuator_names.at(index); + } +} +std::optional conky_device_info::valuator_index( + const conky_valuator_id &id) const { + if (std::holds_alternative(id)) { + return std::get(id); + } else { + std::string name = std::get(id); + if (this->valuator_indices.count(name) == 0) return std::nullopt; + return this->valuator_indices.at(name); + } +} +conky_valuator_info *conky_device_info::valuator(const conky_valuator_id &id) { + auto name = this->valuator_name(id); + if (name == nullptr) return nullptr; + if (this->valuators.count(*name) == 0) return nullptr; + return &this->valuators.at(*name); +} + +bool xi_event_data::test_valuator(const conky_valuator_id &id) const { + auto index = this->valuator_index(id); + return index.has_value() && this->valuators.count(index.value()) > 0; +} + +const std::string *xi_event_data::valuator_name( + const conky_valuator_id &id) const { + auto dev = conky_device_info::from_xi_id(this->deviceid, this->display); + if (dev == nullptr) return nullptr; + return dev->valuator_name(id); +} +std::optional xi_event_data::valuator_index( + const conky_valuator_id &id) const { + auto dev = conky_device_info::from_xi_id(this->deviceid, this->display); + if (dev == nullptr) return std::nullopt; + return dev->valuator_index(id); } -bool xi_event_data::test_valuator(size_t index) const { - return this->valuators.count(index) > 0; +conky_valuator_info *xi_event_data::valuator_info(const conky_valuator_id &id) { + auto dev = conky_device_info::from_xi_id(this->deviceid, this->display); + if (dev == nullptr) return nullptr; + auto valuator = dev->valuator(id); + if (valuator == nullptr) return nullptr; + return valuator; } -std::optional xi_event_data::valuator_value(size_t index) const { - if (this->valuators.count(index) == 0) return std::nullopt; - return std::optional(this->valuators.at(index)); +std::optional xi_event_data::valuator_value( + const conky_valuator_id &id) const { + auto index = this->valuator_index(id); + if (!index.has_value() || this->valuators.count(index.value()) == 0) + return std::nullopt; + return this->valuators.at(index.value()); +} + +std::optional xi_event_data::valuator_relative_value( + const conky_valuator_id &id) const { + auto current = this->valuator_value(id); + if (!current.has_value()) return std::nullopt; + auto current_v = current.value(); + + auto dev = conky_device_info::from_xi_id(this->deviceid); + if (dev == nullptr) return std::nullopt; + auto valuator_info = dev->valuator(id); + if (valuator_info == nullptr) return std::nullopt; + + if (!valuator_info->relative) { + return current_v - valuator_info->value; + } else { + if (current_v < valuator_info->min || current_v > valuator_info->max) { + valuator_info->relative = false; + return current_v - valuator_info->value; + } + return current_v; + } } xi_event_data *xi_event_data::read_cookie(Display *display, @@ -322,20 +428,25 @@ std::vector> xi_event_data::generate_events( std::vector> result{}; if (this->evtype == XI_Motion) { - auto device_info = conky_device_info::from_xi_id(display, this->deviceid); + auto device_info = + conky_device_info::from_xi_id(this->deviceid, this->display); // Note that these are absolute (not relative) values in some cases - int hor_move_v = device_info->valuators["Rel X"].index; // Almost always 0 - int vert_move_v = device_info->valuators["Rel Y"].index; // Almost always 1 - int hor_scroll_v = - device_info->valuators["Rel Horiz Scroll"].index; // Almost always 2 - int vert_scroll_v = - device_info->valuators["Rel Vert Scroll"].index; // Almost always 3 + size_t hor_move_v = + device_info->valuator_index("Rel X").value(); // Almost always 0 + size_t vert_move_v = + device_info->valuator_index("Rel Y").value(); // Almost always 1 + size_t hor_scroll_v = device_info->valuator_index("Rel Horiz Scroll") + .value(); // Almost always 2 + size_t vert_scroll_v = device_info->valuator_index("Rel Vert Scroll") + .value(); // Almost always 3 bool is_move = this->test_valuator(hor_move_v) || this->test_valuator(vert_move_v); bool is_scroll = this->test_valuator(hor_scroll_v) || this->test_valuator(vert_scroll_v); + DBGP2("IS SCROLL %d %d %s", hor_move_v, vert_move_v, + is_scroll ? "true" : "false"); if (is_move) { XEvent *produced = new XEvent; @@ -362,15 +473,17 @@ std::vector> xi_event_data::generate_events( std::memset(produced, 0, sizeof(XEvent)); uint scroll_direction = 4; - auto vertical = this->valuator_value(vert_scroll_v); + auto vertical = this->valuator_relative_value(vert_scroll_v); + double vertical_value = vertical.value_or(0.0); + DBGP2("Vert Scroll: %d", vertical_value); - // FIXME: Turn into relative values so direction works - if (vertical.value_or(0.0) != 0.0) { - scroll_direction = vertical.value() < 0.0 ? Button4 : Button5; + if (vertical_value != 0.0) { + scroll_direction = vertical_value < 0.0 ? Button4 : Button5; } else { - auto horizontal = this->valuator_value(hor_scroll_v); - if (horizontal.value_or(0.0) != 0.0) { - scroll_direction = horizontal.value() < 0.0 ? 6 : 7; + auto horizontal = this->valuator_relative_value(hor_scroll_v); + double horizontal_value = horizontal.value_or(0.0); + if (horizontal_value != 0.0) { + scroll_direction = horizontal_value < 0.0 ? 6 : 7; } } diff --git a/src/mouse-events.h b/src/mouse-events.h index 0bb2f7dd6..83745d4c7 100644 --- a/src/mouse-events.h +++ b/src/mouse-events.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #endif /* BUILD_XINPUT */ @@ -243,25 +244,34 @@ struct mouse_crossing_event : public mouse_positioned_event { typedef int xi_device_id; typedef int xi_event_type; -struct xi_valuator_info { +/// Either a numeric valuator id or name. +using conky_valuator_id = std::variant; + +struct conky_valuator_info { size_t index; + std::string name; double min; double max; + double value; + bool relative; }; struct conky_device_info { xi_device_id device_id; std::string name; - std::map valuators; + std::map valuators; + std::map valuator_names; + std::map valuator_indices; - static conky_device_info *from_xi_id(Display *display, xi_device_id id); -}; + static conky_device_info *from_xi_id(xi_device_id id, + Display *display = nullptr); + void update(Display *display, XIDeviceInfo *device = nullptr); -typedef std::map XIDeviceInfoMap; -extern XIDeviceInfoMap xi_device_info_cache; + const std::string *valuator_name(const conky_valuator_id &id) const; + std::optional valuator_index(const conky_valuator_id &id) const; -int xi_valuator_index(Display *display, xi_device_id device_id, - const char *valuator); + conky_valuator_info *valuator(const conky_valuator_id &id); +}; /// Almost an exact copy of `XIDeviceEvent`, except it owns all data. struct xi_event_data { @@ -296,8 +306,13 @@ struct xi_event_data { static xi_event_data *read_cookie(Display *display, XGenericEventCookie *cookie); - bool test_valuator(size_t index) const; - std::optional valuator_value(size_t index) const; + bool test_valuator(const conky_valuator_id &id) const; + const std::string *valuator_name(const conky_valuator_id &id) const; + std::optional valuator_index(const conky_valuator_id &id) const; + conky_valuator_info *valuator_info(const conky_valuator_id &id); + std::optional valuator_value(const conky_valuator_id &id) const; + std::optional valuator_relative_value( + const conky_valuator_id &id) const; std::vector> generate_events(Window target, Window child, diff --git a/src/x11.cc b/src/x11.cc index 987bbe8b5..1fc1b05d7 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1434,7 +1434,7 @@ void propagate_xinput_event(const conky::xi_event_data *ev) { target = below.back(); // Update event x and y coordinates to be target window relative - XTranslateCoordinates(display, window.root, ev->event, ev->root_x, + XTranslateCoordinates(display, window.desktop, ev->event, ev->root_x, ev->root_y, &target_x, &target_y, &child); } } From c1f96d7953ca812a0515c2c9f39d2636922681ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Mon, 15 Apr 2024 10:13:20 +0200 Subject: [PATCH 3/8] Simplify use of XInput related classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added support for changing valuator properties through xorg.conf. Signed-off-by: Tin Švagelj --- src/display-x11.cc | 33 ++--- src/mouse-events.cc | 332 +++++++++++++++++++++++++------------------- src/mouse-events.h | 37 ++--- src/x11.cc | 1 + 4 files changed, 216 insertions(+), 187 deletions(-) diff --git a/src/display-x11.cc b/src/display-x11.cc index 816cb88ef..d4b0152ac 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -469,12 +469,13 @@ bool handle_event( } *cookie = data; - auto device_info = conky_device_info::from_xi_id(data->deviceid); - if (data->evtype == XI_DeviceChanged) { - if (device_info != nullptr) { device_info->update(data->display); } + auto device_info = device_info::from_xi_id(data->deviceid); + if (data->evtype == XI_HierarchyChanged) { + auto device_change = reinterpret_cast(data); + handle_xi_device_change(device_change); return true; } - device_info = conky_device_info::from_xi_id(data->deviceid, data->display); + device_info = device_info::from_xi_id(data->deviceid, data->display); Window event_window; modifier_state_t mods; @@ -519,22 +520,10 @@ bool handle_event( if (data->evtype == XI_Motion) { // TODO: Make valuator_index names configurable? - // Note that these are absolute (not relative) values in some cases - conky_valuator_id hor_move_v = - device_info->valuator_index("Rel X").value(); // Almost always 0 - conky_valuator_id vert_move_v = - device_info->valuator_index("Rel Y").value(); // Almost always 1 - conky_valuator_id hor_scroll_v = - device_info->valuator_index("Rel Horiz Scroll") - .value(); // Almost always 2 - conky_valuator_id vert_scroll_v = - device_info->valuator_index("Rel Vert Scroll") - .value(); // Almost always 3 - - bool is_move = - data->test_valuator(hor_move_v) || data->test_valuator(vert_move_v); - bool is_scroll = - data->test_valuator(hor_scroll_v) || data->test_valuator(vert_scroll_v); + bool is_move = data->test_valuator(valuator_t::MOVE_X) || + data->test_valuator(valuator_t::MOVE_Y); + bool is_scroll = data->test_valuator(valuator_t::SCROLL_X) || + data->test_valuator(valuator_t::SCROLL_Y); if (is_move) { static bool cursor_inside = false; @@ -562,7 +551,7 @@ bool handle_event( } if (is_scroll && cursor_over_conky) { scroll_direction_t scroll_direction; - auto vertical = data->valuator_relative_value(vert_scroll_v); + auto vertical = data->valuator_relative_value(valuator_t::SCROLL_Y); double vertical_value = vertical.value_or(0.0); if (vertical_value != 0.0) { @@ -570,7 +559,7 @@ bool handle_event( ? scroll_direction_t::SCROLL_UP : scroll_direction_t::SCROLL_DOWN; } else { - auto horizontal = data->valuator_relative_value(hor_scroll_v); + auto horizontal = data->valuator_relative_value(valuator_t::SCROLL_X); double horizontal_value = horizontal.value_or(0.0); if (horizontal_value != 0.0) { scroll_direction = horizontal_value < 0.0 diff --git a/src/mouse-events.cc b/src/mouse-events.cc index 766a2139e..864b1081a 100644 --- a/src/mouse-events.cc +++ b/src/mouse-events.cc @@ -38,6 +38,7 @@ extern "C" { #include #endif +#include #include #include } @@ -218,13 +219,15 @@ void mouse_button_event::push_lua_data(lua_State *L) const { } #ifdef BUILD_XINPUT -conky_device_info *conky_device_info::from_xi_id(int device_id, - Display *display) { - using XIDeviceInfoMap = std::map; - static XIDeviceInfoMap xi_device_info_cache{}; +/// Last global device id. +size_t last_device_id = 0; - if (xi_device_info_cache.count(device_id)) { - return &xi_device_info_cache[device_id]; +static std::map device_info_cache{}; +static std::map xi_id_mapping{}; + +device_info *device_info::from_xi_id(xi_device_id device_id, Display *display) { + if (xi_id_mapping.count(device_id) > 0) { + return &device_info_cache[xi_id_mapping[device_id]]; } if (display == nullptr) return nullptr; @@ -232,146 +235,166 @@ conky_device_info *conky_device_info::from_xi_id(int device_id, XIDeviceInfo *device = XIQueryDevice(display, device_id, &num_devices); if (num_devices == 0) return nullptr; - conky_device_info info = conky_device_info{ - .device_id = device_id, - .name = std::string(device->name), - .valuators = std::map(), - .valuator_names = std::map()}; - info.update(display, device); - xi_device_info_cache[device_id] = info; - return &xi_device_info_cache[device_id]; + device_info info = device_info{.name = std::string(device->name)}; + + size_t id = last_device_id++; + info.init_xi_device(display, device); + XIFreeDeviceInfo(device); + + device_info_cache[id] = info; + xi_id_mapping[device_id] = id; + + return &device_info_cache[id]; } -void conky_device_info::update(Display *display, XIDeviceInfo *device) { - this->valuators.clear(); - this->valuator_names.clear(); - this->valuator_indices.clear(); +void handle_xi_device_change(const XIHierarchyEvent *event) { + if (event->flags & XISlaveRemoved != 0) { + for (int i = 0; i < event->num_info; i++) { + auto info = event->info[i]; + if (info.flags & XISlaveRemoved != 0 && + xi_id_mapping.count(info.deviceid) > 0) { + size_t id = xi_id_mapping[info.deviceid]; + xi_id_mapping.erase(info.deviceid); + device_info_cache.erase(id); + return; + } + } + } +} + +/// Allows override of valuator indices in `xorg.conf` in case they're wrong for +/// some device (unlikely). +size_t fixed_valuator_index(Display *display, XIDeviceInfo *device, + valuator_t valuator) { + const std::array atom_names = { + "ConkyValuatorMoveX", "ConkyValuatorMoveY", "ConkyValuatorScrollX", + "ConkyValuatorScrollY"}; + Atom override_atom = XInternAtom(display, atom_names[valuator], False); + unsigned char *value; + Atom type_return; + 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]); + XFree(value); + return valuator; + } + uint32_t result = *reinterpret_cast(value); + XFree(value); + return static_cast(result); + } + return valuator; +} + +/// Allows override of valuator value type in `xorg.conf` in case they're wrong +/// for some device (happens with VMs and some devices/setups). +bool fixed_valuator_relative(Display *display, XIDeviceInfo *device, + valuator_t valuator, + XIValuatorClassInfo *class_info) { + const std::array atom_names = { + "ConkyValuatorMoveType", + "ConkyValuatorScrollType", + }; + Atom override_atom = XInternAtom(display, atom_names[valuator >> 1], False); + unsigned char *value; + 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]); + XFree(value); + return class_info->type == XIModeRelative; + } + XFree(value); + return relative; + } + return class_info->type == XIModeRelative; +} - if (device == nullptr) { +void device_info::init_xi_device( + Display *display, std::variant source) { + XIDeviceInfo *device = nullptr; + if (std::holds_alternative(source)) { + device = std::get(source); + } else if (std::holds_alternative(source)) { int num_devices; - device = XIQueryDevice(display, device_id, &num_devices); + device = + XIQueryDevice(display, std::get(source), &num_devices); if (num_devices == 0) return; } + if (device == nullptr) return; + std::array valuator_indices; + for (size_t i = 0; i < valuator_t::VALUATOR_COUNT; i++) { + valuator_indices[i] = + fixed_valuator_index(display, device, static_cast(i)); + } + + // class order is undefined! for (int i = 0; i < device->num_classes; i++) { if (device->classes[i]->type != XIValuatorClass) continue; - XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)device->classes[i]; - char *label = XGetAtomName(display, class_info->label); - if (label == nullptr) { - XFree(label); - continue; + + // check if one of used (mapped) valuators + valuator_t valuator = valuator_t::VALUATOR_COUNT; + for (size_t i = 0; i < valuator_t::VALUATOR_COUNT; i++) { + if (valuator_indices[i] == class_info->number) { + valuator = static_cast(i); + break; + } } - auto name = std::string(label); - XFree(label); + if (valuator == valuator_t::VALUATOR_COUNT) { continue; } auto info = conky_valuator_info{ .index = static_cast(class_info->number), - .name = name, .min = class_info->min, .max = class_info->max, .value = class_info->value, - .relative = class_info->mode == XIModeRelative, + .relative = + fixed_valuator_relative(display, device, valuator, class_info), }; - // mode can be wrong, depending on device, drivers and system setup - if (info.value < info.min || info.value > info.max) { - info.relative = false; - } - // also (probably) not relative - // a single value state doesn't make sense for a valuator, unless the min & - // max don't exist, and they should(?) exist for relative valuator - if (info.min == info.max) { info.relative = false; } - - this->valuator_names[static_cast(class_info->number)] = name; - this->valuator_indices[name] = static_cast(class_info->number); - this->valuators[name] = info; + this->valuators[valuator] = info; DBGP2("SToRING: %s %d", name.c_str(), info.index); } - XIFreeDeviceInfo(device); -} -const std::string *conky_device_info::valuator_name( - const conky_valuator_id &id) const { - if (std::holds_alternative(id)) { - return &std::get(id); - } else { - size_t index = std::get(id); - if (this->valuator_names.count(index) == 0) return nullptr; - return &this->valuator_names.at(index); - } -} -std::optional conky_device_info::valuator_index( - const conky_valuator_id &id) const { - if (std::holds_alternative(id)) { - return std::get(id); - } else { - std::string name = std::get(id); - if (this->valuator_indices.count(name) == 0) return std::nullopt; - return this->valuator_indices.at(name); + if (std::holds_alternative(source)) { + XIFreeDeviceInfo(device); } } -conky_valuator_info *conky_device_info::valuator(const conky_valuator_id &id) { - auto name = this->valuator_name(id); - if (name == nullptr) return nullptr; - if (this->valuators.count(*name) == 0) return nullptr; - return &this->valuators.at(*name); -} - -bool xi_event_data::test_valuator(const conky_valuator_id &id) const { - auto index = this->valuator_index(id); - return index.has_value() && this->valuators.count(index.value()) > 0; -} - -const std::string *xi_event_data::valuator_name( - const conky_valuator_id &id) const { - auto dev = conky_device_info::from_xi_id(this->deviceid, this->display); - if (dev == nullptr) return nullptr; - return dev->valuator_name(id); -} -std::optional xi_event_data::valuator_index( - const conky_valuator_id &id) const { - auto dev = conky_device_info::from_xi_id(this->deviceid, this->display); - if (dev == nullptr) return std::nullopt; - return dev->valuator_index(id); -} - -conky_valuator_info *xi_event_data::valuator_info(const conky_valuator_id &id) { - auto dev = conky_device_info::from_xi_id(this->deviceid, this->display); - if (dev == nullptr) return nullptr; - auto valuator = dev->valuator(id); - if (valuator == nullptr) return nullptr; - return valuator; -} - -std::optional xi_event_data::valuator_value( - const conky_valuator_id &id) const { - auto index = this->valuator_index(id); - if (!index.has_value() || this->valuators.count(index.value()) == 0) - return std::nullopt; - return this->valuators.at(index.value()); -} - -std::optional xi_event_data::valuator_relative_value( - const conky_valuator_id &id) const { - auto current = this->valuator_value(id); - if (!current.has_value()) return std::nullopt; - auto current_v = current.value(); - - auto dev = conky_device_info::from_xi_id(this->deviceid); - if (dev == nullptr) return std::nullopt; - auto valuator_info = dev->valuator(id); - if (valuator_info == nullptr) return std::nullopt; - - if (!valuator_info->relative) { - return current_v - valuator_info->value; - } else { - if (current_v < valuator_info->min || current_v > valuator_info->max) { - valuator_info->relative = false; - return current_v - valuator_info->value; - } - return current_v; - } +conky_valuator_info &device_info::valuator(valuator_t valuator) { + return this->valuators[valuator]; } xi_event_data *xi_event_data::read_cookie(Display *display, @@ -423,30 +446,53 @@ xi_event_data *xi_event_data::read_cookie(Display *display, return result; } +bool xi_event_data::test_valuator(valuator_t valuator) const { + auto device = device_info::from_xi_id(this->deviceid, this->display); + if (device == nullptr) return false; + return this->valuators.count(device->valuator(valuator).index) > 0; +} +conky_valuator_info *xi_event_data::valuator_info(valuator_t valuator) const { + auto device = device_info::from_xi_id(this->deviceid, this->display); + if (device == nullptr) return nullptr; + return &device->valuator(valuator); +} +std::optional xi_event_data::valuator_value(valuator_t valuator) const { + auto info = this->valuator_info(valuator); + if (info == nullptr) return std::nullopt; + size_t index = info->index; + if (this->valuators.count(index) == 0) return std::nullopt; + return this->valuators.at(index); +} + +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; + } +} + std::vector> xi_event_data::generate_events( Window target, Window child, double target_x, double target_y) const { std::vector> result{}; if (this->evtype == XI_Motion) { - auto device_info = - conky_device_info::from_xi_id(this->deviceid, this->display); - - // Note that these are absolute (not relative) values in some cases - size_t hor_move_v = - device_info->valuator_index("Rel X").value(); // Almost always 0 - size_t vert_move_v = - device_info->valuator_index("Rel Y").value(); // Almost always 1 - size_t hor_scroll_v = device_info->valuator_index("Rel Horiz Scroll") - .value(); // Almost always 2 - size_t vert_scroll_v = device_info->valuator_index("Rel Vert Scroll") - .value(); // Almost always 3 - - bool is_move = - this->test_valuator(hor_move_v) || this->test_valuator(vert_move_v); - bool is_scroll = - this->test_valuator(hor_scroll_v) || this->test_valuator(vert_scroll_v); - DBGP2("IS SCROLL %d %d %s", hor_move_v, vert_move_v, - is_scroll ? "true" : "false"); + auto device_info = device_info::from_xi_id(this->deviceid, this->display); + + bool is_move = this->test_valuator(valuator_t::MOVE_X) || + this->test_valuator(valuator_t::MOVE_Y); + bool is_scroll = this->test_valuator(valuator_t::SCROLL_X) || + this->test_valuator(valuator_t::SCROLL_Y); if (is_move) { XEvent *produced = new XEvent; @@ -473,14 +519,14 @@ std::vector> xi_event_data::generate_events( std::memset(produced, 0, sizeof(XEvent)); uint scroll_direction = 4; - auto vertical = this->valuator_relative_value(vert_scroll_v); + auto vertical = this->valuator_relative_value(valuator_t::SCROLL_Y); double vertical_value = vertical.value_or(0.0); DBGP2("Vert Scroll: %d", vertical_value); if (vertical_value != 0.0) { scroll_direction = vertical_value < 0.0 ? Button4 : Button5; } else { - auto horizontal = this->valuator_relative_value(hor_scroll_v); + auto horizontal = this->valuator_relative_value(valuator_t::SCROLL_X); double horizontal_value = horizontal.value_or(0.0); if (horizontal_value != 0.0) { scroll_direction = horizontal_value < 0.0 ? 6 : 7; diff --git a/src/mouse-events.h b/src/mouse-events.h index 83745d4c7..0af245c35 100644 --- a/src/mouse-events.h +++ b/src/mouse-events.h @@ -29,6 +29,7 @@ #include "logging.h" #ifdef BUILD_XINPUT +#include #include #include #include @@ -244,35 +245,30 @@ struct mouse_crossing_event : public mouse_positioned_event { typedef int xi_device_id; typedef int xi_event_type; -/// Either a numeric valuator id or name. -using conky_valuator_id = std::variant; +enum valuator_t : size_t { MOVE_X, MOVE_Y, SCROLL_X, SCROLL_Y, VALUATOR_COUNT }; struct conky_valuator_info { size_t index; - std::string name; double min; double max; double value; bool relative; }; -struct conky_device_info { - xi_device_id device_id; +struct device_info { + /// @brief Device name. std::string name; - std::map valuators; - std::map valuator_names; - std::map valuator_indices; - - static conky_device_info *from_xi_id(xi_device_id id, - Display *display = nullptr); - void update(Display *display, XIDeviceInfo *device = nullptr); + std::array valuators{}; - const std::string *valuator_name(const conky_valuator_id &id) const; - std::optional valuator_index(const conky_valuator_id &id) const; + static device_info *from_xi_id(xi_device_id id, Display *display = nullptr); + void init_xi_device(Display *display, + std::variant device); - conky_valuator_info *valuator(const conky_valuator_id &id); + conky_valuator_info &valuator(valuator_t valuator); }; +void handle_xi_device_change(const XIHierarchyEvent *event); + /// Almost an exact copy of `XIDeviceEvent`, except it owns all data. struct xi_event_data { xi_event_type evtype; @@ -306,13 +302,10 @@ struct xi_event_data { static xi_event_data *read_cookie(Display *display, XGenericEventCookie *cookie); - bool test_valuator(const conky_valuator_id &id) const; - const std::string *valuator_name(const conky_valuator_id &id) const; - std::optional valuator_index(const conky_valuator_id &id) const; - conky_valuator_info *valuator_info(const conky_valuator_id &id); - std::optional valuator_value(const conky_valuator_id &id) const; - std::optional valuator_relative_value( - const conky_valuator_id &id) const; + bool test_valuator(valuator_t id) const; + conky_valuator_info *valuator_info(valuator_t id) const; + std::optional valuator_value(valuator_t id) const; + std::optional valuator_relative_value(valuator_t valuator) const; std::vector> generate_events(Window target, Window child, diff --git a/src/x11.cc b/src/x11.cc index 1fc1b05d7..68ac7ae3d 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -969,6 +969,7 @@ void x11_init_window(lua::state &l, bool own) { const std::size_t mask_size = (XI_LASTEVENT + 7) / 8; unsigned char mask_bytes[mask_size] = {0}; /* must be zeroed! */ + XISetMask(mask_bytes, XI_HierarchyChanged); XISetMask(mask_bytes, XI_Motion); // Capture click events for "override" window type if (!own) { From db70a1b6cfcf01b8e8c1ecbb313449bb8bb7eb63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Wed, 17 Apr 2024 01:08:51 +0200 Subject: [PATCH 4/8] Fix cursor_over_conky and override atoms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WIP commit - relative calculation is still off. Calculate relative valuators only once per event and cache results. Switch to finding top-level window before root. This made me reuse virtual root code so I refactored it and made it apply automatically in place of X11 macros. This means that all code in x11.cc will use virtual roots if they exist. Fixed query_x11_windows fallback to exclude windows without WM hints. I believe this is the correct way to ignore windows specific to some special WM functionality. Removed InputEvent and xev_as_input_event as they were being used in only a single place. Signed-off-by: Tin Švagelj --- src/display-x11.cc | 28 ++--- src/mouse-events.cc | 146 ++++++++++++++---------- src/mouse-events.h | 8 +- src/x11.cc | 272 ++++++++++++++++++++------------------------ src/x11.h | 58 +++------- 5 files changed, 242 insertions(+), 270 deletions(-) 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..5adc16d5a 100644 --- a/src/mouse-events.cc +++ b/src/mouse-events.cc @@ -248,10 +248,10 @@ device_info *device_info::from_xi_id(xi_device_id device_id, Display *display) { } void handle_xi_device_change(const XIHierarchyEvent *event) { - if (event->flags & XISlaveRemoved != 0) { + if ((event->flags & XISlaveRemoved) != 0) { for (int i = 0; i < event->num_info; i++) { auto info = event->info[i]; - if (info.flags & XISlaveRemoved != 0 && + if ((info.flags & XISlaveRemoved) != 0 && xi_id_mapping.count(info.deviceid) > 0) { size_t id = xi_id_mapping[info.deviceid]; xi_id_mapping.erase(info.deviceid); @@ -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. /// From a8bef653fa347e8d82e191a9a1a3beb0aa61cf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Wed, 17 Apr 2024 02:00:22 +0200 Subject: [PATCH 5/8] Fix valuator_info not being updated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wasn't updated due to implicit reference copy. Now relative seems to work. Signed-off-by: Tin Švagelj --- src/mouse-events.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/mouse-events.cc b/src/mouse-events.cc index 5adc16d5a..e90dee30d 100644 --- a/src/mouse-events.cc +++ b/src/mouse-events.cc @@ -422,11 +422,9 @@ xi_event_data *xi_event_data::read_cookie(Display *display, } std::map valuators{}; - size_t valuator_index = 0; + double *values = source->valuators.values; for (size_t vi = 0; vi < source->valuators.mask_len * 8; vi++) { - if (XIMaskIsSet(source->valuators.mask, vi)) { - valuators[vi] = source->valuators.values[valuator_index++]; - } + if (XIMaskIsSet(source->valuators.mask, vi)) { valuators[vi] = *values++; } } auto result = new xi_event_data{ @@ -460,7 +458,7 @@ xi_event_data *xi_event_data::read_cookie(Display *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); + auto &valuator_info = device->valuator(valuator); if (result->valuators.count(valuator_info.index) == 0) { continue; } auto current = result->valuators[valuator_info.index]; From 7c747796820eeb4ea363537af45ed62c1f7a1031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Wed, 17 Apr 2024 02:54:08 +0200 Subject: [PATCH 6/8] Fill cache at startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First reported event was being reported with bad computed relative value otherwise. Signed-off-by: Tin Švagelj --- src/mouse-events.cc | 22 +++++++++++++++------- src/mouse-events.h | 13 +++++++++---- src/x11.cc | 16 +++++++++++++++- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/mouse-events.cc b/src/mouse-events.cc index e90dee30d..d2ebc84c9 100644 --- a/src/mouse-events.cc +++ b/src/mouse-events.cc @@ -251,13 +251,21 @@ void handle_xi_device_change(const XIHierarchyEvent *event) { if ((event->flags & XISlaveRemoved) != 0) { for (int i = 0; i < event->num_info; i++) { auto info = event->info[i]; - if ((info.flags & XISlaveRemoved) != 0 && - xi_id_mapping.count(info.deviceid) > 0) { - size_t id = xi_id_mapping[info.deviceid]; - xi_id_mapping.erase(info.deviceid); - device_info_cache.erase(id); - return; - } + if ((info.flags & XISlaveRemoved) == 0) continue; + if (xi_id_mapping.count(info.deviceid) == 0) continue; + + size_t id = xi_id_mapping[info.deviceid]; + xi_id_mapping.erase(info.deviceid); + device_info_cache.erase(id); + } + } + + if ((event->flags & XISlaveAdded) != 0) { + for (int i = 0; i < event->num_info; i++) { + auto info = event->info[i]; + if ((info.flags & XISlaveAdded) == 0) continue; + if (info.use == IsXPointer || info.use == IsXExtensionPointer) + conky::device_info::from_xi_id(info.deviceid, event->display); } } } diff --git a/src/mouse-events.h b/src/mouse-events.h index 3c55358cc..9566bb3d9 100644 --- a/src/mouse-events.h +++ b/src/mouse-events.h @@ -42,7 +42,10 @@ extern "C" { #include #ifdef BUILD_XINPUT +#include #include +#undef COUNT // define from X11/extendsions/Xi.h + #endif /* BUILD_XINPUT */ #endif /* BUILD_X11 */ @@ -54,7 +57,7 @@ extern "C" { #include #elif __DragonFly__ #include -#else +#else /* platform */ // Probably incorrect for some platforms, feel free to add your platform to the // above list if it has other event codes or a standard file containing them. @@ -69,7 +72,7 @@ extern "C" { #define BTN_BACK 0x116 // Forward mouse button event code #define BTN_FORWARD 0x115 -#endif +#endif /* platform */ } namespace conky { @@ -260,10 +263,12 @@ struct device_info { std::array valuators{}; static device_info *from_xi_id(xi_device_id id, Display *display = nullptr); - void init_xi_device(Display *display, - std::variant device); conky_valuator_info &valuator(valuator_t valuator); + + private: + void init_xi_device(Display *display, + std::variant device); }; void handle_xi_device_change(const XIHierarchyEvent *event); diff --git a/src/x11.cc b/src/x11.cc index 184a8b430..961cf3639 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -39,6 +39,8 @@ #ifdef BUILD_XINPUT #include "mouse-events.h" + +#include #endif #include @@ -48,6 +50,7 @@ #include #include +extern "C" { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wvariadic-macros" #pragma GCC diagnostic ignored "-Wregister" @@ -77,13 +80,14 @@ #include #endif /* BUILD_XFIXES */ #ifdef BUILD_XINPUT +#include #include -#include #endif /* BUILD_XINPUT */ #ifdef HAVE_XCB_ERRORS #include #include #endif +} /* some basic X11 stuff */ Display *display = nullptr; @@ -975,6 +979,16 @@ void x11_init_window(lua::state &l, bool own) { XISelectEvents(display, window.window, ev_masks, 1); } + // setup cache + int num_devices; + XDeviceInfo *info = XListInputDevices(display, &num_devices); + for (int i = 0; i < num_devices; i++) { + if (info[i].use == IsXPointer || info[i].use == IsXExtensionPointer) { + conky::device_info::from_xi_id(info[i].id, display); + } + } + XFreeDeviceList(info); + xinput_ok = true; } while (false); #endif /* BUILD_XINPUT */ From feab28a75eec75779847cb7dfd1a2e6710c61e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Wed, 17 Apr 2024 03:50:55 +0200 Subject: [PATCH 7/8] Fix crash when device disconnected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tin Švagelj --- src/display-x11.cc | 23 ++++++++++++----------- src/mouse-events.cc | 42 +++++++++++++++++++----------------------- src/mouse-events.h | 6 +++--- src/x11.cc | 3 ++- 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/display-x11.cc b/src/display-x11.cc index 045566368..d6defcf99 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -462,25 +462,26 @@ bool handle_event( if (ev.type != GenericEvent || ev.xgeneric.extension != window.xi_opcode) return false; - auto *data = xi_event_data::read_cookie(display, &ev.xcookie); - if (data == nullptr) { - NORM_ERR("unable to get XInput event data"); - return false; + if (!XGetEventData(display, &ev.xcookie)) { + // already consumed + return true; } - *cookie = data; + xi_event_type event_type = ev.xcookie.evtype; - auto device_info = device_info::from_xi_id(data->deviceid); - if (data->evtype == XI_HierarchyChanged) { - auto device_change = reinterpret_cast(data); + if (event_type == XI_HierarchyChanged) { + auto device_change = reinterpret_cast(ev.xcookie.data); handle_xi_device_change(device_change); + XFreeEventData(display, &ev.xcookie); return true; } - device_info = device_info::from_xi_id(data->deviceid, data->display); - if (!(data->evtype == XI_Motion || data->evtype == XI_ButtonPress || - data->evtype == XI_ButtonRelease)) { + auto *data = xi_event_data::read_cookie(display, ev.xcookie.data); + XFreeEventData(display, &ev.xcookie); + if (data == nullptr) { + // we ate the cookie, Xi event not handled return true; } + *cookie = data; Window event_window = query_x11_window_at_pos(display, data->root_x, data->root_y); diff --git a/src/mouse-events.cc b/src/mouse-events.cc index d2ebc84c9..4a1c04864 100644 --- a/src/mouse-events.cc +++ b/src/mouse-events.cc @@ -235,7 +235,8 @@ device_info *device_info::from_xi_id(xi_device_id device_id, Display *display) { XIDeviceInfo *device = XIQueryDevice(display, device_id, &num_devices); if (num_devices == 0) return nullptr; - device_info info = device_info{.name = std::string(device->name)}; + device_info info = + device_info{.id = device_id, .name = std::string(device->name)}; size_t id = last_device_id++; info.init_xi_device(display, device); @@ -416,25 +417,28 @@ conky_valuator_info &device_info::valuator(valuator_t valuator) { return this->valuators[valuator]; } -xi_event_data *xi_event_data::read_cookie(Display *display, - XGenericEventCookie *cookie) { - if (!XGetEventData(display, cookie)) { - // already consumed +xi_event_data *xi_event_data::read_cookie(Display *display, const void *data) { + const XIDeviceEvent *source = reinterpret_cast(data); + xi_event_type event_type = source->evtype; + if (!(event_type == XI_Motion || event_type == XI_ButtonPress || + event_type == XI_ButtonRelease)) { return nullptr; } - auto *source = reinterpret_cast(cookie->data); - uint32_t buttons = 0; - for (size_t bi = 1; bi <= source->buttons.mask_len; bi++) { - buttons |= source->buttons.mask[bi] << (source->buttons.mask_len - bi) * 8; + std::bitset<32> buttons; + for (size_t bi = 1; bi < source->buttons.mask_len * 8; bi++) { + if (XIMaskIsSet(source->buttons.mask, bi)) buttons[bi] = true; } std::map valuators{}; double *values = source->valuators.values; for (size_t vi = 0; vi < source->valuators.mask_len * 8; vi++) { - if (XIMaskIsSet(source->valuators.mask, vi)) { valuators[vi] = *values++; } + if (XIMaskIsSet(source->valuators.mask, vi)) valuators[vi] = *values++; } + auto device = device_info::from_xi_id(source->deviceid, source->display); + if (device == nullptr) return nullptr; // shouldn't happen + auto result = new xi_event_data{ .evtype = static_cast(source->evtype), .serial = source->serial, @@ -442,7 +446,7 @@ xi_event_data *xi_event_data::read_cookie(Display *display, .display = source->display, .extension = source->extension, .time = source->time, - .deviceid = source->deviceid, + .device = device, .sourceid = source->sourceid, .detail = source->detail, .root = source->root, @@ -453,17 +457,13 @@ xi_event_data *xi_event_data::read_cookie(Display *display, .event_x = source->event_x, .event_y = source->event_y, .flags = source->flags, - .buttons = std::bitset<32>(buttons), + .buttons = buttons, .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); @@ -486,14 +486,10 @@ xi_event_data *xi_event_data::read_cookie(Display *display, } bool xi_event_data::test_valuator(valuator_t valuator) const { - auto device = device_info::from_xi_id(this->deviceid, this->display); - if (device == nullptr) return false; - return this->valuators.count(device->valuator(valuator).index) > 0; + return this->valuators.count(this->device->valuator(valuator).index) > 0; } conky_valuator_info *xi_event_data::valuator_info(valuator_t valuator) const { - auto device = device_info::from_xi_id(this->deviceid, this->display); - if (device == nullptr) return nullptr; - return &device->valuator(valuator); + return &this->device->valuator(valuator); } std::optional xi_event_data::valuator_value(valuator_t valuator) const { auto info = this->valuator_info(valuator); @@ -513,7 +509,7 @@ std::vector> xi_event_data::generate_events( std::vector> result{}; if (this->evtype == XI_Motion) { - auto device_info = device_info::from_xi_id(this->deviceid, this->display); + auto device_info = this->device; bool is_move = this->test_valuator(valuator_t::MOVE_X) || this->test_valuator(valuator_t::MOVE_Y); diff --git a/src/mouse-events.h b/src/mouse-events.h index 9566bb3d9..cc25dc8c3 100644 --- a/src/mouse-events.h +++ b/src/mouse-events.h @@ -259,6 +259,7 @@ struct conky_valuator_info { struct device_info { /// @brief Device name. + xi_device_id id; std::string name; std::array valuators{}; @@ -284,7 +285,7 @@ struct xi_event_data { // printing. int extension; Time time; - xi_device_id deviceid; + device_info *device; int sourceid; int detail; Window root; @@ -306,8 +307,7 @@ struct xi_event_data { /// Precomputed relative values std::array valuators_relative; - static xi_event_data *read_cookie(Display *display, - XGenericEventCookie *cookie); + static xi_event_data *read_cookie(Display *display, const void *data); bool test_valuator(valuator_t id) const; conky_valuator_info *valuator_info(valuator_t id) const; diff --git a/src/x11.cc b/src/x11.cc index 961cf3639..b567f1e80 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1499,9 +1499,10 @@ void propagate_x11_event(XEvent &ev, const void *cookie) { } Window query_x11_top_level(Display *display, Window child) { - if (child == None) return child; Window root = DefaultVRootWindow(display); + if (child == None || child == root) return child; + Window ret_root, parent, *children; std::uint32_t child_count; From d5af5ac410efe68c4cb7f478a79b3c32ef7e3396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20=C5=A0vagelj?= Date: Wed, 17 Apr 2024 03:55:35 +0200 Subject: [PATCH 8/8] Cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tin Švagelj --- src/display-x11.cc | 4 ++-- src/mouse-events.cc | 1 - src/x11.cc | 2 +- src/x11.h | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/display-x11.cc b/src/display-x11.cc index d6defcf99..30f8b3f5c 100644 --- a/src/display-x11.cc +++ b/src/display-x11.cc @@ -488,8 +488,8 @@ bool handle_event( // 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 same_window = query_x11_top_parent(display, event_window) == + query_x11_top_parent(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 && diff --git a/src/mouse-events.cc b/src/mouse-events.cc index 4a1c04864..41b3e233e 100644 --- a/src/mouse-events.cc +++ b/src/mouse-events.cc @@ -543,7 +543,6 @@ std::vector> xi_event_data::generate_events( uint scroll_direction = 4; auto vertical = this->valuator_relative_value(valuator_t::SCROLL_Y); double vertical_value = vertical.value_or(0.0); - DBGP2("Vert Scroll: %d", vertical_value); if (vertical_value != 0.0) { scroll_direction = vertical_value < 0.0 ? Button4 : Button5; diff --git a/src/x11.cc b/src/x11.cc index b567f1e80..1784f83be 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1498,7 +1498,7 @@ void propagate_x11_event(XEvent &ev, const void *cookie) { } } -Window query_x11_top_level(Display *display, Window child) { +Window query_x11_top_parent(Display *display, Window child) { Window root = DefaultVRootWindow(display); if (child == None || child == root) return child; diff --git a/src/x11.h b/src/x11.h index 7afca0efe..70f858871 100644 --- a/src/x11.h +++ b/src/x11.h @@ -163,7 +163,7 @@ std::vector query_x11_windows(Display *display); /// @param display display of parent /// @param child window whose parents to query /// @return the top level ascendant window -Window query_x11_top_level(Display *display, Window child); +Window query_x11_top_parent(Display *display, Window child); /// @brief Returns the top-most window overlapping provided screen coordinates. ///