use uiautomation_derive::map_as;
use uiautomation_derive::EnumConvert;
use windows::Win32::Foundation::GlobalFree;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::HGLOBAL;
use windows::Win32::System::DataExchange::CloseClipboard;
use windows::Win32::System::DataExchange::EmptyClipboard;
use windows::Win32::System::DataExchange::EnumClipboardFormats;
use windows::Win32::System::DataExchange::GetClipboardData;
use windows::Win32::System::DataExchange::IsClipboardFormatAvailable;
use windows::Win32::System::DataExchange::OpenClipboard;
use windows::Win32::System::DataExchange::SetClipboardData;
use windows::Win32::System::Memory::GlobalAlloc;
use windows::Win32::System::Memory::GlobalLock;
use windows::Win32::System::Memory::GlobalSize;
use windows::Win32::System::Memory::GlobalUnlock;
use windows::Win32::System::Memory::GHND;
use windows::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow;
use windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;
use windows_core::HRESULT;
use windows_core::PWSTR;
use crate::log_error;
use crate::types::Handle;
use crate::Error;
use crate::Result;
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumConvert)]
#[map_as(windows::Win32::System::Ole::CLIPBOARD_FORMAT)]
pub enum ClipboardFormat {
BITMAP = 2u16,
DIB = 8u16,
DIBV5 = 17u16,
DIF = 5u16,
DSPBITMAP = 130u16,
DSPENHMETAFILE = 142u16,
DSPMETAFILEPICT = 131u16,
DSPTEXT = 129u16,
ENHMETAFILE = 14u16,
GDIOBJFIRST = 768u16,
GDIOBJLAST = 1023u16,
HDROP = 15u16,
LOCALE = 16u16,
METAFILEPICT = 3u16,
OEMTEXT = 7u16,
OWNERDISPLAY = 128u16,
PALETTE = 9u16,
PENDATA = 10u16,
PRIVATEFIRST = 512u16,
PRIVATELAST = 767u16,
RIFF = 11u16,
SYLK = 4u16,
TEXT = 1u16,
TIFF = 6u16,
UNICODETEXT = 13u16,
WAVE = 12u16,
}
const SNAPABLE_FORMATS: [ClipboardFormat; 8] = [ClipboardFormat::TEXT, ClipboardFormat::UNICODETEXT, ClipboardFormat::OEMTEXT, ClipboardFormat::DIB, ClipboardFormat::DIBV5,
ClipboardFormat::RIFF, ClipboardFormat::TIFF, ClipboardFormat::WAVE];
#[derive(Debug)]
pub struct Clipboard {
}
impl Clipboard {
const MAX_OPEN_TIMES: u32 = 10;
fn create(owner: Option<Handle>) -> Result<Self> {
unsafe { OpenClipboard(owner.map(|h| h.into()))? };
Ok(Self {})
}
fn attempt(owner: Option<Handle>) -> Result<Self> {
let mut times = Self::MAX_OPEN_TIMES;
loop {
let result = Self::create(owner);
if result.is_ok() || times <= 0 {
break result;
} else {
std::thread::sleep(std::time::Duration::from_millis(5));
times -= 1;
}
}
}
pub fn new() -> Result<Self> {
Self::create(None)
}
pub fn new_for(owner: Handle) -> Result<Self> {
Self::create(Some(owner))
}
pub fn open() -> Result<Self> {
let window = {
let mut wnd = unsafe { GetActiveWindow() };
if wnd.is_invalid() {
wnd = unsafe { GetDesktopWindow() };
}
if wnd.is_invalid() {
None
} else {
Some(Handle::from(wnd))
}
};
Self::attempt(window)
}
pub fn open_for(owner: Handle) -> Result<Self> {
Self::attempt(Some(owner))
}
pub fn empty(&self) -> Result<()> {
unsafe { EmptyClipboard()? };
Ok(())
}
pub fn is_format_available(&self, format: ClipboardFormat) -> Result<bool> {
let result = unsafe {
IsClipboardFormatAvailable(format as _)
};
match result {
Ok(_) => Ok(true),
Err(e) => {
if e.code().is_ok() {
Ok(false)
} else {
Err(e.into())
}
}
}
}
pub fn get_text(&self) -> Result<String> {
let text = unsafe {
IsClipboardFormatAvailable(ClipboardFormat::UNICODETEXT as _)?;
let data = GetClipboardData(ClipboardFormat::UNICODETEXT as _)?;
if data.is_invalid() {
String::default()
} else {
let memory = GlobalData::from(data);
let data = memory.lock()?;
let str = PWSTR::from_raw(data.data as _);
str.to_string()?
}
};
Ok(text)
}
pub fn set_text(&self, text: &str) -> Result<()> {
let data: Vec<u16> = text.encode_utf16().chain(std::iter::once(0u16)).collect();
let data = PWSTR::from_raw(data.as_ptr() as *mut u16);
let data_len = unsafe { data.len() } + 1; let mut memory = GlobalData::alloc(data_len * 2)?;
let ret = unsafe {
let lock_data = memory.lock()?;
std::ptr::copy(data.as_ptr(), lock_data.data as _, data_len);
EmptyClipboard()?;
SetClipboardData(ClipboardFormat::UNICODETEXT as _, memory.handle())?
};
if ret.is_invalid() {
Err(Error::last_os_error())
} else {
memory.forget(); Ok(())
}
}
pub fn snapshot(&self, try_all_formats: bool) -> Result<Snapshot> {
let mut datas = Vec::new();
let mut format = 0u32;
let mut has_text = false;
let mut has_image = false;
loop {
format = unsafe { EnumClipboardFormats(format) };
if format == 0 {
break;
} else if SNAPABLE_FORMATS.iter().any(|f| format == *f as _) || (try_all_formats && format >= 0xC000 && format <= 0xFFFF) { if format == ClipboardFormat::DIB as _ || format == ClipboardFormat::DIBV5 as _ { if has_image {
continue;
} else {
has_image = true;
}
} else if format == ClipboardFormat::TEXT as _ || format == ClipboardFormat::UNICODETEXT as _ || format == ClipboardFormat::OEMTEXT as _ { if has_text {
continue;
} else {
has_text = true;
}
}
let data = unsafe { GetClipboardData(format)? };
let data = GlobalData::from(data);
if let Ok(data) = data.try_clone() {
datas.push((format, data));
}
}
}
Ok(Snapshot { datas })
}
pub fn restore(&self, snapshot: Snapshot) -> Result<()> {
unsafe { EmptyClipboard()? };
for (format, mut data) in snapshot.datas.into_iter() {
let ret = unsafe { SetClipboardData(format, data.handle())? };
if !ret.is_invalid() {
data.forget();
}
}
Ok(())
}
}
impl Drop for Clipboard {
fn drop(&mut self) {
if let Err(e) = unsafe { CloseClipboard() } {
log_error!("Failed to close clipboard: {}", e);
}
}
}
#[derive(Debug, Default)]
pub(crate) struct GlobalData {
memory: HGLOBAL,
owned: bool
}
impl GlobalData {
pub fn alloc(size: usize) -> Result<Self> {
let data = unsafe {
GlobalAlloc(GHND, size)?
};
Ok(Self { memory: data, owned: true })
}
pub fn handle(&self) -> Option<HANDLE> {
if self.memory.is_invalid() {
None
} else {
Some(unsafe { std::mem::transmute(self.memory) })
}
}
pub fn size(&self) -> usize {
unsafe {
GlobalSize(self.memory)
}
}
pub fn lock(&self) -> Result<GlobalGuard> {
let data = unsafe { GlobalLock(self.memory) };
if data.is_null() {
Err(Error::last_os_error())
} else {
Ok(GlobalGuard {
memory: self.memory,
data: data as *mut core::ffi::c_void
})
}
}
pub fn forget(&mut self) {
self.owned = false;
}
pub fn try_clone(&self) -> Result<Self> {
if self.memory.is_invalid() {
Ok(Self {
memory: self.memory,
owned: false
})
} else {
let src = self.lock()?;
let len = self.size();
let data = GlobalData::alloc(len)?;
let dst = data.lock()?;
unsafe { std::ptr::copy(src.data as _, dst.data as _, len) };
Ok(data)
}
}
}
impl From<HGLOBAL> for GlobalData {
fn from(data: HGLOBAL) -> Self {
Self { memory: data, owned: false }
}
}
impl Into<HGLOBAL> for GlobalData {
fn into(self) -> HGLOBAL {
let mut gd = self;
gd.owned = false;
gd.memory
}
}
impl From<HANDLE> for GlobalData {
fn from(data: HANDLE) -> Self {
let data: HGLOBAL = unsafe { std::mem::transmute(data) };
data.into()
}
}
impl AsRef<HGLOBAL> for GlobalData {
fn as_ref(&self) -> &HGLOBAL {
&self.memory
}
}
impl AsMut<HGLOBAL> for GlobalData {
fn as_mut(&mut self) -> &mut HGLOBAL {
&mut self.memory
}
}
impl Drop for GlobalData {
fn drop(&mut self) {
if self.owned && !self.memory.is_invalid() {
let _ = unsafe { GlobalFree(Some(self.memory)) };
self.owned = false
}
}
}
pub(crate) struct GlobalGuard{
memory: HGLOBAL,
pub data: *mut core::ffi::c_void
}
impl Drop for GlobalGuard {
fn drop(&mut self) {
if let Err(e) = unsafe { GlobalUnlock(self.memory) } {
if e.code() != HRESULT(0) {
log_error!("Failed to unlock global memory: {}", e);
}
}
}
}
#[derive(Debug, Default)]
pub struct Snapshot {
datas: Vec<(u32, GlobalData)>
}