Use XInput2 RawMotion to generate MouseMotion events
The current system for capturing the mouse and generating motion events on X11 has issues with inaccurate and lopsided input. This is because both XQueryPointer and XWarpPointer work in terms of integer coordinates when the underlying X11 input driver may be tracking the mouse using subpixel coordinates. When warping the pointer, the fractional part of the pointer position is discarded. To work around this issue, the fix uses raw motion events from XInput 2. These events report relative motion and are not affected by pointer warping. Additionally, this means Godot is able to detect motion at a higher resolution under X11. Because this is raw mouse input, it is not affected by the user's pointer speed and acceleration settings. This is the same system as SDL2 uses for its relative motion. Multitouch input on X requires XInput 2.2. Raw motion events require XInput 2.0. Since 2.0 is old enough, this is now the minimum requirement to use Godot on X.
This commit is contained in:
parent
5f32fc8208
commit
cf124b1415
3 changed files with 282 additions and 130 deletions
|
@ -48,6 +48,11 @@ def can_build():
|
|||
print("xrender not found.. x11 disabled.")
|
||||
return False
|
||||
|
||||
x11_error = os.system("pkg-config xi --modversion > /dev/null ")
|
||||
if (x11_error):
|
||||
print("xi not found.. Aborting.")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_opts():
|
||||
|
@ -170,13 +175,9 @@ def configure(env):
|
|||
env.ParseConfig('pkg-config xinerama --cflags --libs')
|
||||
env.ParseConfig('pkg-config xrandr --cflags --libs')
|
||||
env.ParseConfig('pkg-config xrender --cflags --libs')
|
||||
env.ParseConfig('pkg-config xi --cflags --libs')
|
||||
|
||||
if (env['touch']):
|
||||
x11_error = os.system("pkg-config xi --modversion > /dev/null ")
|
||||
if (x11_error):
|
||||
print("xi not found.. cannot build with touch. Aborting.")
|
||||
sys.exit(255)
|
||||
env.ParseConfig('pkg-config xi --cflags --libs')
|
||||
env.Append(CPPFLAGS=['-DTOUCH_ENABLED'])
|
||||
|
||||
# FIXME: Check for existence of the libs before parsing their flags with pkg-config
|
||||
|
|
|
@ -77,6 +77,13 @@
|
|||
|
||||
#include <X11/XKBlib.h>
|
||||
|
||||
// 2.2 is the first release with multitouch
|
||||
#define XINPUT_CLIENT_VERSION_MAJOR 2
|
||||
#define XINPUT_CLIENT_VERSION_MINOR 2
|
||||
|
||||
static const double abs_resolution_mult = 10000.0;
|
||||
static const double abs_resolution_range_mult = 10.0;
|
||||
|
||||
void OS_X11::initialize_core() {
|
||||
|
||||
crash_handler.initialize();
|
||||
|
@ -170,48 +177,12 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef TOUCH_ENABLED
|
||||
if (!XQueryExtension(x11_display, "XInputExtension", &touch.opcode, &event_base, &error_base)) {
|
||||
print_verbose("XInput extension not available, touch support disabled.");
|
||||
} else {
|
||||
// 2.2 is the first release with multitouch
|
||||
int xi_major = 2;
|
||||
int xi_minor = 2;
|
||||
if (XIQueryVersion(x11_display, &xi_major, &xi_minor) != Success) {
|
||||
print_verbose(vformat("XInput 2.2 not available (server supports %d.%d), touch support disabled.", xi_major, xi_minor));
|
||||
touch.opcode = 0;
|
||||
} else {
|
||||
int dev_count;
|
||||
XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);
|
||||
|
||||
for (int i = 0; i < dev_count; i++) {
|
||||
XIDeviceInfo *dev = &info[i];
|
||||
if (!dev->enabled)
|
||||
continue;
|
||||
if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave))
|
||||
continue;
|
||||
|
||||
bool direct_touch = false;
|
||||
for (int j = 0; j < dev->num_classes; j++) {
|
||||
if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {
|
||||
direct_touch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (direct_touch) {
|
||||
touch.devices.push_back(dev->deviceid);
|
||||
print_verbose("XInput: Using touch device: " + String(dev->name));
|
||||
}
|
||||
}
|
||||
|
||||
XIFreeDeviceInfo(info);
|
||||
|
||||
if (!touch.devices.size()) {
|
||||
print_verbose("XInput: No touch devices found.");
|
||||
}
|
||||
}
|
||||
if (!refresh_device_info()) {
|
||||
OS::get_singleton()->alert("Your system does not support XInput 2.\n"
|
||||
"Please upgrade your distribution.",
|
||||
"Unable to initialize XInput");
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
#endif
|
||||
|
||||
xim = XOpenIM(x11_display, NULL, NULL, NULL);
|
||||
|
||||
|
@ -415,34 +386,42 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
|
|||
|
||||
XChangeWindowAttributes(x11_display, x11_window, CWEventMask, &new_attr);
|
||||
|
||||
static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
|
||||
static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {};
|
||||
|
||||
xi.all_event_mask.deviceid = XIAllDevices;
|
||||
xi.all_event_mask.mask_len = sizeof(all_mask_data);
|
||||
xi.all_event_mask.mask = all_mask_data;
|
||||
|
||||
xi.all_master_event_mask.deviceid = XIAllMasterDevices;
|
||||
xi.all_master_event_mask.mask_len = sizeof(all_master_mask_data);
|
||||
xi.all_master_event_mask.mask = all_master_mask_data;
|
||||
|
||||
XISetMask(xi.all_event_mask.mask, XI_HierarchyChanged);
|
||||
XISetMask(xi.all_master_event_mask.mask, XI_DeviceChanged);
|
||||
XISetMask(xi.all_master_event_mask.mask, XI_RawMotion);
|
||||
|
||||
#ifdef TOUCH_ENABLED
|
||||
if (touch.devices.size()) {
|
||||
|
||||
// Must be alive after this block
|
||||
static unsigned char mask_data[XIMaskLen(XI_LASTEVENT)] = {};
|
||||
|
||||
touch.event_mask.deviceid = XIAllDevices;
|
||||
touch.event_mask.mask_len = sizeof(mask_data);
|
||||
touch.event_mask.mask = mask_data;
|
||||
|
||||
XISetMask(touch.event_mask.mask, XI_TouchBegin);
|
||||
XISetMask(touch.event_mask.mask, XI_TouchUpdate);
|
||||
XISetMask(touch.event_mask.mask, XI_TouchEnd);
|
||||
XISetMask(touch.event_mask.mask, XI_TouchOwnership);
|
||||
|
||||
XISelectEvents(x11_display, x11_window, &touch.event_mask, 1);
|
||||
|
||||
// Disabled by now since grabbing also blocks mouse events
|
||||
// (they are received as extended events instead of standard events)
|
||||
/*XIClearMask(touch.event_mask.mask, XI_TouchOwnership);
|
||||
|
||||
// Grab touch devices to avoid OS gesture interference
|
||||
for (int i = 0; i < touch.devices.size(); ++i) {
|
||||
XIGrabDevice(x11_display, touch.devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &touch.event_mask);
|
||||
}*/
|
||||
if (xi.touch_devices.size()) {
|
||||
XISetMask(xi.all_event_mask.mask, XI_TouchBegin);
|
||||
XISetMask(xi.all_event_mask.mask, XI_TouchUpdate);
|
||||
XISetMask(xi.all_event_mask.mask, XI_TouchEnd);
|
||||
XISetMask(xi.all_event_mask.mask, XI_TouchOwnership);
|
||||
}
|
||||
#endif
|
||||
|
||||
XISelectEvents(x11_display, x11_window, &xi.all_event_mask, 1);
|
||||
XISelectEvents(x11_display, DefaultRootWindow(x11_display), &xi.all_master_event_mask, 1);
|
||||
|
||||
// Disabled by now since grabbing also blocks mouse events
|
||||
// (they are received as extended events instead of standard events)
|
||||
/*XIClearMask(xi.touch_event_mask.mask, XI_TouchOwnership);
|
||||
|
||||
// Grab touch devices to avoid OS gesture interference
|
||||
for (int i = 0; i < xi.touch_devices.size(); ++i) {
|
||||
XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);
|
||||
}*/
|
||||
|
||||
/* set the titlebar name */
|
||||
XStoreName(x11_display, x11_window, "Godot");
|
||||
|
||||
|
@ -592,6 +571,101 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
|
|||
return OK;
|
||||
}
|
||||
|
||||
bool OS_X11::refresh_device_info() {
|
||||
int event_base, error_base;
|
||||
|
||||
print_verbose("XInput: Refreshing devices.");
|
||||
|
||||
if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) {
|
||||
print_verbose("XInput extension not available. Please upgrade your distribution.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR;
|
||||
int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR;
|
||||
|
||||
if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) {
|
||||
print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query));
|
||||
xi.opcode = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) {
|
||||
print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.",
|
||||
XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query));
|
||||
}
|
||||
|
||||
xi.absolute_devices.clear();
|
||||
xi.touch_devices.clear();
|
||||
|
||||
int dev_count;
|
||||
XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);
|
||||
|
||||
for (int i = 0; i < dev_count; i++) {
|
||||
XIDeviceInfo *dev = &info[i];
|
||||
if (!dev->enabled)
|
||||
continue;
|
||||
if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave))
|
||||
continue;
|
||||
|
||||
bool direct_touch = false;
|
||||
bool absolute_mode = false;
|
||||
int resolution_x = 0;
|
||||
int resolution_y = 0;
|
||||
int range_min_x = 0;
|
||||
int range_min_y = 0;
|
||||
int range_max_x = 0;
|
||||
int range_max_y = 0;
|
||||
for (int j = 0; j < dev->num_classes; j++) {
|
||||
#ifdef TOUCH_ENABLED
|
||||
if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {
|
||||
direct_touch = true;
|
||||
}
|
||||
#endif
|
||||
if (dev->classes[j]->type == XIValuatorClass) {
|
||||
XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j];
|
||||
|
||||
if (class_info->number == 0 && class_info->mode == XIModeAbsolute) {
|
||||
resolution_x = class_info->resolution;
|
||||
range_min_x = class_info->min;
|
||||
range_max_x = class_info->max;
|
||||
absolute_mode = true;
|
||||
} else if (class_info->number == 1 && class_info->mode == XIModeAbsolute) {
|
||||
resolution_y = class_info->resolution;
|
||||
range_min_y = class_info->min;
|
||||
range_max_y = class_info->max;
|
||||
absolute_mode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (direct_touch) {
|
||||
xi.touch_devices.push_back(dev->deviceid);
|
||||
print_verbose("XInput: Using touch device: " + String(dev->name));
|
||||
}
|
||||
if (absolute_mode) {
|
||||
// If no resolution was reported, use the min/max ranges.
|
||||
if (resolution_x <= 0) {
|
||||
resolution_x = (range_max_x - range_min_x) * abs_resolution_range_mult;
|
||||
}
|
||||
if (resolution_y <= 0) {
|
||||
resolution_y = (range_max_y - range_min_y) * abs_resolution_range_mult;
|
||||
}
|
||||
|
||||
xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y);
|
||||
print_verbose("XInput: Absolute pointing device: " + String(dev->name));
|
||||
}
|
||||
}
|
||||
|
||||
XIFreeDeviceInfo(info);
|
||||
#ifdef TOUCH_ENABLED
|
||||
if (!xi.touch_devices.size()) {
|
||||
print_verbose("XInput: No touch devices found.");
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OS_X11::xim_destroy_callback(::XIM im, ::XPointer client_data,
|
||||
::XPointer call_data) {
|
||||
|
||||
|
@ -664,10 +738,10 @@ void OS_X11::finalize() {
|
|||
#ifdef JOYDEV_ENABLED
|
||||
memdelete(joypad);
|
||||
#endif
|
||||
#ifdef TOUCH_ENABLED
|
||||
touch.devices.clear();
|
||||
touch.state.clear();
|
||||
#endif
|
||||
|
||||
xi.touch_devices.clear();
|
||||
xi.state.clear();
|
||||
|
||||
memdelete(input);
|
||||
|
||||
visual_server->finish();
|
||||
|
@ -727,21 +801,8 @@ void OS_X11::set_mouse_mode(MouseMode p_mode) {
|
|||
|
||||
if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) {
|
||||
|
||||
while (true) {
|
||||
//flush pending motion events
|
||||
|
||||
if (XPending(x11_display) > 0) {
|
||||
XEvent event;
|
||||
XPeekEvent(x11_display, &event);
|
||||
if (event.type == MotionNotify) {
|
||||
XNextEvent(x11_display, &event);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//flush pending motion events
|
||||
flush_mouse_motion();
|
||||
|
||||
if (XGrabPointer(
|
||||
x11_display, x11_window, True,
|
||||
|
@ -782,6 +843,32 @@ void OS_X11::warp_mouse_position(const Point2 &p_to) {
|
|||
}
|
||||
}
|
||||
|
||||
void OS_X11::flush_mouse_motion() {
|
||||
while (true) {
|
||||
if (XPending(x11_display) > 0) {
|
||||
XEvent event;
|
||||
XPeekEvent(x11_display, &event);
|
||||
|
||||
if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
|
||||
XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
|
||||
|
||||
if (event_data->evtype == XI_RawMotion) {
|
||||
XNextEvent(x11_display, &event);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
xi.relative_motion.x = 0;
|
||||
xi.relative_motion.y = 0;
|
||||
}
|
||||
|
||||
OS::MouseMode OS_X11::get_mouse_mode() const {
|
||||
return mouse_mode;
|
||||
}
|
||||
|
@ -1778,17 +1865,61 @@ void OS_X11::process_xevents() {
|
|||
continue;
|
||||
}
|
||||
|
||||
#ifdef TOUCH_ENABLED
|
||||
if (XGetEventData(x11_display, &event.xcookie)) {
|
||||
|
||||
if (event.xcookie.type == GenericEvent && event.xcookie.extension == touch.opcode) {
|
||||
if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
|
||||
|
||||
XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
|
||||
int index = event_data->detail;
|
||||
Vector2 pos = Vector2(event_data->event_x, event_data->event_y);
|
||||
|
||||
switch (event_data->evtype) {
|
||||
case XI_HierarchyChanged:
|
||||
case XI_DeviceChanged: {
|
||||
refresh_device_info();
|
||||
} break;
|
||||
case XI_RawMotion: {
|
||||
XIRawEvent *raw_event = (XIRawEvent *)event_data;
|
||||
int device_id = raw_event->deviceid;
|
||||
|
||||
// Determine the axis used (called valuators in XInput for some forsaken reason)
|
||||
// Mask is a bitmask indicating which axes are involved.
|
||||
// We are interested in the values of axes 0 and 1.
|
||||
if (raw_event->valuators.mask_len <= 0 || !XIMaskIsSet(raw_event->valuators.mask, 0) || !XIMaskIsSet(raw_event->valuators.mask, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
double rel_x = raw_event->raw_values[0];
|
||||
double rel_y = raw_event->raw_values[1];
|
||||
|
||||
// https://bugs.freedesktop.org/show_bug.cgi?id=71609
|
||||
// http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html
|
||||
if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) {
|
||||
break; // Flush duplicate to avoid overly fast motion
|
||||
}
|
||||
|
||||
xi.old_raw_pos.x = xi.raw_pos.x;
|
||||
xi.old_raw_pos.y = xi.raw_pos.y;
|
||||
xi.raw_pos.x = rel_x;
|
||||
xi.raw_pos.y = rel_y;
|
||||
|
||||
Map<int, Vector2>::Element *abs_info = xi.absolute_devices.find(device_id);
|
||||
|
||||
if (abs_info) {
|
||||
// Absolute mode device
|
||||
Vector2 mult = abs_info->value();
|
||||
|
||||
xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x;
|
||||
xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y;
|
||||
} else {
|
||||
// Relative mode device
|
||||
xi.relative_motion.x = xi.raw_pos.x;
|
||||
xi.relative_motion.y = xi.raw_pos.y;
|
||||
}
|
||||
|
||||
xi.last_relative_time = raw_event->time;
|
||||
} break;
|
||||
#ifdef TOUCH_ENABLED
|
||||
case XI_TouchBegin: // Fall-through
|
||||
// Disabled hand-in-hand with the grabbing
|
||||
//XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch);
|
||||
|
@ -1804,26 +1935,26 @@ void OS_X11::process_xevents() {
|
|||
st->set_pressed(is_begin);
|
||||
|
||||
if (is_begin) {
|
||||
if (touch.state.has(index)) // Defensive
|
||||
if (xi.state.has(index)) // Defensive
|
||||
break;
|
||||
touch.state[index] = pos;
|
||||
if (touch.state.size() == 1) {
|
||||
xi.state[index] = pos;
|
||||
if (xi.state.size() == 1) {
|
||||
// X11 may send a motion event when a touch gesture begins, that would result
|
||||
// in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out
|
||||
touch.mouse_pos_to_filter = pos;
|
||||
xi.mouse_pos_to_filter = pos;
|
||||
}
|
||||
input->parse_input_event(st);
|
||||
} else {
|
||||
if (!touch.state.has(index)) // Defensive
|
||||
if (!xi.state.has(index)) // Defensive
|
||||
break;
|
||||
touch.state.erase(index);
|
||||
xi.state.erase(index);
|
||||
input->parse_input_event(st);
|
||||
}
|
||||
} break;
|
||||
|
||||
case XI_TouchUpdate: {
|
||||
|
||||
Map<int, Vector2>::Element *curr_pos_elem = touch.state.find(index);
|
||||
Map<int, Vector2>::Element *curr_pos_elem = xi.state.find(index);
|
||||
if (!curr_pos_elem) { // Defensive
|
||||
break;
|
||||
}
|
||||
|
@ -1840,11 +1971,11 @@ void OS_X11::process_xevents() {
|
|||
curr_pos_elem->value() = pos;
|
||||
}
|
||||
} break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
XFreeEventData(x11_display, &event.xcookie);
|
||||
#endif
|
||||
|
||||
switch (event.type) {
|
||||
case Expose:
|
||||
|
@ -1890,8 +2021,8 @@ void OS_X11::process_xevents() {
|
|||
}
|
||||
#ifdef TOUCH_ENABLED
|
||||
// Grab touch devices to avoid OS gesture interference
|
||||
/*for (int i = 0; i < touch.devices.size(); ++i) {
|
||||
XIGrabDevice(x11_display, touch.devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &touch.event_mask);
|
||||
/*for (int i = 0; i < xi.touch_devices.size(); ++i) {
|
||||
XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);
|
||||
}*/
|
||||
#endif
|
||||
if (xic) {
|
||||
|
@ -1912,12 +2043,12 @@ void OS_X11::process_xevents() {
|
|||
}
|
||||
#ifdef TOUCH_ENABLED
|
||||
// Ungrab touch devices so input works as usual while we are unfocused
|
||||
/*for (int i = 0; i < touch.devices.size(); ++i) {
|
||||
XIUngrabDevice(x11_display, touch.devices[i], CurrentTime);
|
||||
/*for (int i = 0; i < xi.touch_devices.size(); ++i) {
|
||||
XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime);
|
||||
}*/
|
||||
|
||||
// Release every pointer to avoid sticky points
|
||||
for (Map<int, Vector2>::Element *E = touch.state.front(); E; E = E->next()) {
|
||||
for (Map<int, Vector2>::Element *E = xi.state.front(); E; E = E->next()) {
|
||||
|
||||
Ref<InputEventScreenTouch> st;
|
||||
st.instance();
|
||||
|
@ -1925,7 +2056,7 @@ void OS_X11::process_xevents() {
|
|||
st->set_position(E->get());
|
||||
input->parse_input_event(st);
|
||||
}
|
||||
touch.state.clear();
|
||||
xi.state.clear();
|
||||
#endif
|
||||
if (xic) {
|
||||
XUnsetICFocus(xic);
|
||||
|
@ -2018,34 +2149,27 @@ void OS_X11::process_xevents() {
|
|||
// Motion is also simple.
|
||||
// A little hack is in order
|
||||
// to be able to send relative motion events.
|
||||
Point2i pos(event.xmotion.x, event.xmotion.y);
|
||||
Point2 pos(event.xmotion.x, event.xmotion.y);
|
||||
|
||||
#ifdef TOUCH_ENABLED
|
||||
// Avoidance of spurious mouse motion (see handling of touch)
|
||||
bool filter = false;
|
||||
// Adding some tolerance to match better Point2i to Vector2
|
||||
if (touch.state.size() && Vector2(pos).distance_squared_to(touch.mouse_pos_to_filter) < 2) {
|
||||
if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) {
|
||||
filter = true;
|
||||
}
|
||||
// Invalidate to avoid filtering a possible legitimate similar event coming later
|
||||
touch.mouse_pos_to_filter = Vector2(1e10, 1e10);
|
||||
xi.mouse_pos_to_filter = Vector2(1e10, 1e10);
|
||||
if (filter) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mouse_mode == MOUSE_MODE_CAPTURED) {
|
||||
|
||||
if (pos == Point2i(current_videomode.width / 2, current_videomode.height / 2)) {
|
||||
//this sucks, it's a hack, etc and is a little inaccurate, etc.
|
||||
//but nothing I can do, X11 sucks.
|
||||
|
||||
center = pos;
|
||||
if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
Point2i new_center = pos;
|
||||
pos = last_mouse_pos + (pos - center);
|
||||
pos = last_mouse_pos + xi.relative_motion;
|
||||
center = new_center;
|
||||
do_mouse_warp = window_has_focus; // warp the cursor if we're focused in
|
||||
}
|
||||
|
@ -2056,7 +2180,24 @@ void OS_X11::process_xevents() {
|
|||
last_mouse_pos_valid = true;
|
||||
}
|
||||
|
||||
Point2i rel = pos - last_mouse_pos;
|
||||
// Hackish but relative mouse motion is already handled in the RawMotion event.
|
||||
// RawMotion does not provide the absolute mouse position (whereas MotionNotify does).
|
||||
// Therefore, RawMotion cannot be the authority on absolute mouse position.
|
||||
// RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion.
|
||||
// Therefore, MotionNotify cannot be the authority on relative mouse motion.
|
||||
// This means we need to take a combined approach...
|
||||
Point2 rel;
|
||||
|
||||
// Only use raw input if in capture mode. Otherwise use the classic behavior.
|
||||
if (mouse_mode == MOUSE_MODE_CAPTURED) {
|
||||
rel = xi.relative_motion;
|
||||
} else {
|
||||
rel = pos - last_mouse_pos;
|
||||
}
|
||||
|
||||
// Reset to prevent lingering motion
|
||||
xi.relative_motion.x = 0;
|
||||
xi.relative_motion.y = 0;
|
||||
|
||||
if (mouse_mode == MOUSE_MODE_CAPTURED) {
|
||||
pos = Point2i(current_videomode.width / 2, current_videomode.height / 2);
|
||||
|
@ -2065,12 +2206,16 @@ void OS_X11::process_xevents() {
|
|||
Ref<InputEventMouseMotion> mm;
|
||||
mm.instance();
|
||||
|
||||
// Make the absolute position integral so it doesn't look _too_ weird :)
|
||||
Point2i posi(pos);
|
||||
|
||||
get_key_modifier_state(event.xmotion.state, mm);
|
||||
mm->set_button_mask(get_mouse_button_state());
|
||||
mm->set_position(pos);
|
||||
mm->set_global_position(pos);
|
||||
input->set_mouse_position(pos);
|
||||
mm->set_position(posi);
|
||||
mm->set_global_position(posi);
|
||||
input->set_mouse_position(posi);
|
||||
mm->set_speed(input->get_last_mouse_speed());
|
||||
|
||||
mm->set_relative(rel);
|
||||
|
||||
last_mouse_pos = pos;
|
||||
|
|
|
@ -48,11 +48,9 @@
|
|||
|
||||
#include <X11/Xcursor/Xcursor.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#include <X11/keysym.h>
|
||||
#ifdef TOUCH_ENABLED
|
||||
#include <X11/extensions/XInput2.h>
|
||||
#endif
|
||||
|
||||
// Hints for X11 fullscreen
|
||||
typedef struct {
|
||||
|
@ -121,24 +119,32 @@ class OS_X11 : public OS_Unix {
|
|||
bool im_active;
|
||||
Vector2 im_position;
|
||||
|
||||
Point2i last_mouse_pos;
|
||||
Point2 last_mouse_pos;
|
||||
bool last_mouse_pos_valid;
|
||||
Point2i last_click_pos;
|
||||
uint64_t last_click_ms;
|
||||
int last_click_button_index;
|
||||
uint32_t last_button_state;
|
||||
#ifdef TOUCH_ENABLED
|
||||
|
||||
struct {
|
||||
int opcode;
|
||||
Vector<int> devices;
|
||||
XIEventMask event_mask;
|
||||
Vector<int> touch_devices;
|
||||
Map<int, Vector2> absolute_devices;
|
||||
XIEventMask all_event_mask;
|
||||
XIEventMask all_master_event_mask;
|
||||
Map<int, Vector2> state;
|
||||
Vector2 mouse_pos_to_filter;
|
||||
} touch;
|
||||
#endif
|
||||
Vector2 relative_motion;
|
||||
Vector2 raw_pos;
|
||||
Vector2 old_raw_pos;
|
||||
::Time last_relative_time;
|
||||
} xi;
|
||||
|
||||
bool refresh_device_info();
|
||||
|
||||
unsigned int get_mouse_button_state(unsigned int p_x11_button, int p_x11_type);
|
||||
void get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state);
|
||||
void flush_mouse_motion();
|
||||
|
||||
MouseMode mouse_mode;
|
||||
Point2i center;
|
||||
|
|
Loading…
Reference in a new issue