托盘
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
//! 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<u8>, width: u32, height: u32 },
|
||||
Files(Vec<String>),
|
||||
}
|
||||
|
||||
/// 读取剪贴板(需在 monitor 线程或命令线程调用)。OpenClipboard 失败返回 None。
|
||||
pub fn read_clipboard() -> Option<ClipData> {
|
||||
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<ClipData> {
|
||||
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<ClipData> {
|
||||
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<ClipData> {
|
||||
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<u16> = 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<u16> = 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)。
|
||||
pub fn dib_to_png(dib: &[u8]) -> Option<Vec<u8>> {
|
||||
use image::codecs::png::PngEncoder;
|
||||
use image::ImageEncoder;
|
||||
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 || compression != 0 {
|
||||
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
|
||||
rgba[dp + 3] = 255; // A
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user