use kui_core::ClipboardMarks;
pub(crate) fn marks() -> ClipboardMarks {
platform::marks()
}
pub(crate) fn set_secret(clipboard: Option<&mut arboard::Clipboard>, text: String) {
platform::set_secret(clipboard, text);
}
#[cfg(target_os = "macos")]
mod platform {
use kui_core::ClipboardMarks;
use objc2_app_kit::{NSPasteboard, NSPasteboardTypeString};
use objc2_foundation::{NSArray, NSString};
const CONCEALED: &str = "org.nspasteboard.ConcealedType";
const TRANSIENT: &str = "org.nspasteboard.TransientType";
pub(super) fn marks() -> ClipboardMarks {
marks_of(&NSPasteboard::generalPasteboard())
}
pub(super) fn set_secret(_clipboard: Option<&mut arboard::Clipboard>, text: String) {
write_secret(&NSPasteboard::generalPasteboard(), &text);
}
pub(super) fn marks_of(pb: &NSPasteboard) -> ClipboardMarks {
let Some(types) = pb.types() else {
return ClipboardMarks::default();
};
let has = |name: &str| {
let name = NSString::from_str(name);
types.iter().any(|t| *t == *name)
};
ClipboardMarks {
concealed: has(CONCEALED),
transient: has(TRANSIENT),
}
}
pub(super) fn write_secret(pb: &NSPasteboard, text: &str) {
let concealed = NSString::from_str(CONCEALED);
let transient = NSString::from_str(TRANSIENT);
let string = unsafe { NSPasteboardTypeString };
let types = NSArray::from_slice(&[string, &*concealed, &*transient]);
unsafe { pb.declareTypes_owner(&types, None) };
pb.setString_forType(&NSString::from_str(text), string);
let empty = NSString::from_str("");
pb.setString_forType(&empty, &concealed);
pb.setString_forType(&empty, &transient);
}
#[cfg(test)]
#[test]
fn a_secret_written_is_read_back_marked() {
let pb = NSPasteboard::pasteboardWithUniqueName();
write_secret(&pb, "hunter2");
assert_eq!(marks_of(&pb), ClipboardMarks::SECRET);
let string = unsafe { NSPasteboardTypeString };
assert_eq!(
pb.stringForType(string).map(|s| s.to_string()).as_deref(),
Some("hunter2")
);
pb.clearContents();
pb.setString_forType(&NSString::from_str("a paragraph"), string);
assert_eq!(marks_of(&pb), ClipboardMarks::default());
let _: () = unsafe { objc2::msg_send![&*pb, releaseGlobally] };
}
}
#[cfg(target_os = "windows")]
mod platform {
use kui_core::ClipboardMarks;
use windows_sys::Win32::System::DataExchange::{
CloseClipboard, GetClipboardData, IsClipboardFormatAvailable, OpenClipboard,
RegisterClipboardFormatW,
};
use windows_sys::Win32::System::Memory::{GlobalLock, GlobalSize, GlobalUnlock};
fn format(name: &str) -> u32 {
let wide: Vec<u16> = name.encode_utf16().chain([0]).collect();
unsafe { RegisterClipboardFormatW(wide.as_ptr()) }
}
pub(super) fn marks() -> ClipboardMarks {
let monitor = format("ExcludeClipboardContentFromMonitorProcessing");
let history = format("CanIncludeInClipboardHistory");
let concealed = monitor != 0 && unsafe { IsClipboardFormatAvailable(monitor) } != 0;
let mut transient = false;
if history != 0
&& unsafe { IsClipboardFormatAvailable(history) } != 0
&& unsafe { OpenClipboard(0) } != 0
{
unsafe {
let h = GetClipboardData(history);
if h != 0 {
let mem = h as *mut core::ffi::c_void;
let p = GlobalLock(mem);
if !p.is_null() {
if GlobalSize(mem) >= 4 {
transient = p.cast::<u32>().read_unaligned() == 0;
}
GlobalUnlock(mem);
}
}
CloseClipboard();
}
}
ClipboardMarks {
concealed,
transient,
}
}
pub(super) fn set_secret(clipboard: Option<&mut arboard::Clipboard>, text: String) {
use arboard::SetExtWindows;
let Some(cb) = clipboard else { return };
let _ = cb
.set()
.exclude_from_monitoring()
.exclude_from_history()
.exclude_from_cloud()
.text(text);
}
}
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))
))]
mod platform {
use kui_core::ClipboardMarks;
pub(super) fn marks() -> ClipboardMarks {
ClipboardMarks::default()
}
pub(super) fn set_secret(clipboard: Option<&mut arboard::Clipboard>, text: String) {
use arboard::SetExtLinux;
let Some(cb) = clipboard else { return };
let _ = cb.set().exclude_from_history().text(text);
}
}
#[cfg(not(any(
target_os = "macos",
target_os = "windows",
all(
unix,
not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))
)
)))]
mod platform {
use kui_core::ClipboardMarks;
pub(super) fn marks() -> ClipboardMarks {
ClipboardMarks::default()
}
pub(super) fn set_secret(clipboard: Option<&mut arboard::Clipboard>, text: String) {
if let Some(cb) = clipboard {
let _ = cb.set_text(text);
}
}
}