Skip to content

gpu display: sync the clipboard between the guest and the viewer - #3

Open
ya-luotao wants to merge 2 commits into
display-frame-pathfrom
display-clipboard
Open

gpu display: sync the clipboard between the guest and the viewer#3
ya-luotao wants to merge 2 commits into
display-frame-pathfrom
display-clipboard

Conversation

@ya-luotao

Copy link
Copy Markdown
Owner

Stacks on #1 (base display-frame-path; retarget to gpu-m0 once #1 merges). Guest side: ya-luotao/msb-omarchy clipboard-agent.

Text copied in the guest's Wayland session reaches the macOS pasteboard and back for as long as msb display is open.

  • The display server owns an in-process vsock backend (gpu_display/clipboard.rs) on port 5910 and registers it whenever it starts, independent of the user's --vsock routes (VmBuilder::vsock() adds routes rather than replacing them; the vsock device is present without --vsock because TSI rides on it).
  • Wire format with the guest agent: newline-delimited JSON, {"t":"set","mime":"text/plain;charset=utf-8","data":"<base64>"} both ways; mime leaves room for images. The same value reaches the viewer as ServerMsg::Clipboard / ViewerMsg::Clipboard (additive, wire-compatible).
  • One guest agent at a time (a new connection retires the old one with EOF), 16 MiB caps both ways, the last guest selection is replayed to a viewer that attaches later, nothing runs on the gpu worker thread.
  • Viewer: arboard; reads the pasteboard on Focused(true) and on key presses, remembers what it last sent/received so nothing bounces. MSB_DISPLAY_CLIPBOARD=0 turns the sync off (the host pasteboard is otherwise pushed into the sandbox on every focus/key press, and a guest can overwrite the Mac pasteboard).
  • 7 unit tests for the backend (line splitting, caps, replacement).

Verified end to end on the omarchy sandbox: printf final-check | wl-copy in the guest → pbpaste on the Mac prints it; ViewerMsg::Clipboard for hello-from-macwl-paste in the guest prints it; a viewer that attaches late receives the remembered guest selection. The pasteboard→viewer hop (arboard::get_text on focus) could not be driven from a script (no Automation permission) and fires on the first click into the window.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PN7mepn7ryjXupoHbQjFmR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T16:28:20.447666Z 16c6800 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6929e80d42

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/runtime/lib/vm.rs
let backend: std::sync::Arc<dyn msb_krun::backends::vsock::VsockPortBackend> =
server.clipboard_backend();
builder = builder.vsock(move |vsock| {
vsock.custom(crate::gpu_display::protocol::CLIPBOARD_VSOCK_PORT, backend)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject collisions with the clipboard vsock port

When a GPU-enabled sandbox already configures a stream route on host port 5910, this adds a second route for the same port. The vendored VmBuilder rejects duplicate stream ports during build(), so an otherwise valid existing sandbox configuration now fails to start whenever GPU display is enabled. Reserve and validate this port before constructing the builder, or choose a route that cannot collide with user configuration.

AGENTS.md reference: AGENTS.md:L124-L124

Useful? React with 👍 / 👎.

Comment on lines +388 to +392
self.sender.send(&ViewerMsg::Clipboard {
mime: TEXT_MIME.to_string(),
data: base64::engine::general_purpose::STANDARD.encode(&text),
});
self.last_host_text = Some(text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Replay host clipboard data after the guest connects

If the viewer polls the Mac pasteboard before the guest clipboard agent connects—a normal startup race for msb run --display—the server drops this message in send_to_guest, but the viewer still records the text as sent here. Later focus and key events suppress that same value, so the guest never receives the initial host clipboard unless the user changes it again. Retain and replay the latest host selection when the agent connects, or only mark it delivered after acknowledgement.

Useful? React with 👍 / 👎.

};
if text.is_empty()
|| self.last_host_text.as_deref() == Some(text.as_str())
|| self.last_guest_text.as_deref() == Some(text.as_str())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear stale guest-origin clipboard suppression

After the guest sends value A and the host subsequently sends value B, last_guest_text remains A because the guest agent intentionally does not echo B. If the user later copies A on the Mac, this condition suppresses it even though the guest currently holds B, preventing a legitimate clipboard update. Track the origin of the current pasteboard value or clear the guest marker once the host selection changes.

Useful? React with 👍 / 👎.

Comment on lines +285 to +286
fn shutdown(&self, _how: VsockShutdown) -> io::Result<()> {
self.retire();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the unclosed half of a vsock stream

When the guest requests only SHUT_RD or SHUT_WR, this ignores the direction and retires the entire current connection. The opposite half of the stream is still valid under the VsockStreamBackend contract, but subsequent reads report EOF and writes are discarded, so clipboard updates stop prematurely for agents that half-close. Track read/write closure separately and retire only after both directions close or the stream is dropped.

AGENTS.md reference: AGENTS.md:L124-L124

Useful? React with 👍 / 👎.

ya-luotao and others added 2 commits August 31, 2026 00:22
Text copied in the guest's Wayland session reaches the macOS pasteboard
and back, for as long as `msb display` is open.

The display server owns an in-process vsock backend on port 5910 and
registers it whenever it starts, independent of the user's `--vsock`
routes: `VmBuilder::vsock()` threads the same builder, so the extra
`.vsock()` call adds a route rather than replacing the existing ones.
The guest agent speaks newline-delimited JSON both ways, carrying the
selection as base64 with a `mime` field so images can be added later
without a new message; the same value reaches the viewer as
`ServerMsg::Clipboard` / `ViewerMsg::Clipboard`. Unknown JSON lines were
already skipped by both ends, so this stays wire-compatible.

The backend serves one guest at a time, like viewers: a new connection
retires the old stream, which then reports EOF rather than an empty read
(`Ok(0)` on a live stream would close it). Queues are capped at 16 MiB in
both directions, the last guest selection is replayed to a viewer that
attaches later, and nothing here touches the gpu worker thread, so the
`present_frame` contract is unchanged.

Loop prevention lives at the edges: the viewer remembers the last text it
sent and received, and reads the pasteboard on focus and on key presses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjNQH1XtX3TCGAKMoBNEw8
The pasteboard is pushed into the sandbox whenever the window takes focus
or a key is pressed, and the guest can replace what the Mac holds, so a
viewer on an untrusted image needs a switch. Anything other than unset or
`1` disables both directions: the pasteboard is never opened, so nothing
is read from it, and a guest selection is dropped before it is decoded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjNQH1XtX3TCGAKMoBNEw8

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 16c680032d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +14 to +15
/// Clipboard text is kept in sync with the Mac pasteboard while the window is
/// open. Set `MSB_DISPLAY_CLIPBOARD=0` to turn that off in both directions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Document the default-on clipboard trust boundary

Clipboard synchronization is enabled by default and allows an untrusted guest to replace the host pasteboard, but the new behavior and MSB_DISPLAY_CLIPBOARD=0 opt-out appear only in source-level CLI documentation; the published docs/ contain no mention of msb display clipboard behavior. Users following the documentation therefore cannot discover this security-relevant default or how to disable it, so add it to the relevant CLI/display documentation.

AGENTS.md reference: AGENTS.md:L321-L324

Useful? React with 👍 / 👎.

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.

1 participant