diff options
-rw-r--r-- | platform/x11/detect.py | 11 | ||||
-rw-r--r-- | platform/x11/os_x11.cpp | 371 | ||||
-rw-r--r-- | platform/x11/os_x11.h | 24 |
3 files changed, 279 insertions, 127 deletions
diff --git a/platform/x11/detect.py b/platform/x11/detect.py index 524c8448bc..9b6fb2f478 100644 --- a/platform/x11/detect.py +++ b/platform/x11/detect.py @@ -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 diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 1a6fcc933a..7b30e7a064 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -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); -#ifdef TOUCH_ENABLED - if (touch.devices.size()) { - - // Must be alive after this block - static unsigned char mask_data[XIMaskLen(XI_LASTEVENT)] = {}; + static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; + static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; - touch.event_mask.deviceid = XIAllDevices; - touch.event_mask.mask_len = sizeof(mask_data); - touch.event_mask.mask = mask_data; + xi.all_event_mask.deviceid = XIAllDevices; + xi.all_event_mask.mask_len = sizeof(all_mask_data); + xi.all_event_mask.mask = all_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); + 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; - XISelectEvents(x11_display, x11_window, &touch.event_mask, 1); + 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); - // 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); - }*/ +#ifdef TOUCH_ENABLED + 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; diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index 68a1e51376..4e73c5beec 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -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; |