osc: clipboard get/set via OSC 52

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I39637cb00313f1f9f83a4ac2977794246a6a6964
This commit is contained in:
raf 2026-06-25 09:30:20 +03:00
commit 28a49c5bbe
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
2 changed files with 196 additions and 10 deletions

View file

@ -689,11 +689,8 @@ impl App {
(!text.is_empty()).then_some(text)
}
/// Claim the clipboard (CLIPBOARD) with the current selection (Ctrl+Shift+C).
fn set_clipboard(&mut self, qh: &QueueHandle<App>) {
let Some(text) = self.selection_text() else {
return;
};
/// Take ownership of the CLIPBOARD selection, serving `text` to pasters.
fn claim_clipboard(&mut self, text: String, qh: &QueueHandle<App>) {
let Some(device) = self.data_device.as_ref() else {
return;
};
@ -705,11 +702,8 @@ impl App {
self.copy_source = Some(source);
}
/// Claim the primary selection with the current selection (select-to-copy).
fn set_primary(&mut self, qh: &QueueHandle<App>) {
let Some(text) = self.selection_text() else {
return;
};
/// Take ownership of the primary selection, serving `text` to pasters.
fn claim_primary(&mut self, text: String, qh: &QueueHandle<App>) {
let (Some(manager), Some(device)) =
(self.primary_manager.as_ref(), self.primary_device.as_ref())
else {
@ -721,6 +715,52 @@ impl App {
self.primary_source = Some(source);
}
/// Claim the clipboard (CLIPBOARD) with the current selection (Ctrl+Shift+C).
fn set_clipboard(&mut self, qh: &QueueHandle<App>) {
if let Some(text) = self.selection_text() {
self.claim_clipboard(text, qh);
}
}
/// Claim the primary selection with the current selection (select-to-copy).
fn set_primary(&mut self, qh: &QueueHandle<App>) {
if let Some(text) = self.selection_text() {
self.claim_primary(text, qh);
}
}
/// Act on the OSC 52 clipboard requests an application made: take ownership
/// of the selection it set, or answer a query with what we currently hold.
fn handle_clipboard_ops(&mut self, ops: Vec<crate::vt::ClipboardOp>) {
use crate::vt::ClipboardOp;
let qh = self.qh.clone();
for op in ops {
match op {
ClipboardOp::Set {
primary: true,
text,
} => self.claim_primary(text, &qh),
ClipboardOp::Set {
primary: false,
text,
} => self.claim_clipboard(text, &qh),
ClipboardOp::Query { primary } => {
let text = if primary {
&self.primary_clip
} else {
&self.clipboard
};
let kind = if primary { 'p' } else { 'c' };
let reply = format!(
"\x1b]52;{kind};{}\x07",
crate::vt::base64_encode(text.as_bytes())
);
self.write_to_pty(reply.as_bytes());
}
}
}
}
/// Paste the CLIPBOARD selection into the shell (Ctrl+Shift+V).
fn paste_clipboard(&mut self) {
let Some(offer) = self
@ -836,6 +876,10 @@ impl App {
self.window
.set_title(self.title.clone().unwrap_or_default());
}
let ops = session.term.take_clipboard_ops();
if !ops.is_empty() {
self.handle_clipboard_ops(ops);
}
self.needs_draw = true;
}