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.
This commit is contained in:
rdb 2020-02-20 12:17:47 +01:00
parent fda898807a
commit d0337b8233
5 changed files with 217 additions and 37 deletions

View File

@ -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);

View File

@ -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.
*/

View File

@ -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();

View File

@ -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));
}
/**

View File

@ -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;