perf: avoid JXA startup for macOS clipboard writes

This commit is contained in:
Valdemar Lorenzen
2026-10-13 11:05:37 +02:00
parent 8c5d218c11
commit b94739183d
4 changed files with 194 additions and 39 deletions
+190 -36
View File
@@ -15,20 +15,6 @@ function run() {
}
"#;
#[cfg(target_os = "macos")]
const MAC_WRITE: &str = r#"
ObjC.import('AppKit');
function run() {
var data = $.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile;
var text = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding);
if (text.isNil()) throw Error('Clipboard input is not UTF-8');
var pasteboard = $.NSPasteboard.generalPasteboard;
pasteboard.clearContents;
if (!pasteboard.setStringForType(text, $.NSPasteboardTypeString))
throw Error('Cannot write plain-text clipboard');
}
"#;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ClipboardMode {
System,
@@ -42,7 +28,7 @@ struct ClipboardCommand {
struct Commands {
reader: ClipboardCommand,
writer: ClipboardCommand,
writer: Option<ClipboardCommand>,
}
pub struct SystemClipboard {
@@ -72,10 +58,10 @@ impl SystemClipboard {
program: reader,
args: &[],
},
writer: ClipboardCommand {
writer: Some(ClipboardCommand {
program: writer,
args: &[],
},
}),
})),
timeout: COMMAND_TIMEOUT,
}
@@ -95,8 +81,13 @@ impl SystemClipboard {
}
let timeout = self.timeout;
let commands = self.commands.get_or_insert_with(select_commands);
let command = &commands.as_ref().map_err(Clone::clone)?.writer;
run(command, Some(text.as_bytes()), timeout).map(|_| ())
match &commands.as_ref().map_err(Clone::clone)?.writer {
Some(command) => run(command, Some(text.as_bytes()), timeout).map(|_| ()),
#[cfg(target_os = "macos")]
None => macos::write(text),
#[cfg(not(target_os = "macos"))]
None => Err("System clipboard is unsupported on this platform".into()),
}
}
}
@@ -117,19 +108,13 @@ fn executable(name: &str) -> Option<PathBuf> {
fn select_commands() -> Result<Commands, String> {
#[cfg(target_os = "macos")]
{
if let Some(program) = executable("osascript") {
return Ok(Commands {
reader: ClipboardCommand {
program: program.clone(),
args: &["-l", "JavaScript", "-e", MAC_READ],
},
writer: ClipboardCommand {
program,
args: &["-l", "JavaScript", "-e", MAC_WRITE],
},
});
}
Err("System clipboard requires osascript with JXA/AppKit on PATH".into())
Ok(Commands {
reader: ClipboardCommand {
program: executable("osascript").unwrap_or_else(|| "/usr/bin/osascript".into()),
args: &["-l", "JavaScript", "-e", MAC_READ],
},
writer: None,
})
}
#[cfg(target_os = "linux")]
{
@@ -140,10 +125,10 @@ fn select_commands() -> Result<Commands, String> {
program: reader,
args: &["--no-newline", "--type", "text"],
},
writer: ClipboardCommand {
writer: Some(ClipboardCommand {
program: writer,
args: &["--type", "text/plain;charset=utf-8"],
},
}),
});
}
}
@@ -154,10 +139,10 @@ fn select_commands() -> Result<Commands, String> {
program: program.clone(),
args: &["-selection", "clipboard", "-target", "UTF8_STRING", "-out"],
},
writer: ClipboardCommand {
writer: Some(ClipboardCommand {
program,
args: &["-selection", "clipboard", "-target", "UTF8_STRING", "-in"],
},
}),
});
}
}
@@ -169,6 +154,173 @@ fn select_commands() -> Result<Commands, String> {
}
}
#[cfg(target_os = "macos")]
mod macos {
use std::ffi::{c_char, c_void};
use std::ptr;
type Object = *const c_void;
#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern "C" {
fn CFStringCreateWithCString(
allocator: Object,
bytes: *const c_char,
encoding: u32,
) -> Object;
fn CFDataCreate(allocator: Object, bytes: *const u8, length: isize) -> Object;
fn CFRelease(object: Object);
}
#[link(name = "CoreServices", kind = "framework")]
unsafe extern "C" {
static kUTTypeUTF8PlainText: Object;
}
#[link(name = "ApplicationServices", kind = "framework")]
unsafe extern "C" {
fn PasteboardCreate(name: Object, pasteboard: *mut Object) -> i32;
fn PasteboardClear(pasteboard: Object) -> i32;
fn PasteboardPutItemFlavor(
pasteboard: Object,
item: usize,
flavor: Object,
data: Object,
flags: u32,
) -> i32;
}
struct Owned(Object);
impl Owned {
fn new(object: Object) -> Result<Self, String> {
if object.is_null() {
Err("Cannot allocate system clipboard data".into())
} else {
Ok(Self(object))
}
}
}
impl Drop for Owned {
fn drop(&mut self) {
// SAFETY: each handle owns one Core Foundation reference.
unsafe { CFRelease(self.0) };
}
}
fn check(status: i32) -> Result<(), String> {
if status == 0 {
Ok(())
} else {
Err(format!("System clipboard publication failed ({status})"))
}
}
fn write_to(name: Object, text: &str) -> Result<(), String> {
// SAFETY: framework constants and byte slices live throughout the calls.
unsafe {
let data = Owned::new(CFDataCreate(
ptr::null(),
text.as_ptr(),
text.len() as isize,
))?;
let mut board = ptr::null();
check(PasteboardCreate(name, &mut board))?;
let board = Owned::new(board)?;
// Prepare all allocations before changing clipboard contents.
check(PasteboardClear(board.0))?;
check(PasteboardPutItemFlavor(
board.0,
1,
kUTTypeUTF8PlainText,
data.0,
0,
))
}
}
pub(super) fn write(text: &str) -> Result<(), String> {
// SAFETY: the clipboard name is a process-lifetime, null-terminated literal.
let name = unsafe {
Owned::new(CFStringCreateWithCString(
ptr::null(),
c"com.apple.pasteboard.clipboard".as_ptr(),
0x0800_0100,
))?
};
write_to(name.0, text)
}
#[cfg(test)]
mod tests {
use super::*;
unsafe extern "C" {
fn PasteboardSynchronize(pasteboard: Object) -> u32;
fn PasteboardGetItemIdentifier(
pasteboard: Object,
index: isize,
item: *mut usize,
) -> i32;
fn PasteboardCopyItemFlavorData(
pasteboard: Object,
item: usize,
flavor: Object,
data: *mut Object,
) -> i32;
fn CFDataGetBytePtr(data: Object) -> *const u8;
fn CFDataGetLength(data: Object) -> isize;
}
#[test]
fn native_publication_preserves_plain_utf8_bytes_on_an_isolated_pasteboard() {
// A private pasteboard leaves the user's clipboard untouched.
let name = unsafe {
Owned::new(CFStringCreateWithCString(
ptr::null(),
c"vamp-native-clipboard-regression".as_ptr(),
0x0800_0100,
))
.unwrap()
};
let board = unsafe {
let mut board = ptr::null();
check(PasteboardCreate(name.0, &mut board)).unwrap();
Owned::new(board).unwrap()
};
for text in [
"",
"é e\u{301} 👨‍💻\n\0tail",
"{\\rtf1 plain text}",
"%!PS-Adobe-3.0\nplain",
] {
write_to(name.0, text).unwrap();
unsafe {
PasteboardSynchronize(board.0);
let mut item = 0;
check(PasteboardGetItemIdentifier(board.0, 1, &mut item)).unwrap();
let flavor = Owned::new(CFStringCreateWithCString(
ptr::null(),
c"public.utf8-plain-text".as_ptr(),
0x0800_0100,
))
.unwrap();
let mut data = ptr::null();
check(PasteboardCopyItemFlavorData(
board.0, item, flavor.0, &mut data,
))
.unwrap();
let data = Owned::new(data).unwrap();
let length = CFDataGetLength(data.0) as usize;
let bytes = if length == 0 {
&[]
} else {
std::slice::from_raw_parts(CFDataGetBytePtr(data.0), length)
};
assert_eq!(bytes, text.as_bytes());
}
}
}
}
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
fn run(_: &ClipboardCommand, _: Option<&[u8]>, _: Duration) -> Result<Vec<u8>, String> {
Err("System clipboard is unsupported on this platform".into())
@@ -514,6 +666,8 @@ mod tests {
.as_mut()
.unwrap()
.writer
.as_mut()
.unwrap()
.program = fixture.command(
"noisy",
&format!("/bin/dd if=/dev/zero bs=65536 count=8 >&2\nexec /bin/cat > {state}"),