Skip to content

Fix 6 client-server mode bugs (display-message, mouse, popup, paste, right-click)#118

Closed
ToxMox wants to merge 5 commits into
psmux:masterfrom
ToxMox:fixes/client-server-bugs
Closed

Fix 6 client-server mode bugs (display-message, mouse, popup, paste, right-click)#118
ToxMox wants to merge 5 commits into
psmux:masterfrom
ToxMox:fixes/client-server-bugs

Conversation

@ToxMox

@ToxMox ToxMox commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

I ran into these issues while using psmux in client-server mode from VS Code today and ended up forking and fixing them for myself. Figured they might help others out too. Thanks for the awesome work on psmux!

Context

When psmux runs in client-server mode (psmux attach from VS Code terminal or Windows Terminal), several features silently break because the client and server have separate input/rendering paths. These fixes address the most impactful issues for multi-client workflows and VS Code terminal usage.

Fixes

display-message

The rendering infrastructure existed (server serializes status_message to JSON, client renders it on the status bar with message_style) but the DisplayMessage handler only expanded the format string and returned it — it never set app.status_message. One-line fix.

Mouse multi-client tracking

With multiple clients attached at different terminal sizes, mouse clicks on the status bar only worked in whichever client last resized. latest_client_id was only updated on ClientAttach and ClientSize — not on mouse events. Added client_id to all 10 mouse/scroll CtrlReq variants. The server now recomputes effective window size from the clicking client's dimensions before processing coordinates.

Popup race condition

display-popup with fast commands (e.g. echo test) produced blank popups. The PTY reader thread hadn't processed any output before the first frame was serialized to the client. Added a 50ms delay after PTY spawn to let the reader thread populate the vt100 parser.

Paste fragmentation (VS Code terminal)

Large pastes in VS Code's terminal arrived as multiple [Pasted text #N] chunks instead of one. Root cause chain:

  1. ConPTY with VTI disabled strips bracket paste markers, delivering paste as individual KEY_EVENT records
  2. The client's paste_pend burst detector entered stage2 but timed out at 300ms mid-paste (ConPTY delivers text over ~1 second)
  3. Ctrl+V Release triggered an immediate flush before ConPTY finished injecting characters
  4. Right-click clipboard paste sent the full content, then ConPTY also injected the same text as key events — producing a duplicate
  5. Non-ASCII characters (em-dashes, etc.) triggered immediate flush as "normal text" instead of accumulating

Fixes: Ctrl+V Release no longer flushes immediately — promotes to stage2 for growth detection so ConPTY can finish delivery; right-click clipboard paste suppresses duplicate ConPTY key events for 2s (discards text keys during the suppression window to prevent leaked Enter/Tab); large non-ASCII buffers (>=20 chars) enter stage2 instead of being treated as IME input; added growth detection to stage2 timeout so ongoing pastes aren't split.

Right-click copy triggers unwanted paste

In VS Code with "smart copy/paste" right-click behavior, highlighting text and right-clicking would copy the selection AND paste the clipboard content. VS Code injects clipboard content as key events after the copy action. Uses the same 2s suppression window with full text-key filtering.

Clipboard line ending normalization

Right-click paste produced double-spaced text because the Windows clipboard preserves \r\n line endings. Now normalizes \r\n to \n and trims trailing newlines from clipboard reads.

Test plan

  • display-message "hello" from command prompt shows in status bar
  • Two clients attached with different sizes — mouse clicks work in both
  • display-popup -E "echo hello" renders content (not blank)
  • Ctrl+V paste of 40+ lines in VS Code terminal arrives as single chunk
  • Right-click paste in VS Code arrives as single chunk, matching Ctrl+V formatting
  • Highlight + right-click copies without pasting or inserting carriage returns
  • Paste containing non-ASCII (em-dashes, backticks) arrives as single chunk
  • ESC key works normally (not swallowed or delayed)
  • Normal typing, IME input, and Ctrl+V in Windows Terminal remain unaffected

ToxMox added 5 commits March 16, 2026 22:25
1. display-message: Set app.status_message so messages appear on the
   status bar (tmux parity). The rendering infrastructure existed but
   the DisplayMessage handler never populated the field.

2. Mouse client tracking: Add client_id to all mouse/scroll CtrlReqs.
   On mouse events, update latest_client_id and recompute effective
   window size before processing coordinates. Fixes status bar clicks
   breaking when multiple clients have different terminal sizes.

3. Popup race condition: Add 50ms delay after PTY spawn before entering
   PopupMode, giving the reader thread time to populate the vt100
   parser. Prevents empty/invisible popups for fast commands.

4. Paste coalescing: Increase paste chunk size from 512 to 4096 bytes.
   Add 50ms coalescing window for bracket paste events in embedded mode
   to merge rapid successive paste fragments from VS Code's ConPTY.
Three fixes for paste fragmentation:

1. Ctrl+V paste: Don't flush immediately on paste_confirmed (Ctrl+V
   Release). ConPTY is still injecting chars. Promote to stage2 and
   let growth detection wait for delivery to finish.

2. Right-click paste: After sending clipboard content, suppress
   paste_pend for 2s to discard duplicate key events that VS Code
   injects through ConPTY.

3. Non-ASCII paste: Allow large (>=20 char) buffers with non-ASCII
   into stage2 — they're pastes, not IME. Only apply IME exclusion
   for small (3-19 char) buffers.

Also adds bracket paste detection state machine and coalescing to
client-server mode (ported from embedded mode in app.rs).
When highlighting text and right-clicking to copy, VS Code's "smart
copy/paste" also injects clipboard content as key events through
ConPTY. Suppress paste_pend for 2s after the copy-on-right-click
path to discard those duplicate key events.
ConPTY strips bracket paste markers when VTI is disabled, so the
state machine never fires in client-server mode. It was added before
the real paste fix (paste_pend improvements) was identified. Removing
it fixes ESC key being swallowed by the detector's 5ms buffering.
- Normalize \r\n to \n in clipboard reads (Windows clipboard preserves
  \r, causing double-spaced pastes)
- Trim trailing newlines from clipboard paste to avoid blank lines
- During paste suppression window, discard text key events (Char,
  Enter, Tab) that VS Code injects as ConPTY echoes — prevents
  carriage returns leaking into prompt after highlight+right-click copy
@ToxMox

ToxMox commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

Apologies for the extra commits after opening — I was still testing edge cases when I submitted. The last two commits fix:

  • ESC key regression: Removed the bracket paste detection state machine from client mode. ConPTY strips bracket paste markers when VTI is disabled, so it never fired — but it was buffering ESC keys for 5ms and sometimes swallowing them.
  • Clipboard line endings: Right-click paste was double-spacing text because the Windows clipboard preserves \r\n. Now normalizes to \n and trims trailing newlines. Also tightened the paste suppression window to discard leaked Enter/Tab key events that VS Code injects after a right-click copy.

Should be stable now — tested right-click paste, Ctrl+V paste, highlight+right-click copy, ESC, and normal typing.

@psmux

psmux commented Mar 18, 2026

Copy link
Copy Markdown
Owner

Hey @ToxMox, thanks for the incredibly thorough analysis! I went through each of the 6 bugs you identified and can confirm every single one is real — great detective work.

I've independently fixed all 6 in commit ffcf3d3:

1. display-message — Added a set_status_bar flag to the DisplayMessage CtrlReq. The handler now sets app.status_message when -p isn't used. The list-sessions -F path passes false so it doesn't pollute the status bar.

2. Mouse multi-client — Added client_id (u64) to all 10 mouse/scroll CtrlReq variants. The server now updates latest_client_id before processing each mouse event, so coordinate resolution uses the clicking client's dimensions.

3. Popup race — Added a 50ms thread::sleep after PTY spawn + reader thread creation, before setting the popup mode. This gives the reader thread time to process initial output before the first frame serializes.

4. Paste fragmentation — Added growth detection to the stage2 timeout: if paste_pend.len() increased since the last 300ms check, the timer resets instead of splitting. Also added a path for large non-ASCII buffers (≥20 chars) to enter stage2 instead of being treated as IME input.

5. Right-click copy suppression — After a right-click copy, text key events are now suppressed for 2 seconds via paste_suppress_until. This prevents VS Code's ConPTY from injecting duplicate clipboard content.

6. Clipboard CRLFread_from_system_clipboard() now normalizes \r\n\n before returning.

I'm closing this PR since the fixes are already on master, but I really appreciate you taking the time to identify and document all of these — the root cause analysis in your PR description was spot-on. Cheers!

psmux pushed a commit that referenced this pull request Mar 18, 2026
… right-click, CRLF)

Fix 6 bugs in client-server mode reported in #118:

1. display-message: handler now sets app.status_message when not using
   -p flag, so messages actually appear on the status bar.

2. Mouse multi-client tracking: all 10 mouse/scroll CtrlReq variants now
   carry client_id. Server updates latest_client_id on mouse events so
   coordinate processing uses the clicking client's dimensions.

3. Popup race condition: added 50ms delay after PTY spawn in display-popup
   so the reader thread can populate the vt100 parser before the first
   frame is serialized to clients. Prevents blank popups with fast commands.

4. Paste fragmentation: added growth detection to stage2 timeout. If the
   paste_pend buffer grew since the last check, the timeout is extended
   instead of splitting the paste. Large non-ASCII buffers (>=20 chars)
   now enter stage2 instead of being flushed as IME input.

5. Right-click copy paste suppression: after right-click copy, text key
   events are suppressed for 2s to prevent VS Code ConPTY from injecting
   duplicate clipboard content as key events.

6. Clipboard CRLF normalization: read_from_system_clipboard() now normalizes
   Windows CRLF line endings to LF, preventing double-spaced paste output.

Also includes the parse_command_line \\\\ escape fix from #123.
psmux pushed a commit that referenced this pull request Mar 18, 2026
Fix 6 bugs in client-server mode reported by @ToxMox in #118:

1. display-message: handler now sets app.status_message when not using
   -p flag, so messages actually appear on the status bar.

2. Mouse multi-client tracking: all 10 mouse/scroll CtrlReq variants now
   carry client_id. Server updates latest_client_id on mouse events so
   coordinate processing uses the clicking client's dimensions.

3. Popup race condition: added 50ms delay after PTY spawn in display-popup
   so the reader thread can populate the vt100 parser before the first
   frame is serialized to clients. Prevents blank popups with fast commands.

4. Paste fragmentation: added growth detection to stage2 timeout. If the
   paste_pend buffer grew since the last check, the timeout is extended
   instead of splitting the paste. Large non-ASCII buffers (>=20 chars)
   now enter stage2 instead of being flushed as IME input.

5. Right-click copy paste suppression: after right-click copy, text key
   events are suppressed for 2s to prevent VS Code ConPTY from injecting
   duplicate clipboard content as key events.

6. Clipboard CRLF normalization: read_from_system_clipboard() now normalizes
   Windows CRLF line endings to LF, preventing double-spaced paste output.

Also fixes parse_command_line backslash escape reported by @schgoo in #123.
The parser now recognises \\ as an escape sequence inside double quotes
(producing a single \), preventing the closing quote from being consumed
when the client sends send-text "\\".

Co-investigated-by: @ToxMox (#118)
Co-investigated-by: @schgoo (#123)
@psmux psmux closed this Mar 18, 2026
ohboyftw pushed a commit to ohboyftw/psmux that referenced this pull request Mar 19, 2026
Brings 17 upstream commits into ohboy-builds:
- fix: warm claim race condition (psmux#136)
- fix: client_prefix flag, window_zoomed_flag (psmux#125, psmux#126)
- fix: Shift+Enter/Ctrl+Enter modifiers, paste normalization (psmux#131, psmux#132)
- fix: backslash escape, 6 client-server bugs (psmux#118, psmux#123)
- fix: -f global option (psmux#119)
- fix: set-hook replace/remove, zoomed navigation wrap (psmux#133, psmux#134)
- fix: TERM env var mapping, hyphenated option leak (psmux#137)
- feat: bell/activity/silence monitoring, allow-rename, update-environment
- feat: vim-style bind-key C-hjkl pane navigation (psmux#130)
- feat: XDG plugin path support (psmux#135)
- feat: auto-generate changelog in release workflow
- refactor: move Rust tests to tests-rs/ directory
- refactor: rich test dashboard

Also applies ohboy-builds stashed changes:
- Remove claude-code-fix-tty (Claude Code now auto-detects $TMUX)
- Migrate env::set_var to safe crate::util::set_env wrappers (Rust 1.83)
- Selection clamp to pane boundaries in copy mode
- Fix clippy warnings in forked crates (vt100-psmux, portable-pty-psmux)
souhaiebtar pushed a commit to souhaiebtar/psmux that referenced this pull request Mar 20, 2026
Fix 6 bugs in client-server mode reported by @ToxMox in psmux#118:

1. display-message: handler now sets app.status_message when not using
   -p flag, so messages actually appear on the status bar.

2. Mouse multi-client tracking: all 10 mouse/scroll CtrlReq variants now
   carry client_id. Server updates latest_client_id on mouse events so
   coordinate processing uses the clicking client's dimensions.

3. Popup race condition: added 50ms delay after PTY spawn in display-popup
   so the reader thread can populate the vt100 parser before the first
   frame is serialized to clients. Prevents blank popups with fast commands.

4. Paste fragmentation: added growth detection to stage2 timeout. If the
   paste_pend buffer grew since the last check, the timeout is extended
   instead of splitting the paste. Large non-ASCII buffers (>=20 chars)
   now enter stage2 instead of being flushed as IME input.

5. Right-click copy paste suppression: after right-click copy, text key
   events are suppressed for 2s to prevent VS Code ConPTY from injecting
   duplicate clipboard content as key events.

6. Clipboard CRLF normalization: read_from_system_clipboard() now normalizes
   Windows CRLF line endings to LF, preventing double-spaced paste output.

Also fixes parse_command_line backslash escape reported by @schgoo in psmux#123.
The parser now recognises \\ as an escape sequence inside double quotes
(producing a single \), preventing the closing quote from being consumed
when the client sends send-text "\\".

Co-investigated-by: @ToxMox (psmux#118)
Co-investigated-by: @schgoo (psmux#123)
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