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;