feat(windows): touch activates screen — switch to touched computer#147
feat(windows): touch activates screen — switch to touched computer#147stefanverleysen wants to merge 8 commits intosymless:masterfrom
Conversation
src/lib/server/Server.cpp
Outdated
| #if WINAPI_MSWINDOWS | ||
| #define WIN32_LEAN_AND_MEAN | ||
| #include <Windows.h> | ||
| #endif |
There was a problem hiding this comment.
The project structure is designed so that platform/arch specific code goes in the platform and arch dirs.
Generally, we avoid #if WINAPI_MSWINDOWS when we can, and in this case it should certainly be possible to write the platform-specific code in the right place.
Why? #if WINAPI_MSWINDOWS blocks in platform-agnostic files like Server.cpp makes life harder for every developer who touches the code after you. Right now, if someone needs to fix a Windows bug, they know to look in src/lib/platform/MSWindows*.cpp, but if platform-specific code is scattered across Server.cpp and other core files behind #ifdef blocks, now they have to hunt through the whole codebase to find all the places where Windows behavior lives. It only gets worse over time because once one person does it, the next person (or LLM) does too, and eventually you end up with a tangled mess of conditional compilation blocks that increases tech debt and adds to the maintenance burden.
There was a problem hiding this comment.
Fixed. Removed all #if WINAPI_MSWINDOWS and #include <Windows.h> from Server.cpp.
Added activateWindowAt(SInt32 x, SInt32 y) through the platform abstraction chain:
IPlatformScreen— virtual with empty defaultScreen/PrimaryClient— forwardingMSWindowsScreen— Windows-specificSetForegroundWindowimplementation
Also removed the dead mouseDown/mouseUp calls — PrimaryClient ignores them.
| } | ||
| } | ||
|
|
||
| void Server::handleTouchActivatedPrimaryEvent(const Event &event, void *) |
There was a problem hiding this comment.
Please review all comments in this function. It reads like a student "hello world" project. As I mentioned in my last PR review, code comments "rot" -- they eventually drift away from reality and become lies, confusing other devs in future.
Check out the Deskflow guidance on comments: https://github.com/deskflow/deskflow/wiki/Hacking-Guide#6-do-not-add-redundant-comments
The most important part:
a comment can be added but it must explain why we are doing something and never what the code is doing.
There was a problem hiding this comment.
Fixed. Removed all play-by-play narration comments from both handleTouchActivatedPrimaryEvent and handleGrabScreenEvent.
Remaining comments only explain why:
- Jump zone clamping — avoids triggering immediate edge switch
activateWindowAt— hook eats the original touch event- Cooldown reset — prevents edge switches from undoing the touch switch
There was a problem hiding this comment.
Still an issue, loads of student-project style comments.
@stefanverleysen The PR description (and future PR descriptions) could benefit from some improvements: Move API-level details (hook types, raw input flags, specific Win32 calls) into code comments but make sure they explain why the code does what it does (never what the code does). The PR description should describe behavior and scope, not implementation mechanics. Separate the cursor-hiding fix into its own PR if possible. Bundling a related bugfix into a feature PR makes both harder to review and harder to revert independently (often something that needs to be done after the PR lands if the bug fix turns out to have too many negative side effects). In this case you can add a "blocked by" note at the top, linking to the other PR. It's important that bug fixes are tested separately. Trim the test environment table to OS version and touch device type. Hardware specs like GPU and CPU model are noise for this kind of change. The test results section: Replace with a list of "To test" numbered steps (so the PR reviewer knows how to test the PR). |
There was a problem hiding this comment.
Pull request overview
Adds a Windows-only “touch activates screen” feature that switches keyboard/mouse focus to the touched computer, plus supporting protocol/config plumbing and improved cursor hiding on touch hardware.
Changes:
- Adds
touchActivateScreenoption (withtouchInputLocalbackward-compat) and GUI toggle. - Introduces protocol v1.9 message for client→server “grab screen” requests.
- Implements Windows touch detection (LL hook + Raw Input + WM_POINTER) and a more reliable cursor hider on touch devices.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/server/Server.h | Adds handlers and cooldown state for touch-triggered switching. |
| src/lib/server/Server.cpp | Implements touch activation + client grab handling and applies cooldown to edge switching. |
| src/lib/server/PrimaryClient.h | Adds window-activation API for primary screen. |
| src/lib/server/PrimaryClient.cpp | Forwards activateWindowAt to platform screen. |
| src/lib/server/Config.cpp | Parses new option name with backward-compatible alias. |
| src/lib/server/ClientProxyUnknown.cpp | Routes protocol minor 1.9 to new proxy. |
| src/lib/server/ClientProxy1_9.h | New proxy type for protocol v1.9. |
| src/lib/server/ClientProxy1_9.cpp | Parses CGRB (grab screen) message and emits an event. |
| src/lib/platform/dfwhook.h | Adds new hook message ID for touch events. |
| src/lib/platform/MSWindowsScreen.h | Adds touch option state/debounce and platform activateWindowAt. |
| src/lib/platform/MSWindowsScreen.cpp | Implements touch detection paths (WM_POINTER + hook message handling) and window activation. |
| src/lib/platform/MSWindowsHook.h | Exposes toggles for touch activate + primary/secondary role. |
| src/lib/platform/MSWindowsHook.cpp | Detects touch-originated mouse events and posts touch messages. |
| src/lib/platform/MSWindowsDesks.cpp | Improves cursor hiding on touchscreen clients; adds Raw Input forwarding for touch. |
| src/lib/deskflow/protocol_types.h | Bumps protocol minor to 1.9; declares CGRB message. |
| src/lib/deskflow/protocol_types.cpp | Defines kMsgCGrabScreen. |
| src/lib/deskflow/option_types.h | Adds kOptionTouchActivateScreen. |
| src/lib/deskflow/Screen.h | Adds activateWindowAt API. |
| src/lib/deskflow/Screen.cpp | Forwards activateWindowAt to platform screen. |
| src/lib/deskflow/IPlatformScreen.h | Adds default no-op activateWindowAt to platform interface. |
| src/lib/client/ServerProxy.h | Adds grabScreen() API documentation. |
| src/lib/client/ServerProxy.cpp | Sends CGRB message to server. |
| src/lib/client/Client.h | Adds handler for local grabScreen events. |
| src/lib/client/Client.cpp | Forwards local grabScreen events to server; updates compatibility table. |
| src/lib/base/EventTypes.h | Adds new event types for touch-activated primary + grabScreen. |
| src/lib/base/EventTypes.cpp | Registers new event types. |
| src/gui/src/ServerConfigDialogBase.ui | Adds GUI checkbox for touch activation. |
| src/gui/src/ServerConfigDialog.cpp | Wires checkbox to ServerConfig. |
| src/gui/src/ServerConfig.h | Adds touchActivateScreen storage + accessors. |
| src/gui/src/ServerConfig.cpp | Persists/loads/txt-dumps touchActivateScreen (with alias fallback). |
| src/gui/CMakeLists.txt | Adds include path for multi-config Qt autogen headers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/lib/platform/MSWindowsScreen.cpp
Outdated
| if (!GetCursorPos(&pt)) | ||
| return false; |
There was a problem hiding this comment.
WM_POINTER* handling uses GetCursorPos() to determine the activation point. For touch, the mouse cursor may be elsewhere (or not updated), causing a switch at the wrong coordinates. Use the pointer message coordinates (e.g., screen coords from lParam such as GET_X_LPARAM(lParam) / GET_Y_LPARAM(lParam), or query pointer info via the pointer APIs) instead of the cursor position.
| if (!GetCursorPos(&pt)) | |
| return false; | |
| pt.x = GET_X_LPARAM(lParam); | |
| pt.y = GET_Y_LPARAM(lParam); |
src/lib/platform/MSWindowsScreen.cpp
Outdated
| DWORD foreThread = GetWindowThreadProcessId(GetForegroundWindow(), NULL); | ||
| DWORD curThread = GetCurrentThreadId(); | ||
| if (foreThread != curThread) { | ||
| AttachThreadInput(foreThread, curThread, TRUE); | ||
| } | ||
| SetForegroundWindow(root); | ||
| if (foreThread != curThread) { |
There was a problem hiding this comment.
GetForegroundWindow() can return NULL (e.g., during certain desktop transitions). In that case GetWindowThreadProcessId(NULL, ...) yields 0, and AttachThreadInput(0, ...) is invalid and may fail unpredictably. Capture the foreground HWND into a variable, check for NULL, and only call AttachThreadInput when you have a valid non-zero foreground thread id; ensure detach happens only if attach succeeded.
| DWORD foreThread = GetWindowThreadProcessId(GetForegroundWindow(), NULL); | |
| DWORD curThread = GetCurrentThreadId(); | |
| if (foreThread != curThread) { | |
| AttachThreadInput(foreThread, curThread, TRUE); | |
| } | |
| SetForegroundWindow(root); | |
| if (foreThread != curThread) { | |
| HWND foreground = GetForegroundWindow(); | |
| DWORD foreThread = 0; | |
| if (foreground != NULL) { | |
| foreThread = GetWindowThreadProcessId(foreground, NULL); | |
| } | |
| DWORD curThread = GetCurrentThreadId(); | |
| BOOL attached = FALSE; | |
| if (foreThread != 0 && foreThread != curThread) { | |
| attached = AttachThreadInput(foreThread, curThread, TRUE); | |
| } | |
| SetForegroundWindow(root); | |
| if (attached) { |
src/lib/platform/MSWindowsScreen.cpp
Outdated
| if (foreThread != curThread) { | ||
| AttachThreadInput(foreThread, curThread, FALSE); | ||
| } | ||
| LOG((CLOG_DEBUG1 "touch: forced foreground window 0x%08x", root)); |
There was a problem hiding this comment.
Logging an HWND with 0x%08x truncates the value on 64-bit builds. Prefer %p (casting to void*) so logs remain correct across architectures.
| LOG((CLOG_DEBUG1 "touch: forced foreground window 0x%08x", root)); | |
| LOG((CLOG_DEBUG1 "touch: forced foreground window %p", static_cast<void*>(root))); |
| case WM_INPUT: { | ||
| // Raw touch input from digitizer. Forward to main thread as | ||
| // DESKFLOW_MSG_TOUCH for the same handling as the LL mouse hook. | ||
| UINT size = 0; | ||
| GetRawInputData( | ||
| reinterpret_cast<HRAWINPUT>(msg.lParam), RID_INPUT, | ||
| NULL, &size, sizeof(RAWINPUTHEADER)); | ||
| if (size > 0 && size <= 1024) { | ||
| BYTE buffer[1024]; | ||
| if (GetRawInputData( | ||
| reinterpret_cast<HRAWINPUT>(msg.lParam), RID_INPUT, | ||
| buffer, &size, sizeof(RAWINPUTHEADER)) != static_cast<UINT>(-1)) { | ||
| RAWINPUT *raw = reinterpret_cast<RAWINPUT *>(buffer); | ||
| if (raw->header.dwType == RIM_TYPEHID && | ||
| raw->data.hid.dwCount > 0 && raw->data.hid.dwSizeHid > 0) { | ||
| POINT pt; | ||
| GetCursorPos(&pt); | ||
| LOG((CLOG_DEBUG1 "desk raw touch at %d,%d", pt.x, pt.y)); | ||
| PostThreadMessage(m_threadID, DESKFLOW_MSG_TOUCH, | ||
| static_cast<WPARAM>(pt.x), static_cast<LPARAM>(pt.y)); | ||
| } | ||
| } | ||
| } | ||
| continue; | ||
| } |
There was a problem hiding this comment.
This forwards/logs every WM_INPUT digitizer event unconditionally, even when touchActivateScreen is disabled (the main thread later discards it). On touch-heavy devices this can create avoidable CPU/log overhead. Consider gating the forwarding/logging on an enabled flag (shared option state) and/or only registering raw input when the feature is enabled.
src/lib/server/Server.cpp
Outdated
| if (m_touchSwitchCooldown.getTime() < kTouchSwitchCooldownTime) { | ||
| LOG((CLOG_DEBUG1 "edge switch blocked by touch cooldown (%.2fs remaining)", | ||
| kTouchSwitchCooldownTime - m_touchSwitchCooldown.getTime())); |
There was a problem hiding this comment.
If Stopwatch starts at 0 on construction (common), this blocks all edge switching for the first 0.5s after server startup (and potentially after any lifecycle that reconstructs Server). If the intent is 'only block immediately after a touch/grab switch', consider initializing the cooldown as 'expired' (e.g., via an explicit boolean m_touchSwitchCooldownActive that is set on touch/grab and cleared once elapsed), or use a hasElapsed()-style check if available instead of comparing against a stopwatch that begins at 0.
| if (m_touchSwitchCooldown.getTime() < kTouchSwitchCooldownTime) { | |
| LOG((CLOG_DEBUG1 "edge switch blocked by touch cooldown (%.2fs remaining)", | |
| kTouchSwitchCooldownTime - m_touchSwitchCooldown.getTime())); | |
| double elapsedTouchCooldown = m_touchSwitchCooldown.getTime(); | |
| if (elapsedTouchCooldown > 0.0 && elapsedTouchCooldown < kTouchSwitchCooldownTime) { | |
| LOG((CLOG_DEBUG1 "edge switch blocked by touch cooldown (%.2fs remaining)", | |
| kTouchSwitchCooldownTime - elapsedTouchCooldown)); |
nbolton
left a comment
There was a problem hiding this comment.
💡 Idea: Can this PR be further improved to allow client mouse input to take focus rather than just touch input? If so that would be massively useful to a much bigger number of Synergy users.
Review summary
Please find everywhere that says "screen grab" or grabScreen etc and change to "grab input" or "grab focus" -- we avoid using "screen" in new code because it's ambiguous (computers can have multiple screens).
There are also still so any AI slop "hello world" comments that make this diff read like student's homework. Purely as an example/starting point -- Here's what Claude found (this is by no means an exhaustive list and may be innacruate):
- Client.cpp (line in diff): // Forward grab screen request to server via protocol — the function name handleGrabScreen and the two lines of code make this obvious.
- MSWindowsDesks.cpp deskEnter: // Restore WS_EX_TRANSPARENT that was removed in deskLeave — just restates what | WS_EX_TRANSPARENT does. Better: explain why the pairing matters (e.g. "so hit-testing passes through again").
- MSWindowsDesks.cpp deskLeave: // Brief delay for cursor hiding to take effect, then center the cursor. — just describes the sleep + move. The original comment at least explained why 30ms was chosen and the tradeoffs. This lost the "why".
- MSWindowsDesks.cpp WM_INPUT case: // Raw touch input from digitizer. Forward to main thread as DESKFLOW_MSG_TOUCH... — the first sentence ("Raw touch input from digitizer") is pure "what"; the case label and code make that clear. The "for the same handling as the LL mouse hook" part is useful though.
- MSWindowsScreen.cpp: // WM_POINTER stuff (Windows 8+) — section label describing what, not why.
- MSWindowsScreen.cpp setOptions: // check for touch input local option — self-evident from kOptionTouchActivateScreen on the next line.
- MSWindowsScreen.cpp isPointerTypeTouch: // Dynamically load GetPointerType for Windows 7 compatibility — duplicates the comment already at the declaration (// Function pointer type for GetPointerType (loaded dynamically for Win7 compat)).
- MSWindowsScreen.h: // When true, touching this screen activates it (switches focus here) — m_touchActivateScreen already says exactly this.
- Server.h: // state for touch-triggered screen switching cooldown — the member name m_touchSwitchCooldown already says this. (The next line — "prevents edge-triggered switches from immediately undoing touch switches" — is the useful "why" part.)
src/lib/server/ClientProxy1_9.h
Outdated
| @@ -0,0 +1,36 @@ | |||
| /* | |||
| * Deskflow -- mouse and keyboard sharing utility | |||
| * Copyright (C) 2012-2016 Symless Ltd. | |||
There was a problem hiding this comment.
Only 10 years off 😉
| * Copyright (C) 2012-2016 Symless Ltd. | |
| * Copyright (C) 2012-2026 Symless Ltd. |
src/lib/server/ClientProxy1_9.cpp
Outdated
| @@ -0,0 +1,59 @@ | |||
| /* | |||
| * Deskflow -- mouse and keyboard sharing utility | |||
| * Copyright (C) 2012-2016 Symless Ltd. | |||
There was a problem hiding this comment.
| * Copyright (C) 2012-2016 Symless Ltd. | |
| * Copyright (C) 2012-2026 Symless Ltd. |
src/lib/deskflow/protocol_types.h
Outdated
| // grab screen request: secondary -> primary | ||
| // Client requests to become the active screen (e.g., due to touch input). | ||
| // $1 = x position, $2 = y position where activation occurred | ||
| extern const char *const kMsgCGrabScreen; |
There was a problem hiding this comment.
I need to spend some time figuring out if it's really necessary to change the protocol. We're able to switch screens without a protocol change, but I suppose the issue here is that the client has no way currently of saying to a server "Hey, I need to be the active input"
src/lib/deskflow/protocol_types.h
Outdated
| // grab screen request: secondary -> primary | ||
| // Client requests to become the active screen (e.g., due to touch input). | ||
| // $1 = x position, $2 = y position where activation occurred | ||
| extern const char *const kMsgCGrabScreen; |
There was a problem hiding this comment.
Assuming the protocol change is necessary, we try to avoid the word "screen" since computers can have multiple screens... I wonder if "input" would make sense?
| // grab screen request: secondary -> primary | |
| // Client requests to become the active screen (e.g., due to touch input). | |
| // $1 = x position, $2 = y position where activation occurred | |
| extern const char *const kMsgCGrabScreen; | |
| // grab input request: secondary -> primary | |
| // Client requests to become the active computer | |
| // $1 = x position, $2 = y position where activation occurred | |
| extern const char *const kMsgCGrabInput; |
... or maybe "focus"?
| // grab screen request: secondary -> primary | |
| // Client requests to become the active screen (e.g., due to touch input). | |
| // $1 = x position, $2 = y position where activation occurred | |
| extern const char *const kMsgCGrabScreen; | |
| // grab focus request: secondary -> primary | |
| // Client requests to become the active computer | |
| // $1 = x position, $2 = y position where activation occurred | |
| extern const char *const kMsgCGrabFocus; |
Afterthought: Borderline semantics here but I wonder if "take focus" or "take input" would make more sense?
| // grab screen request: secondary -> primary | |
| // Client requests to become the active screen (e.g., due to touch input). | |
| // $1 = x position, $2 = y position where activation occurred | |
| extern const char *const kMsgCGrabScreen; | |
| // take focus request: secondary -> primary | |
| // Client requests to become the active computer | |
| // $1 = x position, $2 = y position where input occurred | |
| extern const char *const kMsgCTakeFocus; |
src/lib/client/Client.cpp
Outdated
| const std::map<int, std::set<int>> compatibleTable{ | ||
| {6, {7, 8}}, // 1.6 is compatible with 1.7 and 1.8 | ||
| {7, {8}} // 1.7 is compatible with 1.8 | ||
| {7, {8}}, // 1.7 is compatible with 1.8 | ||
| {8, {9}} // 1.8 is compatible with 1.9 (touch-to-switch unavailable) | ||
| }; |
There was a problem hiding this comment.
This table has been removed upstream, as it doesn't make sense. I wonder if it's pointless modifying it.
src/lib/deskflow/IPlatformScreen.h
Outdated
| { | ||
| } |
There was a problem hiding this comment.
| { | |
| } | |
| { | |
| // do nothing | |
| } |
src/gui/CMakeLists.txt
Outdated
| # single-config generators (Make) use include/, multi-config (VS) use include_$<CONFIG>/ | ||
| include_directories(${PROJECT_BINARY_DIR}/src/lib/gui/gui_autogen/include) | ||
| include_directories(${PROJECT_BINARY_DIR}/src/lib/gui/gui_autogen/include_$<CONFIG>) |
There was a problem hiding this comment.
I wonder why this change was needed. Maybe it should be a separate PR?
src/lib/client/Client.cpp
Outdated
|
|
||
| void Client::handleGrabScreen(const Event &event, void *) | ||
| { | ||
| // Forward grab screen request to server via protocol |
There was a problem hiding this comment.
Comment doesn't explain why -- is it redundant?
src/lib/platform/MSWindowsScreen.cpp
Outdated
| // WM_POINTER stuff (Windows 8+) | ||
| #if !defined(WM_POINTERDOWN) | ||
| #define WM_POINTERDOWN 0x0246 | ||
| #define WM_POINTERUP 0x0247 | ||
| #define WM_POINTERUPDATE 0x0245 | ||
| #define WM_POINTERENTER 0x0249 | ||
| #define WM_POINTERLEAVE 0x024A | ||
| #define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam)) | ||
| #endif | ||
|
|
||
| #if !defined(PT_POINTER) | ||
| #define PT_POINTER 1 | ||
| #define PT_TOUCH 2 | ||
| #define PT_PEN 3 | ||
| #define PT_MOUSE 4 | ||
| #endif | ||
|
|
||
| // Function pointer type for GetPointerType (loaded dynamically for Win7 compat) | ||
| typedef BOOL(WINAPI *GetPointerTypeFunc)(UINT32 pointerId, DWORD *pointerType); | ||
| static GetPointerTypeFunc s_getPointerType = NULL; | ||
| static bool s_pointerApiChecked = false; |
There was a problem hiding this comment.
I asked you to remove this kind of backward compat stuff in your old PR.
It's redundant. WM_POINTER* and PT_* have been in the Windows SDK since Windows 8 (SDK 8.0+). Any SDK that can target Win11 will have them. The #if !defined guards are unnecessary
Same story as the VS2005 hack in dfwhook.h:L21-27
The dynamic loading of GetPointerType via GetProcAddress a bit further down is the same pattern. It's a Win8+ API that will always be present on Win11. Could just #include <winuser.h> and call it directly.
| } | ||
| } | ||
|
|
||
| void Server::handleTouchActivatedPrimaryEvent(const Event &event, void *) |
There was a problem hiding this comment.
Still an issue, loads of student-project style comments.
debe224 to
fa14779
Compare
fa14779 to
3fab44b
Compare
nbolton
left a comment
There was a problem hiding this comment.
Lots of good Doxygen comments were removed. Please restore them. I left a comment explaining the difference between Doxygen comments and inline comments.
src/lib/base/EventTypes.h
Outdated
| //! Get grab screen event type | ||
| /*! | ||
| Returns the grab screen event type. This is sent when a secondary screen | ||
| requests to become the active screen (e.g., due to touch input). | ||
| Event data is MotionInfo* with the position where activation occurred. | ||
| */ |
There was a problem hiding this comment.
Keep Doxygen comments. Those are used to generate documentation and it's ok to explain how the function inputs, outputs, and effects works here (as long it's kept up to date).
It's only the inline "how" comments that are bad (typical in a student homework assignment). Inline "why" comments are fine.
src/lib/platform/MSWindowsDesks.cpp
Outdated
|
|
||
| // Restore WS_EX_TRANSPARENT that was removed in deskLeave | ||
| LONG_PTR exStyle = GetWindowLongPtr(desk->m_window, GWL_EXSTYLE); | ||
| // let hit-testing pass through to windows underneath |
There was a problem hiding this comment.
If this is a "why" comment, I'm not seeing how. It needs to be better.
src/lib/platform/MSWindowsDesks.cpp
Outdated
| POINT pt; | ||
| GetCursorPos(&pt); | ||
| LOG((CLOG_DEBUG1 "desk raw touch at %d,%d", pt.x, pt.y)); | ||
| // same handling path as the LL mouse hook touch detection |
| HWND foreground = GetForegroundWindow(); | ||
| DWORD foreThread = 0; | ||
| if (foreground != NULL) { | ||
| foreThread = GetWindowThreadProcessId(foreground, NULL); | ||
| } | ||
| DWORD curThread = GetCurrentThreadId(); |
There was a problem hiding this comment.
These var names aren't very good. foregroundWindow would be better than foreground and cur and fore are unneccesary shortenings. This was OK in the 80's and 90's before code completion and AI, but now it's just a dead convention that causes obscurity.
…ymless#147) Detailed root cause analysis of client-to-client touch regression reported by Abel Lin. Identifies 4 bugs introduced in commit 9d05f96, with recommended fixes prioritized for the dev team. https://claude.ai/code/session_01QozaSLEiL5DvhJx9C88NRB
Add a touchActivateScreen option that switches keyboard/mouse focus to whichever computer the user touches. Works bidirectionally between server and clients, and detects touch in all applications including Chrome, Electron, UWP, and legacy Win32 apps. Touch detection uses three independent paths for broad hardware coverage: low-level mouse hook (dwExtraInfo MI_WP_SIGNATURE), raw input (RIDEV_INPUTSINK on desk thread for WM_INPUT), and WM_POINTER messages (Win8+ API). All three converge through debounced event dispatch to the server's screen-switch logic. Raw input is registered on the desk thread window rather than the main window because the main event loop uses QS_ALLPOSTMESSAGE, which never wakes for WM_INPUT messages. Cursor hiding on touchscreen clients uses a full-screen hider window with a blank cursor class instead of ShowCursor(FALSE), which is unreliable on touch hardware. WS_EX_TRANSPARENT is toggled on leave/enter for correct hit-testing. The server synthesizes a click and forces the foreground window after touch-triggered switches so the window under the touch point receives focus. A 500ms cooldown prevents edge-triggered switches from immediately undoing touch switches. Tested on: - Server: ASUS 3090DEV, i9-12900K, RTX 3090, Windows 11 Enterprise Build 26100 (64-bit), USB HID touch screen (VID 0457 / PID 0819) - Client: Microsoft Surface Book (1st gen), integrated touchscreen, Windows 11 - Applications tested: Chrome, Cursor (Electron), Windows Settings (UWP), Notepad, Synergy GUI, Start menu, File Explorer
…on, comment cleanup - Create ClientProxy1_9 for kMsgCGrabScreen (was incorrectly in ClientProxy1_0, breaking backward compatibility with older clients) - Bump protocol version 1.8 → 1.9 for touch-activated screen switching - Move Windows-specific SetForegroundWindow logic from Server.cpp to MSWindowsScreen::activateWindowAt() via platform abstraction chain - Remove dead mouseDown/mouseUp calls (PrimaryClient ignores them) - Remove #if WINAPI_MSWINDOWS and Windows.h include from Server.cpp - Clean up comments: keep only "why" comments, remove "what" comments Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename grabScreen -> grabInput (avoid "screen" ambiguity) - Strip "what" comments, keep only "why" explanations - Remove Win7/Win8 backwards-compat guards (direct WM_POINTER/GetPointerType) - Fix cursor hiding on secondary touchscreen clients (WM_MOUSEMOVE hider dismissal) - Update copyright dates to 2012-2026 - Drop compat table entry, clean up empty virtuals and doxygen blocks
- Save foreground window in secondary deskLeave so deskEnter can restore it; fixes SetForegroundWindow failing on re-enter which required a double-tap to click after switching - Skip hider auto-hide on touch-generated WM_MOUSEMOVE by checking the MI_WP_SIGNATURE touch signature via GetMessageExtraInfo - Restrict WM_INPUT raw touch handler to primary screens; on clients GetCursorPos returns the parked center position, not the touch point, causing clicks to land at screen center - Only eat touch events in the LL hook when in relay mode, not watch mode; fixes touch on server's secondary touchscreens acting as a giant cursor on the primary monitor - Move TOUCH_SIGNATURE defines to dfwhook.h shared header - Add diagnostic logging to activateWindowAt and click replay path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server: skip LL hook touch handling in watch mode to prevent double-tap requirement on the server's own touchscreen. Register raw input for touch only when cursor leaves (deskLeave) and unregister on return (deskEnter). Replace GetCursorPos with proper HID preparsed data parsing for accurate touch coordinates. Client: replay consumed first-touch as InjectTouchInput on the desk thread, with mouse_event fallback for processes without UIAccess. Handle WM_POINTERACTIVATE with PA_NOACTIVATE to prevent the hider window from stealing focus on touch. Add 100ms settle delay after deskEnter before replay so full-screen apps can resume input processing. Plumbing: add fakeTouchClick virtual method through IPlatformScreen/Screen/MSWindowsScreen to route touch replay through the platform layer. Link hid.lib on Windows for HID preparsed data APIs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3b53108 to
aff75ee
Compare
- Async touch injection via PostThreadMessage chain (no Sleep calls) - WS_EX_LAYERED hider (alpha=1) prevents DWM fullscreen occlusion - Local cursor restored on real mouse movement (position check, not timing) - Fix InjectTouchInput pointer ID (pointerId=1, InitializeTouchInjection(2)) - WM_INPUT touch detection on secondary with m_isOnScreen guard - Foreground window restored before injection - Bidirectional touch switch cooldown - Remove m_deskLeaveTime, m_touchHideTime, WM_TIMER, polling loops Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Adds a
touchActivateScreenoption. Touching a computer's screen switches keyboard/mouse focus to that computer. Works both ways: touching the server or any client triggers the switch at the touch point.Requires protocol version 1.9 (
ClientProxy1_9). Older clients connect normally but can't trigger touch switching.Changes
kMsgCGrabInputfor clients to request focusClientProxy1_9handles the new message; older proxies untouchedactivateWindowAt()throughIPlatformScreenvirtual; onlyMSWindowsScreenimplementsTo test: