From d0337b8233c8ec388c0d623dcc9799c40def2d39 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 20 Feb 2020 12:17:47 +0100 Subject: [PATCH] x11: implement fallback relative mouse mode using XInput2 It's not "real" relative mouse like DGA, but it's good enough: it confines the cursor to the window and continues to provide relative mouse movement regardless of the position of the cursor in the window. This gets relative mouse mode working under wayland, where cursor warping is not supported; see #746. --- panda/src/x11display/x11GraphicsPipe.I | 8 +- panda/src/x11display/x11GraphicsPipe.cxx | 95 ++++++++++++++++++++- panda/src/x11display/x11GraphicsPipe.h | 52 +++++++++++- panda/src/x11display/x11GraphicsWindow.cxx | 98 +++++++++++++++------- panda/src/x11display/x11GraphicsWindow.h | 1 + 5 files changed, 217 insertions(+), 37 deletions(-) diff --git a/panda/src/x11display/x11GraphicsPipe.I b/panda/src/x11display/x11GraphicsPipe.I index a516b2e6a2..3f608bad5b 100644 --- a/panda/src/x11display/x11GraphicsPipe.I +++ b/panda/src/x11display/x11GraphicsPipe.I @@ -58,18 +58,18 @@ get_hidden_cursor() { } /** - * Returns true if relative mouse mode is supported on this display. + * Returns true if a form of relative mouse mode is supported on this display. */ INLINE bool x11GraphicsPipe:: supports_relative_mouse() const { - return (_XF86DGADirectVideo != nullptr); + return (_XF86DGADirectVideo != nullptr) || (_XISelectEvents != nullptr); } /** * Enables relative mouse mode for this display. Returns false if unsupported. */ INLINE bool x11GraphicsPipe:: -enable_relative_mouse() { +enable_dga_mouse() { if (_XF86DGADirectVideo != nullptr) { x11display_cat.info() << "Enabling relative mouse using XF86DGA extension\n"; _XF86DGADirectVideo(_display, _screen, XF86DGADirectMouse); @@ -82,7 +82,7 @@ enable_relative_mouse() { * Disables relative mouse mode for this display. */ INLINE void x11GraphicsPipe:: -disable_relative_mouse() { +disable_dga_mouse() { if (_XF86DGADirectVideo != nullptr) { x11display_cat.info() << "Disabling relative mouse using XF86DGA extension\n"; _XF86DGADirectVideo(_display, _screen, 0); diff --git a/panda/src/x11display/x11GraphicsPipe.cxx b/panda/src/x11display/x11GraphicsPipe.cxx index 8f87b57609..e2f3da7d9d 100644 --- a/panda/src/x11display/x11GraphicsPipe.cxx +++ b/panda/src/x11display/x11GraphicsPipe.cxx @@ -107,7 +107,7 @@ x11GraphicsPipe(const std::string &display) : int major_ver, minor_ver; if (_XF86DGAQueryVersion == nullptr || _XF86DGADirectVideo == nullptr) { x11display_cat.warning() - << "libXxf86dga.so.1 does not provide required functions; relative mouse mode will not work.\n"; + << "libXxf86dga.so.1 does not provide required functions; relative mouse mode may not work.\n"; } else if (!_XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) { _XF86DGADirectVideo = nullptr; @@ -116,7 +116,7 @@ x11GraphicsPipe(const std::string &display) : _XF86DGADirectVideo = nullptr; if (x11display_cat.is_debug()) { x11display_cat.debug() - << "cannot dlopen libXxf86dga.so.1; cursor changing will not work.\n"; + << "cannot dlopen libXxf86dga.so.1; relative mouse mode may not work.\n"; } } @@ -214,6 +214,44 @@ x11GraphicsPipe(const std::string &display) : } } + // Dynamically load the XInput2 extension. + int ev, err; + if (XQueryExtension(_display, "XInputExtension", &_xi_opcode, &ev, &err)) { + void *xi = dlopen("libXi.so.6", RTLD_NOW | RTLD_LOCAL); + if (xi != nullptr) { + pfn_XIQueryVersion _XIQueryVersion = (pfn_XIQueryVersion)dlsym(xi, "XIQueryVersion"); + _XISelectEvents = (pfn_XISelectEvents)dlsym(xi, "XISelectEvents"); + + int major_ver = 2, minor_ver = 0; + if (_XIQueryVersion == nullptr || _XISelectEvents == nullptr) { + x11display_cat.warning() + << "libXi.so.6 does not provide required functions; relative mouse mode will not work.\n"; + _XISelectEvents = nullptr; + dlclose(xi); + + } else if (_XIQueryVersion(_display, &major_ver, &minor_ver) == Success) { + if (x11display_cat.is_debug()) { + x11display_cat.debug() + << "Found XInput extension " << major_ver << "." << minor_ver << "\n"; + } + + } else { + if (x11display_cat.is_debug()) { + x11display_cat.debug() + << "XInput2 extension not supported; relative mouse mode will not work.\n"; + } + _XISelectEvents = nullptr; + dlclose(xi); + } + } else { + _XISelectEvents = nullptr; + if (x11display_cat.is_debug()) { + x11display_cat.debug() + << "cannot dlopen libXi.so.1; relative mouse mode will not work.\n"; + } + } + } + // Use Xrandr to fill in the supported resolution list. if (_have_xrandr) { // If we have XRRGetScreenResources, we prefer that. It seems to be more @@ -324,6 +362,59 @@ x11GraphicsPipe:: } } +/** + * Enables raw mouse mode for this display. Returns false if unsupported. + */ +INLINE bool x11GraphicsPipe:: +enable_raw_mouse() { + if (_num_raw_mouse_windows > 0) { + // Already enabled by another window. + ++_num_raw_mouse_windows; + return true; + } + if (_XISelectEvents != nullptr) { + XIEventMask event_mask; + unsigned char mask[XIMaskLen(XI_RawMotion)] = {0}; + + event_mask.deviceid = XIAllMasterDevices; + event_mask.mask_len = sizeof(mask); + event_mask.mask = mask; + XISetMask(mask, XI_RawMotion); + + if (_XISelectEvents(_display, _root, &event_mask, 1) == Success) { + if (x11display_cat.info()) { + x11display_cat.info() + << "Enabled raw mouse events using XInput2 extension\n"; + } + ++_num_raw_mouse_windows; + return true; + } + } + return false; +} + +/** + * Disables raw mouse mode for this display. + */ +void x11GraphicsPipe:: +disable_raw_mouse() { + if (--_num_raw_mouse_windows == 0) { + if (x11display_cat.is_debug()) { + x11display_cat.debug() + << "Disabling raw mouse events using XInput2 extension\n"; + } + + XIEventMask event_mask; + unsigned char mask[] = {0}; + + event_mask.deviceid = XIAllMasterDevices; + event_mask.mask_len = sizeof(mask); + event_mask.mask = mask; + + _XISelectEvents(_display, _root, &event_mask, 1); + } +} + /** * Returns an XRRScreenResources object, or null if RandR 1.2 is not supported. */ diff --git a/panda/src/x11display/x11GraphicsPipe.h b/panda/src/x11display/x11GraphicsPipe.h index e1abf80b00..260ec941d6 100644 --- a/panda/src/x11display/x11GraphicsPipe.h +++ b/panda/src/x11display/x11GraphicsPipe.h @@ -87,6 +87,45 @@ typedef struct _XRRCrtcInfo { typedef void (*pfn_XRRFreeScreenResources)(XRRScreenResources *resources); typedef void (*pfn_XRRFreeCrtcInfo)(XRRCrtcInfo *crtcInfo); +typedef struct { + int deviceid; + int mask_len; + unsigned char *mask; +} XIEventMask; + +typedef struct { + int mask_len; + unsigned char *mask; + double *values; +} XIValuatorState; + +typedef struct { + int type; + unsigned long serial; + Bool send_event; + X11_Display *display; + int extension; + int evtype; + Time time; + int deviceid; + int sourceid; + int detail; + int flags; + XIValuatorState valuators; + double *raw_values; +} XIRawEvent; + +#define XI_RawMotion 17 +#define XI_RawMotionMask (1 << XI_RawMotion) + +#define XISetMask(ptr, event) (((unsigned char*)(ptr))[(event)>>3] |= (1 << ((event) & 7))) +#define XIClearMask(ptr, event) (((unsigned char*)(ptr))[(event)>>3] &= ~(1 << ((event) & 7))) +#define XIMaskIsSet(ptr, event) (((unsigned char*)(ptr))[(event)>>3] & (1 << ((event) & 7))) +#define XIMaskLen(event) (((event) >> 3) + 1) + +#define XIAllDevices 0 +#define XIAllMasterDevices 1 + class FrameBufferProperties; /** @@ -106,8 +145,10 @@ public: INLINE X11_Cursor get_hidden_cursor(); INLINE bool supports_relative_mouse() const; - INLINE bool enable_relative_mouse(); - INLINE void disable_relative_mouse(); + INLINE bool enable_dga_mouse(); + INLINE void disable_dga_mouse(); + INLINE bool enable_raw_mouse(); + INLINE void disable_raw_mouse(); static INLINE int disable_x_error_messages(); static INLINE int enable_x_error_messages(); @@ -170,6 +211,8 @@ public: pfn_XRRConfigCurrentConfiguration _XRRConfigCurrentConfiguration; pfn_XRRSetScreenConfig _XRRSetScreenConfig; + int _xi_opcode; + protected: X11_Display *_display; int _screen; @@ -190,6 +233,11 @@ protected: pfn_XRRGetCrtcInfo _XRRGetCrtcInfo; pfn_XRRFreeCrtcInfo _XRRFreeCrtcInfo; + typedef Status (*pfn_XIQueryVersion)(X11_Display *, int*, int*); + typedef Status (*pfn_XISelectEvents)(X11_Display *, X11_Window, XIEventMask *, int); + pfn_XISelectEvents _XISelectEvents = nullptr; + int _num_raw_mouse_windows = 0; + private: void make_hidden_cursor(); void release_hidden_cursor(); diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index de58cd1028..6ba210bc1f 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -117,6 +117,7 @@ x11GraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _awaiting_configure = false; _dga_mouse_enabled = false; + _raw_mouse_enabled = false; _override_redirect = False; _wm_delete_window = x11_pipe->_wm_delete_window; @@ -154,8 +155,8 @@ get_pointer(int device) const { // We recheck this immediately to get the most up-to-date value, but we // won't bother waiting for the lock if we can't. - if (device == 0 && !_dga_mouse_enabled && result._in_window && - x11GraphicsPipe::_x_mutex.try_lock()) { + if (device == 0 && !_dga_mouse_enabled && !_raw_mouse_enabled && + result._in_window && x11GraphicsPipe::_x_mutex.try_lock()) { XEvent event; if (_xwindow != None && XQueryPointer(_display, _xwindow, &event.xbutton.root, @@ -355,7 +356,7 @@ process_events() { case ButtonPress: // This refers to the mouse buttons. button = get_mouse_button(event.xbutton); - if (!_dga_mouse_enabled) { + if (_properties.get_mouse_mode() != WindowProperties::M_relative) { _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); } _input->button_down(button); @@ -363,18 +364,49 @@ process_events() { case ButtonRelease: button = get_mouse_button(event.xbutton); - if (!_dga_mouse_enabled) { + if (_properties.get_mouse_mode() != WindowProperties::M_relative) { _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); } _input->button_up(button); break; case MotionNotify: - if (_dga_mouse_enabled) { - PointerData md = _input->get_pointer(); - _input->set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root); - } else { - _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y); + if (!_raw_mouse_enabled) { + if (_dga_mouse_enabled) { + PointerData md = _input->get_pointer(); + _input->set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root); + } else { + _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y); + } + } + break; + + case GenericEvent: + if (_raw_mouse_enabled) { + XGenericEventCookie *cookie = &event.xcookie; + XGetEventData(_display, cookie); + + x11GraphicsPipe *x11_pipe; + DCAST_INTO_V(x11_pipe, _pipe); + + if (cookie->evtype == XI_RawMotion && + cookie->extension == x11_pipe->_xi_opcode) { + const XIRawEvent *raw_event = (const XIRawEvent *)cookie->data; + const double *values = raw_event->raw_values; + + double x = 0, y = 0; + if (XIMaskIsSet(raw_event->valuators.mask, 0)) { + x = values[0]; + } + if (XIMaskIsSet(raw_event->valuators.mask, 1)) { + y = values[1]; + } + + PointerData md = _input->get_pointer(); + _input->set_pointer_in_window(md.get_x() + x, md.get_y() + y); + } + + XFreeEventData(_display, cookie); } break; @@ -392,7 +424,7 @@ process_events() { break; case EnterNotify: - if (_dga_mouse_enabled) { + if (_properties.get_mouse_mode() == WindowProperties::M_relative) { PointerData md = _input->get_pointer(); _input->set_pointer_in_window(md.get_x(), md.get_y()); } else { @@ -508,9 +540,8 @@ process_events() { changed_properties = true; } - if (properties.has_foreground() && ( - _properties.get_mouse_mode() == WindowProperties::M_confined || - _dga_mouse_enabled)) { + if (properties.has_foreground() && + (_properties.get_mouse_mode() != WindowProperties::M_absolute)) { x11GraphicsPipe *x11_pipe; DCAST_INTO_V(x11_pipe, _pipe); @@ -525,13 +556,13 @@ process_events() { XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, GrabModeAsync, _xwindow, cursor, CurrentTime); if (_dga_mouse_enabled) { - x11_pipe->enable_relative_mouse(); + x11_pipe->enable_dga_mouse(); } } else { // window is leaving the foreground, ungrab the pointer if (_dga_mouse_enabled) { - x11_pipe->disable_relative_mouse(); + x11_pipe->disable_dga_mouse(); } else if (_properties.get_mouse_mode() == WindowProperties::M_confined) { XUngrabPointer(_display, CurrentTime); } @@ -850,9 +881,13 @@ set_properties_now(WindowProperties &properties) { case WindowProperties::M_absolute: XUngrabPointer(_display, CurrentTime); if (_dga_mouse_enabled) { - x11_pipe->disable_relative_mouse(); + x11_pipe->disable_dga_mouse(); _dga_mouse_enabled = false; } + if (_raw_mouse_enabled) { + x11_pipe->disable_raw_mouse(); + _raw_mouse_enabled = false; + } _properties.set_mouse_mode(WindowProperties::M_absolute); properties.clear_mouse_mode(); break; @@ -871,13 +906,16 @@ set_properties_now(WindowProperties &properties) { GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) { x11display_cat.error() << "Failed to grab pointer!\n"; } else { - x11_pipe->enable_relative_mouse(); + if (x11_pipe->enable_dga_mouse()) { + _dga_mouse_enabled = true; + } else { + _raw_mouse_enabled = _raw_mouse_enabled || x11_pipe->enable_raw_mouse(); + } _properties.set_mouse_mode(WindowProperties::M_relative); properties.clear_mouse_mode(); - _dga_mouse_enabled = true; - // Get the real mouse position, so we can addsubtract our relative + // Get the real mouse position, so we can add/subtract our relative // coordinates later. XEvent event; XQueryPointer(_display, _xwindow, &event.xbutton.root, @@ -885,10 +923,6 @@ set_properties_now(WindowProperties &properties) { &event.xbutton.x, &event.xbutton.y, &event.xbutton.state); _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); } - } else { - x11display_cat.warning() - << "XF86DGA extension not available, cannot enable relative mouse mode\n"; - _dga_mouse_enabled = false; } } break; @@ -899,9 +933,13 @@ set_properties_now(WindowProperties &properties) { DCAST_INTO_V(x11_pipe, _pipe); if (_dga_mouse_enabled) { - x11_pipe->disable_relative_mouse(); + x11_pipe->disable_dga_mouse(); _dga_mouse_enabled = false; } + if (_raw_mouse_enabled) { + x11_pipe->disable_raw_mouse(); + _raw_mouse_enabled = false; + } X11_Cursor cursor = None; if (_properties.get_cursor_hidden()) { cursor = x11_pipe->get_hidden_cursor(); @@ -1436,7 +1474,7 @@ open_raw_mice() { */ void x11GraphicsWindow:: handle_keystroke(XKeyEvent &event) { - if (!_dga_mouse_enabled) { + if (_properties.get_mouse_mode() != WindowProperties::M_relative) { _input->set_pointer_in_window(event.x, event.y); } @@ -1471,7 +1509,7 @@ handle_keystroke(XKeyEvent &event) { */ void x11GraphicsWindow:: handle_keypress(XKeyEvent &event) { - if (!_dga_mouse_enabled) { + if (_properties.get_mouse_mode() != WindowProperties::M_relative) { _input->set_pointer_in_window(event.x, event.y); } @@ -1506,7 +1544,7 @@ handle_keypress(XKeyEvent &event) { */ void x11GraphicsWindow:: handle_keyrelease(XKeyEvent &event) { - if (!_dga_mouse_enabled) { + if (_properties.get_mouse_mode() != WindowProperties::M_relative) { _input->set_pointer_in_window(event.x, event.y); } @@ -2025,8 +2063,10 @@ Bool x11GraphicsWindow:: check_event(X11_Display *display, XEvent *event, char *arg) { const x11GraphicsWindow *self = (x11GraphicsWindow *)arg; - // We accept any event that is sent to our window. - return (event->xany.window == self->_xwindow); + // We accept any event that is sent to our window. However, we have to let + // raw mouse events through, since they're not associated with any window. + return (event->xany.window == self->_xwindow || + (event->type == GenericEvent && self->_raw_mouse_enabled)); } /** diff --git a/panda/src/x11display/x11GraphicsWindow.h b/panda/src/x11display/x11GraphicsWindow.h index f512b16a19..e3d4a86382 100644 --- a/panda/src/x11display/x11GraphicsWindow.h +++ b/panda/src/x11display/x11GraphicsWindow.h @@ -92,6 +92,7 @@ protected: long _event_mask; bool _awaiting_configure; bool _dga_mouse_enabled; + bool _raw_mouse_enabled; Bool _override_redirect; Atom _wm_delete_window;