Skip to content

feat(windows): touch activates screen — switch to touched computer#147

Open
stefanverleysen wants to merge 3 commits intosymless:masterfrom
stefanverleysen:feat/touch-activates-screen
Open

feat(windows): touch activates screen — switch to touched computer#147
stefanverleysen wants to merge 3 commits intosymless:masterfrom
stefanverleysen:feat/touch-activates-screen

Conversation

@stefanverleysen
Copy link

Summary

Adds a touchActivateScreen option that lets users switch keyboard/mouse focus by touching a screen. When the cursor is on computer A and you touch computer B's screen, focus switches to B at the touch point. Works bidirectionally (server/client).

This feature is Windows-only. Touch detection relies on Windows-specific APIs (WH_MOUSE_LL, RegisterRawInputDevices, WM_POINTER), and cursor hiding uses Win32 window management. The option plumbing and protocol messages are cross-platform, but detection and activation are only implemented in the MSWindows* platform layer. macOS and Linux are unaffected.

Three independent detection paths ensure coverage across all Windows apps:

  • LL mouse hook (dwExtraInfo MI_WP_SIGNATURE) — legacy Win32 apps
  • Raw input (RIDEV_INPUTSINK on desk thread) — Chrome, Electron, UWP, modern apps
  • WM_POINTER (Win8+ API) — additional coverage

Also fixes cursor hiding on touchscreen clients (ShowCursor(FALSE) is unreliable on touch hardware — replaced with full-screen hider window + blank cursor class).

Test environment

Role Hardware OS Touch device
Server ASUS 3090DEV, i9-12900K, RTX 3090 Windows 11 Enterprise Build 26100 (64-bit) USB HID touchscreen (VID 0457 / PID 0819)
Client Microsoft Surface Book (1st gen) Windows 11 Integrated touchscreen

Applications tested: Chrome, Cursor (Electron), Windows Settings (UWP), Notepad, Synergy GUI, Start menu, File Explorer

Test results

  • Server to Client: touch client screen while cursor is on server — cursor jumps to client at touch point
  • Client to Server: touch server screen while cursor is on client — cursor jumps to server at touch point
  • Touch detection works in Chrome, Cursor, UWP Settings, Notepad, and legacy Win32 apps
  • Cursor is hidden on client after switching away via touch
  • Normal edge-crossing still works in both directions
  • Keyboard input follows the screen switch
  • Feature disabled by default; enabled via Server Config, Switch screens on touch
  • Config backwards-compatible (parser accepts old touchInputLocal name)
  • No impact on macOS/Linux builds (platform-specific code is in MSWindows* files only)

Development notes

This feature was developed with assistance from Claude Code (Opus 4.6) for code exploration, debugging, and review. All changes were manually tested on physical hardware.

Comment on lines 50 to 53
#if WINAPI_MSWINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 default
  • Screen / PrimaryClient — forwarding
  • MSWindowsScreen — Windows-specific SetForegroundWindow implementation

Also removed the dead mouseDown/mouseUp calls — PrimaryClient ignores them.

}
}

void Server::handleTouchActivatedPrimaryEvent(const Event &event, void *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still an issue, loads of student-project style comments.

@nbolton nbolton self-requested a review February 12, 2026 09:54
@nbolton
Copy link
Member

nbolton commented Feb 12, 2026

Summary

Adds a touchActivateScreen option that lets users switch keyboard/mouse focus by touching a screen. When the cursor is on computer A and you touch computer B's screen, focus switches to B at the touch point. Works bidirectionally (server/client).

@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).

@nbolton nbolton requested a review from Copilot February 12, 2026 11:58
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 touchActivateScreen option (with touchInputLocal backward-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.

Comment on lines +1520 to +1521
if (!GetCursorPos(&pt))
return false;
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if (!GetCursorPos(&pt))
return false;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);

Copilot uses AI. Check for mistakes.
Comment on lines +2020 to +2026
DWORD foreThread = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
DWORD curThread = GetCurrentThreadId();
if (foreThread != curThread) {
AttachThreadInput(foreThread, curThread, TRUE);
}
SetForegroundWindow(root);
if (foreThread != curThread) {
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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) {

Copilot uses AI. Check for mistakes.
if (foreThread != curThread) {
AttachThreadInput(foreThread, curThread, FALSE);
}
LOG((CLOG_DEBUG1 "touch: forced foreground window 0x%08x", root));
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging an HWND with 0x%08x truncates the value on 64-bit builds. Prefer %p (casting to void*) so logs remain correct across architectures.

Suggested change
LOG((CLOG_DEBUG1 "touch: forced foreground window 0x%08x", root));
LOG((CLOG_DEBUG1 "touch: forced foreground window %p", static_cast<void*>(root)));

Copilot uses AI. Check for mistakes.
Comment on lines +684 to +708
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;
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +787 to +789
if (m_touchSwitchCooldown.getTime() < kTouchSwitchCooldownTime) {
LOG((CLOG_DEBUG1 "edge switch blocked by touch cooldown (%.2fs remaining)",
kTouchSwitchCooldownTime - m_touchSwitchCooldown.getTime()));
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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));

Copilot uses AI. Check for mistakes.
Copy link
Member

@nbolton nbolton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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.)

@@ -0,0 +1,36 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only 10 years off 😉

Suggested change
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012-2026 Symless Ltd.

@@ -0,0 +1,59 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012-2026 Symless Ltd.

// 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Comment on lines +298 to +301
// 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Suggested change
// 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"?

Suggested change
// 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?

Suggested change
// 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;

Comment on lines 662 to 666
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)
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This table has been removed upstream, as it doesn't make sense. I wonder if it's pointless modifying it.

Comment on lines +209 to +210
{
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{
}
{
// do nothing
}

Comment on lines +44 to +46
# 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>)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why this change was needed. Maybe it should be a separate PR?


void Client::handleGrabScreen(const Event &event, void *)
{
// Forward grab screen request to server via protocol
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment doesn't explain why -- is it redundant?

Comment on lines +87 to +107
// 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still an issue, loads of student-project style comments.

stefanverleysen and others added 3 commits February 12, 2026 12:29
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>
…vers

A 1.9 client connecting to a 1.8 server would be rejected as incompatible.
Add compat table entry so the client downgrades to 1.8 (touch-to-switch
unavailable, but connection works).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nbolton nbolton force-pushed the feat/touch-activates-screen branch from debe224 to fa14779 Compare February 12, 2026 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants