From 4511241991aabcc353059ee50def928f15a65062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20J=2E=20Est=C3=A9banez?= Date: Wed, 29 Nov 2017 21:01:20 +0100 Subject: [PATCH 1/4] Implement multitouch on X11 --- platform/x11/detect.py | 9 +++ platform/x11/os_x11.cpp | 166 ++++++++++++++++++++++++++++++++++++++++ platform/x11/os_x11.h | 11 +++ 3 files changed, 186 insertions(+) diff --git a/platform/x11/detect.py b/platform/x11/detect.py index 3f91e78864..904fd862a8 100644 --- a/platform/x11/detect.py +++ b/platform/x11/detect.py @@ -59,6 +59,7 @@ def get_opts(): ('pulseaudio', 'Detect & Use pulseaudio', 'yes'), ('udev', 'Use udev for gamepad connection callbacks', 'no'), ('debug_release', 'Add debug symbols to release version', 'no'), + ('touch', 'Enable touch events', 'yes'), ] @@ -145,6 +146,14 @@ def configure(env): env.ParseConfig('pkg-config xcursor --cflags --libs') env.ParseConfig('pkg-config xrandr --cflags --libs') + if (env['touch'] == 'yes'): + 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']) + if (env['builtin_openssl'] == 'no'): env.ParseConfig('pkg-config openssl --cflags --libs') diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 527ad1ae64..8599232487 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -154,6 +154,50 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au } } +#ifdef TOUCH_ENABLED + if (!XQueryExtension(x11_display, "XInputExtension", &touch.opcode, &event_base, &error_base)) { + fprintf(stderr, "XInput extension not available"); + } 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) { + fprintf(stderr, "XInput 2.2 not available (server supports %d.%d)\n", 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) + 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; + printf("%d) %d %s\n", i, dev->attachment, dev->name); + break; + } + } + if (direct_touch) { + touch.devices.push_back(dev->deviceid); + fprintf(stderr, "Using touch device: %s\n", dev->name); + } + } + + XIFreeDeviceInfo(info); + + if (!touch.devices.size()) { + fprintf(stderr, "No suitable touch device found\n"); + } + } + } +#endif + xim = XOpenIM(x11_display, NULL, NULL, NULL); if (xim == NULL) { @@ -320,6 +364,32 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au 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)] = {}; + + touch.event_mask.deviceid = XIAllMasterDevices; + 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); + + 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); + } + } +#endif + XClassHint *classHint; /* set the titlebar name */ @@ -473,6 +543,10 @@ void OS_X11::finalize() { #ifdef JOYDEV_ENABLED memdelete(joystick); +#endif +#ifdef TOUCH_ENABLED + touch.devices.clear(); + touch.state.clear(); #endif memdelete(input); @@ -1294,6 +1368,73 @@ void OS_X11::process_xevents() { XEvent event; XNextEvent(x11_display, &event); +#ifdef TOUCH_ENABLED + if (XGetEventData(x11_display, &event.xcookie)) { + + if (event.xcookie.extension == touch.opcode) { + + InputEvent input_event; + input_event.ID = ++event_id; + input_event.device = 0; + + XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; + int index = event_data->detail; + Point2i pos = Point2i(event_data->event_x, event_data->event_y); + + switch (event_data->evtype) { + + case XI_TouchBegin: // Fall-through + XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch); + + case XI_TouchEnd: { + + bool is_begin = event_data->evtype == XI_TouchBegin; + + input_event.type = InputEvent::SCREEN_TOUCH; + input_event.screen_touch.index = index; + input_event.screen_touch.x = pos.x; + input_event.screen_touch.y = pos.y; + input_event.screen_touch.pressed = is_begin; + + if (is_begin) { + if (touch.state.has(index)) // Defensive + break; + touch.state[index] = pos; + input->parse_input_event(input_event); + } else { + if (!touch.state.has(index)) // Defensive + break; + touch.state.erase(index); + input->parse_input_event(input_event); + } + } break; + + case XI_TouchUpdate: { + + Map::Element *curr_pos_elem = touch.state.find(index); + if (!curr_pos_elem) // Defensive + break; + + if (curr_pos_elem->value() != pos) { + + input_event.type = InputEvent::SCREEN_DRAG; + input_event.screen_drag.index = index; + input_event.screen_drag.x = pos.x; + input_event.screen_drag.y = pos.y; + input_event.screen_drag.relative_x = pos.x - curr_pos_elem->value().x; + input_event.screen_drag.relative_y = pos.y - curr_pos_elem->value().y; + input->parse_input_event(input_event); + + curr_pos_elem->value() = pos; + } + } break; + } + } + + XFreeEventData(x11_display, &event.xcookie); + } +#endif + switch (event.type) { case Expose: Main::force_redraw(); @@ -1331,6 +1472,12 @@ void OS_X11::process_xevents() { ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime); } +#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); + } +#endif break; case FocusOut: @@ -1339,6 +1486,25 @@ void OS_X11::process_xevents() { //dear X11, I try, I really try, but you never work, you do whathever you want. XUngrabPointer(x11_display, CurrentTime); } +#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); + } + + // Release every pointer to avoid sticky points + for (Map::Element *E = touch.state.front(); E; E = E->next()) { + + InputEvent input_event; + input_event.type = InputEvent::SCREEN_TOUCH; + input_event.screen_touch.index = E->key(); + input_event.screen_touch.x = E->get().x; + input_event.screen_touch.y = E->get().y; + input_event.screen_touch.pressed = false; + input->parse_input_event(input_event); + } + touch.state.clear(); +#endif break; case ConfigureNotify: diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index 6c6492a9fb..8926e457c8 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -55,6 +55,9 @@ #include #include #include +#ifdef TOUCH_ENABLED +#include +#endif // Hints for X11 fullscreen typedef struct { @@ -122,6 +125,14 @@ class OS_X11 : public OS_Unix { uint64_t last_click_ms; unsigned int event_id; uint32_t last_button_state; +#ifdef TOUCH_ENABLED + struct { + int opcode; + Vector devices; + XIEventMask event_mask; + Map state; + } touch; +#endif PhysicsServer *physics_server; unsigned int get_mouse_button_state(unsigned int p_x11_state); From cb23cc1ca41d2533343b5080562d1a86f51754f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20J=2E=20Est=C3=A9banez?= Date: Thu, 30 Nov 2017 19:56:44 +0100 Subject: [PATCH 2/4] Improve/fix multitouch on Windows - Fix logic error. - Track touches to enable defensive handling and releasing on focus out. - Change comment-out by preprocessor `#if`. --- platform/windows/os_windows.cpp | 44 +++++++++++++++++++++++++++++++-- platform/windows/os_windows.h | 3 +++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index d10c470b70..a1624d2870 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -231,6 +231,18 @@ bool OS_Windows::can_draw() const { void OS_Windows::_touch_event(bool p_pressed, int p_x, int p_y, int idx) { +#if WINVER >= 0x0601 // for windows 7 + // Defensive + if (touch_state.has(idx) == p_pressed) + return; + + if (p_pressed) { + touch_state.insert(idx, Point2i(p_x, p_y)); + } else { + touch_state.erase(idx); + } +#endif + InputEvent event; event.type = InputEvent::SCREEN_TOUCH; event.ID = ++last_id; @@ -248,6 +260,18 @@ void OS_Windows::_touch_event(bool p_pressed, int p_x, int p_y, int idx) { void OS_Windows::_drag_event(int p_x, int p_y, int idx) { +#if WINVER >= 0x0601 // for windows 7 + Map::Element *curr = touch_state.find(idx); + // Defensive + if (!curr) + return; + + if (curr->get() == Point2i(p_x, p_y)) + return; + + curr->get() = Point2i(p_x, p_y); +#endif + InputEvent event; event.type = InputEvent::SCREEN_DRAG; event.ID = ++last_id; @@ -292,6 +316,17 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) return 0; // Return To The Message Loop } + case WM_KILLFOCUS: { + +#if WINVER >= 0x0601 // for windows 7 + // Release every touch to avoid sticky points + for (Map::Element *E = touch_state.front(); E; E = E->next()) { + _touch_event(false, E->get().x, E->get().y, E->key()); + } + touch_state.clear(); +#endif + } break; + case WM_PAINT: Main::force_redraw(); @@ -682,7 +717,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) _drag_event(ti.x / 100, ti.y / 100, ti.dwID); } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) { - _touch_event(ti.dwFlags & TOUCHEVENTF_DOWN != 0, ti.x / 100, ti.y / 100, ti.dwID); + _touch_event(ti.dwFlags & TOUCHEVENTF_DOWN, ti.x / 100, ti.y / 100, ti.dwID); }; } bHandled = TRUE; @@ -1080,7 +1115,9 @@ void OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int tme.dwHoverTime = HOVER_DEFAULT; TrackMouseEvent(&tme); - //RegisterTouchWindow(hWnd, 0); // Windows 7 +#if WINVER >= 0x0601 // for windows 7 + RegisterTouchWindow(hWnd, 0); // Windows 7 +#endif _ensure_data_dir(); @@ -1188,6 +1225,9 @@ void OS_Windows::finalize() { memdelete(joystick); memdelete(input); +#if WINVER >= 0x0601 // for windows 7 + touch_state.clear(); +#endif visual_server->finish(); memdelete(visual_server); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index f9090c7e30..cbb7ee066e 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -130,6 +130,9 @@ class OS_Windows : public OS { InputDefault *input; joystick_windows *joystick; +#if WINVER >= 0x0601 // for windows 7 + Map touch_state; +#endif #ifdef WASAPI_ENABLED AudioDriverWASAPI driver_wasapi; From a6cedd736b432af06a574ff4846a5187d2a49888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20J=2E=20Est=C3=A9banez?= Date: Thu, 30 Nov 2017 20:13:28 +0100 Subject: [PATCH 3/4] Add build param for targeted Windows version --- platform/windows/detect.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 219fd4412c..bd364a59da 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -176,6 +176,8 @@ def get_opts(): return [ ('mingw_prefix', 'Mingw Prefix', mingw32), ('mingw_prefix_64', 'Mingw Prefix 64 bits', mingw64), + # Targeted Windows version: Vista (and later) + ('target_win_version', 'Targeted Windows version, >= 0x0600 (Vista)', '0x0600'), ] @@ -210,16 +212,13 @@ def configure(env): env.Append(CPPPATH=['#platform/windows']) - # Targeted Windows version: Vista (and later) - winver = "0x0600" # Windows Vista is the minimum target for windows builds - env['is_mingw'] = False if (os.name == "nt" and os.getenv("VCINSTALLDIR")): # build using visual studio env['ENV']['TMP'] = os.environ['TMP'] env.Append(CPPPATH=['#platform/windows/include']) env.Append(LIBPATH=['#platform/windows/lib']) - env.Append(CCFLAGS=['/DWINVER=%s' % winver, '/D_WIN32_WINNT=%s' % winver]) + env.Append(CCFLAGS=['/DWINVER=%s' % env['target_win_version'], '/D_WIN32_WINNT=%s' % env['target_win_version']]) if (env["target"] == "release"): From 838fd94a70e697663999e06b653e73618ff41e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20J=2E=20Est=C3=A9banez?= Date: Fri, 1 Dec 2017 00:31:56 +0100 Subject: [PATCH 4/4] Remove dead code from Windows build script --- platform/windows/detect.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/platform/windows/detect.py b/platform/windows/detect.py index bd364a59da..3b922a5ffe 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -98,18 +98,8 @@ def is_active(): def get_name(): return "Windows" - if (os.getenv("MINGW32_PREFIX")): - mingw32=os.getenv("MINGW32_PREFIX") - mingw = mingw32 - if (os.getenv("MINGW64_PREFIX")): - mingw64=os.getenv("MINGW64_PREFIX") - return [ - ('mingw_prefix','Mingw Prefix',mingw32), - ('mingw_prefix_64','Mingw Prefix 64 bits',mingw64), - ] - def can_build(): if (os.name == "nt"):