use super::util;
use crate::clipboard::{ClipboardFormat, FormatId};
use std::{ffi::OsStr, os::windows::ffi::OsStrExt, ptr};
use windows::{
core::{PCWSTR, PWSTR},
Win32::{
Foundation::{HANDLE, HWND},
System::{
DataExchange::{
CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard, RegisterClipboardFormatW,
SetClipboardData,
},
Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
SystemServices::CF_UNICODETEXT,
},
},
};
#[derive(Debug, Clone, Default)]
pub struct Clipboard;
impl Clipboard {
pub fn write_text(&mut self, s: impl AsRef<str>) {
let s = s.as_ref();
let format: ClipboardFormat = s.into();
self.put_formats(&[format])
}
pub(crate) fn read_text(&self) -> Option<String> {
with_clipboard(|| unsafe {
let handle = GetClipboardData(CF_UNICODETEXT.0).unwrap_or_default();
if handle.is_invalid() {
None
} else {
let unic_str = PWSTR::from_raw(GlobalLock(handle.0) as *mut _);
let mut len = 0;
while *unic_str.0.offset(len) != 0 {
len += 1;
}
let utf16_slice = std::slice::from_raw_parts(unic_str.0, len as usize);
let result = String::from_utf16(utf16_slice);
if let Ok(result) = result {
GlobalUnlock(handle.0);
return Some(result);
}
None
}
})
.flatten()
}
pub(crate) fn put_formats(&mut self, formats: &[ClipboardFormat]) {
with_clipboard(|| unsafe {
EmptyClipboard();
for format in formats {
let handle = make_handle(format);
let format_id = match get_format_id(format.identifier) {
Some(id) => id,
None => {
#[cfg(debug_assertions)]
println!("failed to register clipboard format {}", &format.identifier);
continue;
}
};
if let Err(err) = SetClipboardData(format_id, handle) {
#[cfg(debug_assertions)]
println!(
"failed to set clipboard for fmt {}, error: {}",
&format.identifier, err
);
}
}
});
}
}
fn get_format_id(format: FormatId) -> Option<u32> {
if let Some((id, _)) = STANDARD_FORMATS.iter().find(|(_, s)| s == &format) {
return Some(*id);
}
match format {
ClipboardFormat::TEXT => Some(CF_UNICODETEXT.0),
other => register_identifier(other),
}
}
fn register_identifier(ident: &str) -> Option<u32> {
unsafe {
let clipboard_format = util::encode_wide(ident);
let pb_format = RegisterClipboardFormatW(PCWSTR::from_raw(clipboard_format.as_ptr()));
if pb_format == 0 {
#[cfg(debug_assertions)]
println!(
"failed to register clipboard format '{}'; error {}.",
ident,
windows::core::Error::from_win32().code().0
);
return None;
}
Some(pb_format)
}
}
unsafe fn make_handle(format: &ClipboardFormat) -> HANDLE {
HANDLE(if format.identifier == ClipboardFormat::TEXT {
let s: &OsStr = std::str::from_utf8_unchecked(&format.data).as_ref();
let wstr: Vec<u16> = s.encode_wide().chain(Some(0)).collect();
let handle = GlobalAlloc(GMEM_MOVEABLE, wstr.len() * std::mem::size_of::<u16>());
let locked = GlobalLock(handle) as *mut _;
ptr::copy_nonoverlapping(wstr.as_ptr(), locked, wstr.len());
GlobalUnlock(handle);
handle
} else {
let handle = GlobalAlloc(GMEM_MOVEABLE, format.data.len() * std::mem::size_of::<u8>());
let locked = GlobalLock(handle) as *mut _;
ptr::copy_nonoverlapping(format.data.as_ptr(), locked, format.data.len());
GlobalUnlock(handle);
handle
})
}
fn with_clipboard<V>(f: impl FnOnce() -> V) -> Option<V> {
unsafe {
if !OpenClipboard(HWND::default()).as_bool() {
return None;
}
let result = f();
CloseClipboard();
Some(result)
}
}
static STANDARD_FORMATS: &[(u32, &str)] = &[
(1, "CF_TEXT"),
(2, "CF_BITMAP"),
(3, "CF_METAFILEPICT"),
(4, "CF_SYLK"),
(5, "CF_DIF"),
(6, "CF_TIFF"),
(7, "CF_OEMTEXT"),
(8, "CF_DIB"),
(9, "CF_PALETTE"),
(10, "CF_PENDATA"),
(11, "CF_RIFF"),
(12, "CF_WAVE"),
(13, "CF_UNICODETEXT"),
(14, "CF_ENHMETAFILE"),
(15, "CF_HDROP"),
(16, "CF_LOCALE"),
(17, "CF_DIBV5"),
(0x0080, "CF_OWNERDISPLAY"),
(0x0081, "CF_DSPTEXT"),
(0x0082, "CF_DSPBITMAP"),
(0x0083, "CF_DSPMETAFILEPICT"),
(0x008E, "CF_DSPENHMETAFILE"),
(0x0200, "CF_PRIVATEFIRST"),
(0x02FF, "CF_PRIVATELAST"),
(0x0300, "CF_GDIOBJFIRST"),
(0x03FF, "CF_GDIOBJLAST"),
];