use objc2_app_kit::{NSPasteboard, NSPasteboardTypeString};
use objc2_foundation::NSString;
#[allow(dead_code, reason = "wired into AutoIt when the backend is selected")]
fn general() -> objc2::rc::Retained<NSPasteboard> {
NSPasteboard::generalPasteboard()
}
#[allow(dead_code, reason = "wired into AutoIt when the backend is selected")]
pub(crate) fn get() -> crate::Result<String> {
let pb = general();
let text = unsafe { pb.stringForType(NSPasteboardTypeString) };
Ok(text.map(|s| s.to_string()).unwrap_or_default())
}
#[allow(dead_code, reason = "wired into AutoIt when the backend is selected")]
pub(crate) fn put(s: &str) -> crate::Result<()> {
let pb = general();
unsafe {
pb.clearContents();
let ns = NSString::from_str(s);
pb.setString_forType(&ns, NSPasteboardTypeString);
}
Ok(())
}
#[allow(dead_code, reason = "wired into AutoIt when the backend is selected")]
pub(crate) fn sequence() -> Option<u32> {
let pb = general();
let count = pb.changeCount();
Some(count as u32)
}
#[cfg(test)]
mod tests {
use super::*;
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
struct Preserve {
original: String,
_guard: std::sync::MutexGuard<'static, ()>,
}
impl Preserve {
fn new() -> Self {
let guard = LOCK.lock().unwrap_or_else(|e| e.into_inner());
Self {
original: get().unwrap_or_default(),
_guard: guard,
}
}
}
impl Drop for Preserve {
fn drop(&mut self) {
let _ = put(&self.original);
}
}
#[test]
fn text_round_trips_including_non_ascii() {
let _p = Preserve::new();
for s in ["", "plain", "Ünïcödé ãõç — 1.234,56", "ção ãõç", "多字节"] {
put(s).unwrap();
assert_eq!(get().unwrap(), s, "round trip failed for {s:?}");
}
}
#[test]
fn the_change_count_moves_on_every_write() {
let _p = Preserve::new();
let before = sequence().expect("macOS always has a change count");
put("first").unwrap();
let after_first = sequence().unwrap();
assert_ne!(after_first, before, "a write must bump the counter");
put("first").unwrap();
let after_same = sequence().unwrap();
assert_ne!(
after_same, after_first,
"rewriting identical text must still bump the counter"
);
}
#[test]
fn reading_does_not_bump_the_counter() {
let _p = Preserve::new();
put("stable").unwrap();
let before = sequence().unwrap();
for _ in 0..3 {
let _ = get().unwrap();
}
assert_eq!(
sequence().unwrap(),
before,
"reading must not count as a change"
);
}
}