Files
Kigi-CLI/crates/codegen/kigi-pager-pty-harness/src/host_clipboard.rs
T
ZacharyZhang-NY a02b555e66 docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
2026-07-23 16:55:39 -04:00

197 lines
6.9 KiB
Rust

//! REAL host-clipboard plumbing shared by the OS-native paste e2e tests and
//! the `paste_latency` bench: `pbcopy` / `pbpaste` / `osascript` on macOS,
//! PowerShell `Set-Clipboard` / `Get-Clipboard` / WinForms `SetImage` on
//! Windows.
//!
//! Everything here mutates or reads the MACHINE-GLOBAL clipboard, so callers
//! must serialize against each other (e.g. `#[serial_test::serial]`) and
//! should hold a [`HostClipboardTextGuard`] to restore the prior text. CI
//! sessions without a usable clipboard are detected via
//! [`clipboard_roundtrip_works`] so tests can skip instead of fail.
//!
//! Compiles on every platform so cross-platform builds of the consumers stay
//! green (the bench gates at runtime); on unsupported hosts the tool spawns
//! simply fail.
use std::io::Write as _;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use anyhow::{Context, Result, bail};
#[cfg(not(target_os = "windows"))]
pub fn pbcopy(text: &str) -> Result<()> {
let mut cmd = Command::new("pbcopy");
cmd.stdin(Stdio::piped());
kigi_tty_utils::detach_std_command(&mut cmd);
let mut child = cmd.spawn().context("spawn pbcopy")?;
child
.stdin
.take()
.context("pbcopy stdin")?
.write_all(text.as_bytes())
.context("write pbcopy stdin")?;
let status = child.wait().context("wait pbcopy")?;
if !status.success() {
bail!("pbcopy exited with {status}");
}
Ok(())
}
/// Copy `text` to the host clipboard via PowerShell `Set-Clipboard`.
#[cfg(target_os = "windows")]
pub fn pbcopy(text: &str) -> Result<()> {
// Text travels over stdin, never inside the command line — no quoting.
let mut cmd = Command::new("powershell");
cmd.args([
"-NoProfile",
"-NonInteractive",
"-Command",
"Set-Clipboard -Value ([Console]::In.ReadToEnd())",
])
.stdin(Stdio::piped());
kigi_tty_utils::detach_std_command(&mut cmd);
let mut child = cmd.spawn().context("spawn powershell Set-Clipboard")?;
child
.stdin
.take()
.context("powershell stdin")?
.write_all(text.as_bytes())
.context("write powershell stdin")?;
let status = child.wait().context("wait powershell Set-Clipboard")?;
if !status.success() {
bail!("powershell Set-Clipboard exited with {status}");
}
Ok(())
}
/// Current host clipboard TEXT via `pbpaste` (`None` when unavailable).
#[cfg(not(target_os = "windows"))]
pub fn pbpaste() -> Option<String> {
let mut cmd = Command::new("pbpaste");
kigi_tty_utils::detach_std_command(&mut cmd);
let out = cmd.output().ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).into_owned())
}
/// Current host clipboard TEXT via PowerShell `Get-Clipboard` (`None` when
/// unavailable).
#[cfg(target_os = "windows")]
pub fn pbpaste() -> Option<String> {
let mut cmd = Command::new("powershell");
// Console::Out.Write avoids the trailing newline PowerShell's pipeline
// output would append (roundtrip checks compare exact text).
cmd.args([
"-NoProfile",
"-NonInteractive",
"-Command",
"$c = Get-Clipboard -Raw; if ($null -ne $c) { [Console]::Out.Write($c) }",
]);
kigi_tty_utils::detach_std_command(&mut cmd);
let out = cmd.output().ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).into_owned())
}
/// Put the PNG at `path` on the host clipboard as a raster (`«class PNGf»`).
#[cfg(not(target_os = "windows"))]
pub fn set_clipboard_png(path: &Path) -> Result<()> {
let script = format!(
"set the clipboard to (read (POSIX file \"{}\") as «class PNGf»)",
path.display()
);
let mut cmd = Command::new("osascript");
cmd.arg("-e").arg(&script);
kigi_tty_utils::detach_std_command(&mut cmd);
let status = cmd.status().context("spawn osascript")?;
if !status.success() {
bail!("osascript set-clipboard-PNG exited with {status}");
}
Ok(())
}
/// Put the PNG at `path` on the host clipboard as a raster via a WinForms
/// `Clipboard::SetImage` PowerShell one-liner.
#[cfg(target_os = "windows")]
pub fn set_clipboard_png(path: &Path) -> Result<()> {
use base64::Engine as _;
// WinForms Clipboard requires an STA thread; -EncodedCommand (base64 of
// UTF-16LE) sidesteps every cmd/PowerShell quoting layer, leaving only
// the PS single-quote escape for the embedded path.
let ps_path = path.display().to_string().replace('\'', "''");
let script = format!(
"Add-Type -AssemblyName System.Windows.Forms,System.Drawing; \
$img = [System.Drawing.Image]::FromFile('{ps_path}'); \
[System.Windows.Forms.Clipboard]::SetImage($img); \
$img.Dispose()"
);
let utf16: Vec<u8> = script
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let encoded = base64::engine::general_purpose::STANDARD.encode(utf16);
let mut cmd = Command::new("powershell");
cmd.args([
"-NoProfile",
"-NonInteractive",
"-STA",
"-EncodedCommand",
&encoded,
]);
kigi_tty_utils::detach_std_command(&mut cmd);
let status = cmd.status().context("spawn powershell SetImage")?;
if !status.success() {
bail!("powershell SetImage exited with {status}");
}
Ok(())
}
pub fn write_fixture_png(dir: &Path) -> Result<PathBuf> {
let path = dir.join("host_clipboard_fixture.png");
let buf: image::ImageBuffer<image::Rgba<u8>, Vec<u8>> =
image::ImageBuffer::from_pixel(64, 64, image::Rgba([200, 40, 120, 255]));
buf.save(&path)
.context("write host clipboard fixture png")?;
Ok(path)
}
/// Whether the host clipboard actually works in this session: sets a nonce
/// via the text helper and reads it back. False on clipboard-less CI sessions
/// (e.g. a Windows service session with no interactive desktop) so tests can
/// SKIP loudly instead of failing on environment.
pub fn clipboard_roundtrip_works() -> bool {
let nonce = format!("HOSTCLIPROUNDTRIP{}", std::process::id());
if pbcopy(&nonce).is_err() {
return false;
}
// trim_end: some clipboard tool chains append a trailing newline.
pbpaste().is_some_and(|t| t.trim_end() == nonce)
}
/// Best-effort save/restore of the host TEXT clipboard around a test or bench
/// run. Restores on drop (panic/unwind included). A prior IMAGE clipboard
/// cannot be restored — `pbpaste` only reads the text representation.
pub struct HostClipboardTextGuard {
prior: Option<String>,
}
impl HostClipboardTextGuard {
pub fn save() -> Self {
Self { prior: pbpaste() }
}
}
impl Drop for HostClipboardTextGuard {
fn drop(&mut self) {
if let Some(prior) = self.prior.take() {
// Non-panicking restore: a panicking test must still unwind cleanly.
let _ = pbcopy(&prior);
}
}
}