fix: harden clipboard handling and unstick Windows screen switch#3
Draft
hsddszjs wants to merge 3 commits into
Draft
fix: harden clipboard handling and unstick Windows screen switch#3hsddszjs wants to merge 3 commits into
hsddszjs wants to merge 3 commits into
Conversation
Consolidated bug fixes layered on top of upstream master, addressing
clipboard corruption, large-image transfer crashes, and a cursor lockup
on the Windows server side.
Clipboard protocol & bounds:
- IClipboard::unmarshall: check buffer length before readUInt32 so a
truncated header cannot trigger an out-of-bounds read.
- ClipboardChunk::assemble: validate the toULong parse result and use
%zu for size_t in error messages.
- StreamChunker::sendClipboard: stop the loop on size==0 instead of
spinning forever.
Windows clipboard converters:
- AnyText: bounds check before scan[1] access; ensure GlobalUnlock when
srcSize <= 1 in the text converter.
- Bitmap: null-check GetDC / CreateDIBSection / CreateCompatibleDC;
guard against integer overflow on huge bitmap dimensions; validate
biBitCount range; handle V4/V5 DIB headers (BITMAPV4HEADER /
BITMAPV5HEADER) so non-V1 images no longer crash on conversion.
- HTML: bounds check for fragment start/end offsets.
macOS / X11 clipboard:
- BMP fromIClipboard: validate biSize range (40-1024) before reading
DIB fields; handle V4/V5 headers consistently with Windows.
- OSXClipboard: skip file-promise pasteboard data so a Finder copy of
files no longer disconnects the macOS client.
- Text/HTML converters: check CFStringGetBytes buffSize > 0 before
allocation (was checking the wrong return value).
Windows screen switch:
- MSWindowsScreen::isAnyMouseButtonDown: verify cached m_buttons[]
against GetAsyncKeyState before reporting the cursor as locked.
A button-up event from the low-level mouse hook can be silently lost
when the hook proc exceeds LowLevelHooksTimeout (~300 ms) during a
multi-MB clipboard image transfer; without this check, m_buttons[Left]
stays "down" forever and Server::isSwitchOkay() blocks every screen
switch ("locked by Left Button" log spam). Stale entries are now
cleared with a WARN so the condition is observable.
https://claude.ai/code/session_012uK8ms6TCHrBsJGmHRDeWN
Both ClientProxy1_3 (server) and ServerProxy (client) only reset their heartbeat / keep-alive alarm when they receive an explicit kMsgCKeepAlive echo. Every other inbound message - mouse moves, key events, clipboard chunks - leaves the watchdog running. That breaks under bulk clipboard transfer: while the peer ships a multi-MB clipboard payload to us, its outgoing socket queue is also where its keep-alive echo has to wait. The echo can be delayed past the 3 * kKeepAliveRate (=9s) deadline even though data is actively flowing the whole time. Result: "client/server is dead" while the connection is in fact perfectly healthy and busy. Once the watchdog fires we close the socket, the cursor cannot return to the peer screen, and the user has to wait for a reconnect. Reset the watchdog whenever we successfully parse a frame from the peer - any recognized inbound traffic is proof the link is alive. The explicit keep-alive path still works as before; it is just no longer the only thing that resets the timer. Repro from logs (2026-05-08T10:46:38 session): - 10:46:45.139 server: start receiving clipboard data (8 MB x2) - 10:46:45.252 server: clipboard fully written to Windows - 10:46:46-51 server: button/key events from client (link clearly alive) - 10:46:53.358 server: "client \"hikaris-Mac-Pro.local\" is dead" - 10:46:53.885 client: tls error occurred (server killed the socket) https://claude.ai/code/session_012uK8ms6TCHrBsJGmHRDeWN
Both OSXScreen::checkClipboards and MSWindowsScreen (checkClipboards and onClipboardChange) fired ClipboardGrabbed for kClipboardClipboard *and* kClipboardSelection back-to-back whenever the local clipboard changed. On these platforms there is only one OS clipboard, so the two events carry identical content and lead to two identical chunked transfers on the wire. For an 8 MB BMP screenshot that meant 16 MB on the network, every clipboard change. With the keep-alive fix this no longer disconnects the client, but it still doubles bandwidth, marshalling cost, and the window during which a peer might mispaste from a stale state. Drop the kClipboardSelection grab on Mac/Win. Net effect: clipboard traffic from these platforms is halved. Caveat: X11 PRIMARY (mouse-selection) sync from a Mac/Win sender is not supported anymore. Mac/Win never had a PRIMARY-equivalent in the first place, so the previous "support" was a no-op for the user (payload 1 was just a copy of payload 0). X11 senders still ship both clipboards independently, so X11 -> X11 is unchanged. https://claude.ai/code/session_012uK8ms6TCHrBsJGmHRDeWN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidated bug fixes layered on top of upstream master, addressing
clipboard corruption, large-image transfer crashes, and a cursor lockup
on the Windows server side.
Clipboard protocol & bounds
IClipboard::unmarshall: check buffer length beforereadUInt32so a truncated header cannot trigger an out-of-bounds read.ClipboardChunk::assemble: validate thetoULongparse result; use%zuforsize_tin error messages.StreamChunker::sendClipboard: stop the loop onsize==0instead of spinning forever.Windows clipboard converters
scan[1]access; ensureGlobalUnlockwhensrcSize <= 1.GetDC/CreateDIBSection/CreateCompatibleDC; guard against integer overflow on huge dimensions; validatebiBitCount; handle V4/V5 DIB headers (BITMAPV4HEADER/BITMAPV5HEADER).macOS / X11 clipboard
fromIClipboard: validatebiSizerange (40-1024); handle V4/V5 headers consistently with Windows.OSXClipboard: skip file-promise pasteboard data so a Finder copy of files no longer disconnects the macOS client.CFStringGetBytesbuffSize > 0before allocation.Windows screen switch (the cursor-stuck bug)
MSWindowsScreen::isAnyMouseButtonDown: verify cachedm_buttons[]againstGetAsyncKeyStatebefore reporting the cursor as locked.A button-up event from the low-level mouse hook can be silently lost when the hook proc exceeds
LowLevelHooksTimeout(~300 ms) during a multi-MB clipboard image transfer; without this check,m_buttons[Left]stays "down" forever andServer::isSwitchOkay()blocks every screen switch (locked by Left Buttonlog spam). Stale entries are now cleared with aWARNso the condition is observable.Test plan
cmd+shift+ctrl+4), paste in DingTalk on Windows server, verify cursor can return to macOSclearing stuck mouse button stateappears in server loghttps://claude.ai/code/session_012uK8ms6TCHrBsJGmHRDeWN
Generated by Claude Code