From f52eaea76cacaa0cab0894c7eada478f1eea603c Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Sat, 11 Jul 2026 00:24:36 -0700 Subject: [PATCH] touch: fix indicators showing in touch overlay, disable additional gestures (#791) --- src/spice2x/CMakeLists.txt | 2 +- src/spice2x/hooks/graphics/graphics.cpp | 6 +- src/spice2x/touch/touch.cpp | 4 + src/spice2x/touch/touch_gestures.cpp | 168 ++++++++++++++++++++++++ src/spice2x/touch/touch_gestures.h | 8 ++ src/spice2x/touch/touch_indicators.cpp | 57 -------- src/spice2x/touch/touch_indicators.h | 5 - src/spice2x/touch/win8.cpp | 8 +- 8 files changed, 191 insertions(+), 67 deletions(-) create mode 100644 src/spice2x/touch/touch_gestures.cpp create mode 100644 src/spice2x/touch/touch_gestures.h delete mode 100644 src/spice2x/touch/touch_indicators.cpp delete mode 100644 src/spice2x/touch/touch_indicators.h diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index 9bd55d8..ad34693 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -645,7 +645,7 @@ set(SOURCE_FILES ${SOURCE_FILES} # touch touch/touch.cpp - touch/touch_indicators.cpp + touch/touch_gestures.cpp touch/win7.cpp touch/win8.cpp diff --git a/src/spice2x/hooks/graphics/graphics.cpp b/src/spice2x/hooks/graphics/graphics.cpp index 99473fe..5363a45 100644 --- a/src/spice2x/hooks/graphics/graphics.cpp +++ b/src/spice2x/hooks/graphics/graphics.cpp @@ -23,7 +23,7 @@ #include "launcher/shutdown.h" #include "overlay/overlay.h" #include "touch/touch.h" -#include "touch/touch_indicators.h" +#include "touch/touch_gestures.h" #include "util/detour.h" #include "util/logging.h" #include "util/fileutils.h" @@ -627,7 +627,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC } } - disable_touch_indicators(result); + disable_touch_gestures(result); log_misc( "graphics", "CreateWindowExA returned {}, {}", @@ -722,7 +722,7 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP fmt::ptr(result), lpWindowName ? ws2s(lpWindowName) : "(null)"); - disable_touch_indicators(result); + disable_touch_gestures(result); return result; } diff --git a/src/spice2x/touch/touch.cpp b/src/spice2x/touch/touch.cpp index 18803dc..890bd65 100644 --- a/src/spice2x/touch/touch.cpp +++ b/src/spice2x/touch/touch.cpp @@ -22,6 +22,7 @@ #include "util/utils.h" #include "handler.h" +#include "touch_gestures.h" #include "win7.h" #include "win8.h" @@ -751,6 +752,9 @@ void touch_create_wnd(HWND hWnd, bool overlay) { ShowWindow(touch_window, SW_SHOWNOACTIVATE); UpdateWindow(touch_window); + // disable the OS touch contact visualization for our own touch window + disable_touch_gestures(touch_window); + // register touch_register_window(touch_window); diff --git a/src/spice2x/touch/touch_gestures.cpp b/src/spice2x/touch/touch_gestures.cpp new file mode 100644 index 0000000..84b7042 --- /dev/null +++ b/src/spice2x/touch/touch_gestures.cpp @@ -0,0 +1,168 @@ + +// set version to Windows 8 to enable Windows 8 touch functions +#define _WIN32_WINNT 0x0602 + +#include + +#include + +#include "touch_gestures.h" +#include "util/libutils.h" +#include "util/logging.h" + +// tablet/pen service flags (MicrosoftTabletPenServiceProperty atom) +// these are not present in the mingw headers +#ifndef TABLET_DISABLE_PRESSANDHOLD +#define TABLET_DISABLE_PRESSANDHOLD 0x00000001 +#define TABLET_DISABLE_PENTAPFEEDBACK 0x00000008 +#define TABLET_DISABLE_PENBARRELFEEDBACK 0x00000010 +#define TABLET_DISABLE_TOUCHUIFORCEON 0x00000100 +#define TABLET_DISABLE_TOUCHUIFORCEOFF 0x00000200 +#define TABLET_DISABLE_TOUCHSWITCH 0x00008000 +#define TABLET_DISABLE_FLICKS 0x00010000 +#define TABLET_DISABLE_SMOOTHSCROLLING 0x00080000 +#define TABLET_DISABLE_FLICKFALLBACKKEYS 0x00100000 +#endif + +static const char TABLET_ATOM_NAME[] = "MicrosoftTabletPenServiceProperty"; + +// PKEY_EdgeGesture_DisableTouchWhenFullscreen format GUID +// (not defined in the mingw headers); defined inline to avoid an initguid.h +// symbol clash with touch/win8.cpp which declares the same name +// {32CE38B2-2C9A-41B1-9BC5-B3784394AA44} +static const GUID EDGEGESTURE_DISABLE_FMT = + { 0x32CE38B2, 0x2C9A, 0x41B1, { 0x9B, 0xC5, 0xB3, 0x78, 0x43, 0x94, 0xAA, 0x44 } }; + +static HINSTANCE USER32_INSTANCE = nullptr; +typedef BOOL (WINAPI *SetWindowFeedbackSetting_t)(HWND, FEEDBACK_TYPE, DWORD, UINT32, const VOID *); +static SetWindowFeedbackSetting_t pSetWindowFeedbackSetting = nullptr; + +static HINSTANCE SHELL32_INSTANCE = nullptr; +typedef HRESULT (WINAPI *SHGetPropertyStoreForWindow_t)(HWND, REFIID, void **); +static SHGetPropertyStoreForWindow_t pSHGetPropertyStoreForWindow = nullptr; + +static std::once_flag INIT_FLAG; + +// resolve the libraries and entry points once; disable_touch_gestures may be +// called concurrently from the CreateWindowEx hooks and the touch thread +static void init_procs() { + std::call_once(INIT_FLAG, []() { + USER32_INSTANCE = libutils::load_library("user32.dll"); + if (USER32_INSTANCE != nullptr) { + pSetWindowFeedbackSetting = libutils::try_proc( + USER32_INSTANCE, "SetWindowFeedbackSetting"); + } + + SHELL32_INSTANCE = libutils::try_library("shell32.dll"); + if (SHELL32_INSTANCE != nullptr) { + pSHGetPropertyStoreForWindow = libutils::try_proc( + SHELL32_INSTANCE, "SHGetPropertyStoreForWindow"); + } + }); +} + +static void disable_feedback_visuals(HWND hwnd) { + + if (pSetWindowFeedbackSetting == nullptr) { + return; + } + + BOOL enabled = FALSE; + pSetWindowFeedbackSetting( + hwnd, + FEEDBACK_TOUCH_CONTACTVISUALIZATION, + 0, sizeof(enabled), &enabled); + pSetWindowFeedbackSetting( + hwnd, + FEEDBACK_TOUCH_TAP, + 0, sizeof(enabled), &enabled); + pSetWindowFeedbackSetting( + hwnd, + FEEDBACK_TOUCH_DOUBLETAP, + 0, sizeof(enabled), &enabled); + pSetWindowFeedbackSetting( + hwnd, + FEEDBACK_TOUCH_PRESSANDHOLD, + 0, sizeof(enabled), &enabled); + pSetWindowFeedbackSetting( + hwnd, + FEEDBACK_TOUCH_RIGHTTAP, + 0, sizeof(enabled), &enabled); + pSetWindowFeedbackSetting( + hwnd, + FEEDBACK_GESTURE_PRESSANDTAP, + 0, sizeof(enabled), &enabled); +} + +static void disable_gesture_behaviors(HWND hwnd) { + + // the tablet/pen service reads this window property to decide which + // touch/pen gestures to suppress for the window. this covers: + // - press-and-hold (touch right-click / long-press ring) + // - pen tap/barrel feedback + // - flicks (edge/directional flick navigation gestures) + // - the touch keyboard invocation UI + // - smooth scrolling / flick fallback keys + DWORD tablet_flags = TABLET_DISABLE_PRESSANDHOLD | + TABLET_DISABLE_PENTAPFEEDBACK | + TABLET_DISABLE_PENBARRELFEEDBACK | + TABLET_DISABLE_FLICKS | + TABLET_DISABLE_TOUCHUIFORCEOFF | + TABLET_DISABLE_TOUCHSWITCH | + TABLET_DISABLE_SMOOTHSCROLLING | + TABLET_DISABLE_FLICKFALLBACKKEYS; + + ATOM atom_id = GlobalAddAtomA(TABLET_ATOM_NAME); + if (atom_id > 0) { + SetPropA(hwnd, TABLET_ATOM_NAME, (HANDLE) ((ULONG_PTR) tablet_flags)); + } +} + +static void disable_edge_gestures(HWND hwnd) { + + // suppress the touch edge swipes (charms/back/app bar) for the window while + // it is fullscreen. this is normally done by the touch handler's + // window_register(), but that is not called for every handler (e.g. the + // rawinput handler is a no-op), so apply it here as well. + if (pSHGetPropertyStoreForWindow == nullptr) { + return; + } + + IPropertyStore *ps = nullptr; + HRESULT hr = pSHGetPropertyStoreForWindow(hwnd, IID_IPropertyStore, (void **) &ps); + if (SUCCEEDED(hr) && ps != nullptr) { + PROPERTYKEY key = { EDGEGESTURE_DISABLE_FMT, 2 }; + + PROPVARIANT var {}; + var.vt = VT_BOOL; + var.boolVal = VARIANT_TRUE; + + hr = ps->SetValue(key, var); + ps->Release(); + + if (FAILED(hr)) { + log_warning("touch_gestures", + "failed to disable edge gestures on HWND={}", fmt::ptr(hwnd)); + } + } +} + +void disable_touch_gestures(HWND hwnd) { + + init_procs(); + if (USER32_INSTANCE == nullptr) { + return; + } + + log_misc("touch_gestures", + "disable visual feedback and gestures for touch events for HWND={}", fmt::ptr(hwnd)); + + // disable the visual feedback (contact circles, tap/double-tap stars, etc.) + disable_feedback_visuals(hwnd); + + // disable the actual gesture behaviors (press-and-hold, flicks, etc.) + disable_gesture_behaviors(hwnd); + + // disable the fullscreen touch edge swipes + disable_edge_gestures(hwnd); +} diff --git a/src/spice2x/touch/touch_gestures.h b/src/spice2x/touch/touch_gestures.h new file mode 100644 index 0000000..13789ce --- /dev/null +++ b/src/spice2x/touch/touch_gestures.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +// disable the OS touch UX for a window: visual feedback (contact circles, +// tap/press-and-hold indicators) and gesture behaviors (press-and-hold +// right-click, flicks, etc.) +void disable_touch_gestures(HWND hwnd); diff --git a/src/spice2x/touch/touch_indicators.cpp b/src/spice2x/touch/touch_indicators.cpp deleted file mode 100644 index 44e84df..0000000 --- a/src/spice2x/touch/touch_indicators.cpp +++ /dev/null @@ -1,57 +0,0 @@ - -// set version to Windows 8 to enable Windows 8 touch functions -#define _WIN32_WINNT 0x0602 - -#include "touch_indicators.h" -#include "util/libutils.h" -#include "util/logging.h" - -static HINSTANCE USER32_INSTANCE = nullptr; -typedef BOOL (WINAPI *SetWindowFeedbackSetting_t)(HWND, FEEDBACK_TYPE, DWORD, UINT32, const VOID *); -static SetWindowFeedbackSetting_t pSetWindowFeedbackSetting = nullptr; - -void disable_touch_indicators(HWND hwnd) { - - if (USER32_INSTANCE == nullptr) { - USER32_INSTANCE = libutils::load_library("user32.dll"); - } - if (USER32_INSTANCE == nullptr) { - return; - } - - if (pSetWindowFeedbackSetting == nullptr) { - pSetWindowFeedbackSetting = libutils::try_proc( - USER32_INSTANCE, "SetWindowFeedbackSetting"); - } - if (pSetWindowFeedbackSetting == nullptr) { - return; - } - - log_info("touch_indicators", "disable visual feedback for touch events for HWND={}", fmt::ptr(hwnd)); - - BOOL enabled = FALSE; - pSetWindowFeedbackSetting( - hwnd, - FEEDBACK_TOUCH_CONTACTVISUALIZATION, - 0, sizeof(enabled), &enabled); - pSetWindowFeedbackSetting( - hwnd, - FEEDBACK_TOUCH_TAP, - 0, sizeof(enabled), &enabled); - pSetWindowFeedbackSetting( - hwnd, - FEEDBACK_TOUCH_DOUBLETAP, - 0, sizeof(enabled), &enabled); - pSetWindowFeedbackSetting( - hwnd, - FEEDBACK_TOUCH_PRESSANDHOLD, - 0, sizeof(enabled), &enabled); - pSetWindowFeedbackSetting( - hwnd, - FEEDBACK_TOUCH_RIGHTTAP, - 0, sizeof(enabled), &enabled); - pSetWindowFeedbackSetting( - hwnd, - FEEDBACK_GESTURE_PRESSANDTAP, - 0, sizeof(enabled), &enabled); -} diff --git a/src/spice2x/touch/touch_indicators.h b/src/spice2x/touch/touch_indicators.h deleted file mode 100644 index 09a4902..0000000 --- a/src/spice2x/touch/touch_indicators.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -void disable_touch_indicators(HWND hwnd); diff --git a/src/spice2x/touch/win8.cpp b/src/spice2x/touch/win8.cpp index 3b0bd71..11d528d 100644 --- a/src/spice2x/touch/win8.cpp +++ b/src/spice2x/touch/win8.cpp @@ -114,10 +114,16 @@ bool Win8Handler::is_available() { bool Win8Handler::window_register(HWND hWnd) { // atom settings + // keep this in sync with touch/touch_gestures.cpp so registering a touch + // window does not downgrade the flag set applied there DWORD dwHwndTabletProperty = TABLET_DISABLE_PRESSANDHOLD | TABLET_DISABLE_PENTAPFEEDBACK | TABLET_DISABLE_PENBARRELFEEDBACK | - TABLET_DISABLE_FLICKS; + TABLET_DISABLE_FLICKS | + TABLET_DISABLE_TOUCHUIFORCEOFF | + TABLET_DISABLE_TOUCHSWITCH | + TABLET_DISABLE_SMOOTHSCROLLING | + TABLET_DISABLE_FLICKFALLBACKKEYS; // get atom ID ATOM atomID = GlobalAddAtom(ATOM_NAME);