//! Win32 剪贴板读取与写回 //! //! 读取优先级:文本 > 图片 > 文件。写回支持三种格式。 //! 句柄类型说明(windows-sys 0.52): //! - HWND / HANDLE = isize //! - HGLOBAL / HLOCAL = *mut c_void //! - HDROP = isize(与 HANDLE 同) use windows_sys::Win32::System::DataExchange::{ CloseClipboard, EmptyClipboard, GetClipboardData, IsClipboardFormatAvailable, OpenClipboard, SetClipboardData, }; use windows_sys::Win32::System::Memory::{GlobalAlloc, GlobalLock, GlobalSize, GlobalUnlock, GMEM_MOVEABLE, GMEM_ZEROINIT}; use windows_sys::Win32::System::Ole::{CF_DIB, CF_HDROP, CF_UNICODETEXT}; use windows_sys::Win32::UI::Shell::DragQueryFileW; /// 剪贴板当前内容 pub enum ClipData { Text(String), Image { dib: Vec, width: u32, height: u32 }, Files(Vec), } /// 读取剪贴板(需在 monitor 线程或命令线程调用)。OpenClipboard 失败返回 None。 pub fn read_clipboard() -> Option { unsafe { if OpenClipboard(0) == 0 { return None; } let result = if IsClipboardFormatAvailable(CF_UNICODETEXT as u32) != 0 { read_text() } else if IsClipboardFormatAvailable(CF_DIB as u32) != 0 { read_image() } else if IsClipboardFormatAvailable(CF_HDROP as u32) != 0 { read_files() } else { None }; CloseClipboard(); result } } unsafe fn read_text() -> Option { let h = GetClipboardData(CF_UNICODETEXT as u32); // HANDLE = isize if h == 0 { return None; } let hglob = h as *mut core::ffi::c_void; // 实际是 HGLOBAL let ptr = GlobalLock(hglob) as *const u16; if ptr.is_null() { return None; } let mut len = 0usize; while *ptr.add(len) != 0 { len += 1; } let slice = core::slice::from_raw_parts(ptr, len); let s = String::from_utf16_lossy(slice); GlobalUnlock(hglob); if s.is_empty() { None } else { Some(ClipData::Text(s)) } } unsafe fn read_image() -> Option { let h = GetClipboardData(CF_DIB as u32); if h == 0 { return None; } let hglob = h as *mut core::ffi::c_void; let ptr = GlobalLock(hglob) as *const u8; if ptr.is_null() { return None; } let size = GlobalSize(hglob); let slice = core::slice::from_raw_parts(ptr, size); let dib = slice.to_vec(); GlobalUnlock(hglob); let (w, hgt) = dib_info(&dib).unwrap_or((0, 0)); Some(ClipData::Image { dib, width: w, height: hgt, }) } unsafe fn read_files() -> Option { let h = GetClipboardData(CF_HDROP as u32); // HANDLE = isize if h == 0 { return None; } let hdrop = h; // HDROP = isize let count = DragQueryFileW(hdrop, 0xFFFFFFFF, core::ptr::null_mut(), 0); let mut files = Vec::with_capacity(count as usize); for i in 0..count { let len = DragQueryFileW(hdrop, i, core::ptr::null_mut(), 0); if len == 0 { continue; } let mut buf = vec![0u16; (len as usize) + 1]; let got = DragQueryFileW(hdrop, i, buf.as_mut_ptr(), buf.len() as u32); let s = String::from_utf16_lossy(&buf[..got as usize]); files.push(s); } // 剪贴板拥有句柄,不调用 DragFinish if files.is_empty() { None } else { Some(ClipData::Files(files)) } } // ===== 写回剪贴板(copy_back) ===== /// 写回文本。失败返回 false。写回会清空其他格式。 pub fn write_text(s: &str) -> bool { unsafe { if OpenClipboard(0) == 0 { return false; } let r = write_text_inner(s); CloseClipboard(); r } } unsafe fn write_text_inner(s: &str) -> bool { if EmptyClipboard() == 0 { return false; } let mut utf16: Vec = s.encode_utf16().collect(); utf16.push(0); // null terminator let byte_len = utf16.len() * 2; let hglob = GlobalAlloc(GMEM_MOVEABLE, byte_len); if hglob.is_null() { return false; } let ptr = GlobalLock(hglob) as *mut u16; if ptr.is_null() { return false; } core::ptr::copy_nonoverlapping(utf16.as_ptr(), ptr, utf16.len()); GlobalUnlock(hglob); SetClipboardData(CF_UNICODETEXT as u32, hglob as isize) != 0 } /// 写回图片(CF_DIB 原始字节)。 pub fn write_dib(dib: &[u8]) -> bool { unsafe { if OpenClipboard(0) == 0 { return false; } let r = (|| { if EmptyClipboard() == 0 { return false; } let hglob = GlobalAlloc(GMEM_MOVEABLE, dib.len()); if hglob.is_null() { return false; } let ptr = GlobalLock(hglob) as *mut u8; if ptr.is_null() { return false; } core::ptr::copy_nonoverlapping(dib.as_ptr(), ptr, dib.len()); GlobalUnlock(hglob); SetClipboardData(CF_DIB as u32, hglob as isize) != 0 })(); CloseClipboard(); r } } /// 写回文件列表(CF_HDROP)。 pub fn write_files(paths: &[String]) -> bool { unsafe { if OpenClipboard(0) == 0 { return false; } let r = (|| { if EmptyClipboard() == 0 { return false; } // DROPFILES 头部 20 字节:pFiles(u32) + pt.x(i32) + pt.y(i32) + fNC(i32) + fWide(i32) const DF_SIZE: usize = 20; let mut wide: Vec = Vec::new(); for p in paths { wide.extend(p.encode_utf16()); wide.push(0); // 每条字符串 null 结尾 } wide.push(0); // 末尾额外 null 表示列表结束 let total = DF_SIZE + wide.len() * 2; let hglob = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, total); if hglob.is_null() { return false; } let ptr = GlobalLock(hglob) as *mut u8; if ptr.is_null() { return false; } let offset = DF_SIZE as u32; let off_bytes = offset.to_ne_bytes(); core::ptr::copy_nonoverlapping(off_bytes.as_ptr(), ptr, 4); // pFiles let fwide: u32 = 1; let fwide_bytes = fwide.to_ne_bytes(); core::ptr::copy_nonoverlapping(fwide_bytes.as_ptr(), ptr.add(16), 4); // fWide = 1 let str_ptr = ptr.add(DF_SIZE) as *mut u16; core::ptr::copy_nonoverlapping(wide.as_ptr(), str_ptr, wide.len()); GlobalUnlock(hglob); SetClipboardData(CF_HDROP as u32, hglob as isize) != 0 })(); CloseClipboard(); r } } // ===== DIB → PNG(用于预览) ===== /// 从 DIB 字节解析宽高 pub fn dib_info(dib: &[u8]) -> Option<(u32, u32)> { if dib.len() < 40 { return None; } let width = i32::from_le_bytes([dib[4], dib[5], dib[6], dib[7]]); let height = i32::from_le_bytes([dib[8], dib[9], dib[10], dib[11]]); if width <= 0 { return None; } Some((width as u32, height.abs() as u32)) } /// 将 CF_DIB 字节转换为 PNG 字节(支持 24/32bpp BI_RGB / BI_BITFIELDS)。 pub fn dib_to_png(dib: &[u8]) -> Option> { use image::codecs::png::PngEncoder; use image::ImageEncoder; // 兜底:数据本身就是 PNG / JPEG(极少数来源直接存放压缩数据) if dib.len() >= 4 && &dib[0..4] == b"\x89PNG" { return Some(dib.to_vec()); } if dib.len() >= 3 && dib[0] == 0xFF && dib[1] == 0xD8 && dib[2] == 0xFF { return Some(dib.to_vec()); } if dib.len() < 40 { return None; } let header_size = u32::from_le_bytes([dib[0], dib[1], dib[2], dib[3]]); let width = i32::from_le_bytes([dib[4], dib[5], dib[6], dib[7]]); let height_raw = i32::from_le_bytes([dib[8], dib[9], dib[10], dib[11]]); let bpp = u16::from_le_bytes([dib[14], dib[15]]); let compression = u32::from_le_bytes([dib[16], dib[17], dib[18], dib[19]]); if width <= 0 { return None; } // 接受 BI_RGB(0) 和 BI_BITFIELDS(3)。 // Windows 截图工具(Win+Shift+S / 截图工具)常用 BI_BITFIELDS 标记 32bpp BGRA, // 像素数据本身未压缩,与 BI_RGB 解码方式一致。 // 拒绝 BI_RLE4/8(1/2) 和 BI_JPEG/PNG(4/5) 等真正压缩格式。 if compression != 0 && compression != 3 { return None; } if bpp != 24 && bpp != 32 { return None; } let height = height_raw.abs(); let top_down = height_raw < 0; let pixel_offset = header_size as usize; // 24/32bpp 无调色板 let row_size = ((bpp as u32 * width as u32 + 31) / 32) * 4; let bytes_per_pixel = (bpp / 8) as usize; let needed = pixel_offset + (row_size as usize) * (height as usize); if dib.len() < needed { return None; } let mut rgba = vec![0u8; (width as usize) * (height as usize) * 4]; for y in 0..height { let src_row = if top_down { y } else { height - 1 - y }; let src_off = pixel_offset + (src_row as usize) * (row_size as usize); for x in 0..width { let sp = src_off + (x as usize) * bytes_per_pixel; let dp = (y as usize) * (width as usize) * 4 + (x as usize) * 4; rgba[dp] = dib[sp + 2]; // R rgba[dp + 1] = dib[sp + 1]; // G rgba[dp + 2] = dib[sp]; // B // 32bpp 保留 alpha 通道(截图工具常用);24bpp 不透明 rgba[dp + 3] = if bpp == 32 { dib[sp + 3] } else { 255 }; } } let mut buf = Vec::new(); let enc = PngEncoder::new(&mut buf); enc.write_image(&rgba, width as u32, height as u32, image::ExtendedColorType::Rgba8) .ok()?; Some(buf) }