From 33f2e1d4b74005154996373cd0b37e57008c587d Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Sat, 9 May 2026 17:52:43 -0700 Subject: [PATCH] rawinput, touch: attempt to remove touchscreens injecting mouse events (#687) ## Link to GitHub Issue or related Pull Request, if one exists Fixes #562 ## Description of change ### rawinput touch fixes In rawinput touch handler, if the mouse events (`WM_LBUTTONDOWN`, `WM_MOUSEMOVE`, `WM_LBUTTONUP`) originated from a touch screen, drop them on the floor instead of treating them as touch events. This is an attempt to fix phantom touch issues we see with some screens. Fix another issue where, if mouse is clicked inside the window, and then dragged outside the window and button is released, touch point (id 0) continues to be held. ### wintouchemu fixes If we detected a touch input recently (500ms), ignore new mouse clicks. This isn't perfect, but hopefully it helps with filtering out mouse clicks originating from touch screens. Also, switch over to detecting the primary mouse button correctly, not just the left mouse button. ### notes Future investigation: are there really touchscreens that report a mouse HID capability & insert mouse events? We could have a way to filter them at rawinput level but it'll be ugly. ## Testing Tried jubeat, beatstream.. --- src/spice2x/hooks/graphics/graphics.cpp | 1 + src/spice2x/misc/wintouchemu.cpp | 18 +++- src/spice2x/touch/touch.cpp | 115 ++++++++++++++---------- 3 files changed, 83 insertions(+), 51 deletions(-) diff --git a/src/spice2x/hooks/graphics/graphics.cpp b/src/spice2x/hooks/graphics/graphics.cpp index f3e19e6..0cc203b 100644 --- a/src/spice2x/hooks/graphics/graphics.cpp +++ b/src/spice2x/hooks/graphics/graphics.cpp @@ -163,6 +163,7 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l if (wintouchemu::INJECT_MOUSE_AS_WM_TOUCH) { // drop mouse inputs since only wintouches should be used switch (uMsg) { + case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_MBUTTONDOWN: diff --git a/src/spice2x/misc/wintouchemu.cpp b/src/spice2x/misc/wintouchemu.cpp index 944584d..d916d56 100644 --- a/src/spice2x/misc/wintouchemu.cpp +++ b/src/spice2x/misc/wintouchemu.cpp @@ -38,6 +38,9 @@ namespace wintouchemu { bool INJECT_MOUSE_AS_WM_TOUCH = false; bool LOG_FPS = false; bool ADD_TOUCH_FLAG_PRIMARY = false; + + // state + double last_touch_event = 0.0; static inline bool is_emu_enabled() { return FORCE || !is_touch_available("wintouchemu::is_emu_enabled") || GRAPHICS_SHOW_CURSOR; @@ -248,7 +251,10 @@ namespace wintouchemu { touch_input->y -= SPICETOUCH_TOUCH_Y; } - // log_misc("wintouchemu", "mouse state ({}, {})", to_string(touch_input->x), to_string(touch_input->y)); + // log_misc( + // "wintouchemu", + // "mouse state ({}, {}) event={}", + // to_string(touch_input->x), to_string(touch_input->y), mouse_state.touch_event); auto valid = true; if (overlay::OVERLAY) { @@ -439,6 +445,8 @@ namespace wintouchemu { } } + const auto now = get_performance_milliseconds(); + // update touch events if (hWnd != nullptr) { @@ -458,6 +466,7 @@ namespace wintouchemu { // check if new events are available if (event_count > 0) { + last_touch_event = now; // send fake event to make the game update it's touch inputs auto wndProc = (WNDPROC) GetWindowLongPtr(hWnd, GWLP_WNDPROC); @@ -484,7 +493,12 @@ namespace wintouchemu { // value from GetTouchInputInfo or fail to read dwFlags for valid events, so it's not OK to // send empty events when the mouse button is not clicked/released if (hWnd != nullptr && USE_MOUSE) { - bool button_pressed = ((GetKeyState(VK_LBUTTON) & 0x100) != 0); + bool button_pressed = get_async_primary_mouse(); + + // if there was a touch event in the last 500 ms, don't insert new button presses + if (button_pressed && (now - last_touch_event) < 500) { + button_pressed = false; + } // figure out what kind of touch event to simulate if (button_pressed && !mouse_state.last_button_pressed) { diff --git a/src/spice2x/touch/touch.cpp b/src/spice2x/touch/touch.cpp index 43edd5f..18803dc 100644 --- a/src/spice2x/touch/touch.cpp +++ b/src/spice2x/touch/touch.cpp @@ -67,6 +67,8 @@ static const char *LOG_MODULE_NAME = "touch"; static TouchHandler *TOUCH_HANDLER = nullptr; +static bool is_mouse_message_from_touchscreen(); + TouchHandler::TouchHandler(std::string name) { log_info("touch", "Using touch handler: {}", name); } @@ -215,6 +217,30 @@ void update_card_button() { } } +static void release_all_mouse_touch_points() { + std::lock_guard lock_points(TOUCH_POINTS_M); + std::lock_guard lock_events(TOUCH_EVENTS_M); + + for (size_t x = 0; x < TOUCH_POINTS.size();) { + TouchPoint *tp = &TOUCH_POINTS[x]; + + if (tp->id == 0u) { + TouchEvent te { + .id = tp->id, + .x = tp->x, + .y = tp->y, + .type = TOUCH_UP, + .mouse = tp->mouse, + }; + add_touch_event(&te); + + TOUCH_POINTS.erase(TOUCH_POINTS.begin() + x); + } else { + x++; + } + } +} + static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { // check if touch was registered @@ -490,35 +516,22 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP // parse mouse messages switch (msg) { case WM_LBUTTONDOWN: { - // check if mouse is enabled if (SPICETOUCH_ENABLE_MOUSE) { + if (is_mouse_message_from_touchscreen()) { + return 0; + } + + // subscribe to mouse messages even when the cursor leaves the window + SetCapture(hWnd); + + // release all old events before inserting a new one + release_all_mouse_touch_points(); // lock touch points std::lock_guard lock_points(TOUCH_POINTS_M); std::lock_guard lock_events(TOUCH_EVENTS_M); - // remove all points with ID 0 - for (size_t x = 0; x < TOUCH_POINTS.size(); x++) { - TouchPoint *tp = &TOUCH_POINTS[x]; - - if (tp->id == 0u) { - - // generate touch up event - TouchEvent te { - .id = tp->id, - .x = tp->x, - .y = tp->y, - .type = TOUCH_UP, - .mouse = tp->mouse, - }; - add_touch_event(&te); - - // erase touch point - TOUCH_POINTS.erase(TOUCH_POINTS.begin() + x); - } - } - // create touch point TouchPoint tp { .id = 0, @@ -545,9 +558,11 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP break; } case WM_MOUSEMOVE: { - // check if mouse is enabled if (SPICETOUCH_ENABLE_MOUSE) { + if (is_mouse_message_from_touchscreen()) { + return 0; + } // lock touch points std::lock_guard lock_points(TOUCH_POINTS_M); @@ -582,40 +597,31 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP break; } case WM_LBUTTONUP: { - // check if mouse is enabled if (SPICETOUCH_ENABLE_MOUSE) { + if (is_mouse_message_from_touchscreen()) { + return 0; + } - // lock touch points - std::lock_guard lock_points(TOUCH_POINTS_M); - std::lock_guard lock_events(TOUCH_EVENTS_M); - - // remove all points with ID 0 - for (size_t x = 0; x < TOUCH_POINTS.size(); x++) { - TouchPoint *tp = &TOUCH_POINTS[x]; - - if (tp->id == 0u) { - - // generate touch up event - TouchEvent te { - .id = tp->id, - .x = tp->x, - .y = tp->y, - .type = TOUCH_UP, - .mouse = tp->mouse, - }; - add_touch_event(&te); - - // remove touch point - TOUCH_POINTS.erase(TOUCH_POINTS.begin() + x); - } + release_all_mouse_touch_points(); + if (GetCapture() == hWnd) { + ReleaseCapture(); } } break; } - default: + case WM_CAPTURECHANGED: + case WM_CANCELMODE: { + // to deal with window losing the capture after SetCapture + release_all_mouse_touch_points(); + if (msg == WM_CANCELMODE && GetCapture() == hWnd) { + ReleaseCapture(); + } + break; + } + default: // call original function if (SPICETOUCH_CALL_OLD_PROC && SPICETOUCH_OLD_PROC != nullptr) { return SPICETOUCH_OLD_PROC(hWnd, msg, wParam, lParam); @@ -663,6 +669,8 @@ void touch_attach_dx_hook() { // initialize touch handler touch_initialize(); + log_info("touch", "touch_attach_dx_hook: attaching SpiceTouchWndProc..."); + // add dx hook graphics_add_wnd_proc(SpiceTouchWndProc); @@ -681,6 +689,8 @@ void touch_create_wnd(HWND hWnd, bool overlay) { // initialize touch handler touch_initialize(); + log_info("touch", "touch_create_wnd: creating SPICETOUCH_TOUCH_THREAD..."); + // create thread SPICETOUCH_TOUCH_THREAD = new std::thread([hWnd, overlay]() { @@ -956,4 +966,11 @@ void update_spicetouch_window_dimensions(HWND hWnd) { SPICETOUCH_TOUCH_Y = topleft.y; SPICETOUCH_TOUCH_WIDTH = bottomright.x - topleft.x; SPICETOUCH_TOUCH_HEIGHT = bottomright.y - topleft.y; -} \ No newline at end of file +} + +// https://learn.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages +static bool is_mouse_message_from_touchscreen() { + constexpr ULONG_PTR MI_WP_SIGNATURE = 0xFF515700; + constexpr ULONG_PTR SIGNATURE_MASK = 0xFFFFFF00; + return (GetMessageExtraInfo() & SIGNATURE_MASK) == MI_WP_SIGNATURE; +}