截图模块初始化
This commit is contained in:
@@ -83,30 +83,28 @@ pub fn show_popup(app: &AppHandle) {
|
||||
let w = 380.0_f64;
|
||||
let h = 460.0_f64;
|
||||
|
||||
// 获取屏幕工作区(物理像素,Per-Monitor DPI V2)
|
||||
let (screen_w, screen_h) = get_work_area().unwrap_or((1920.0, 1080.0));
|
||||
// 获取光标所在显示器的工作区(物理像素,与 get_cursor_pos 同一坐标系)
|
||||
let (wa_left, wa_top, wa_right, wa_bottom) = get_work_area_at_point(mx, my)
|
||||
.unwrap_or((0, 0, 1920, 1040));
|
||||
|
||||
// 获取 scale factor,将窗口逻辑尺寸换算为物理像素用于边界裁剪
|
||||
let scale = app
|
||||
.get_webview_window(POPUP_LABEL)
|
||||
.as_ref()
|
||||
.and_then(|w| w.scale_factor().ok())
|
||||
.or_else(|| {
|
||||
app.get_webview_window("main")
|
||||
.and_then(|w| w.scale_factor().ok())
|
||||
})
|
||||
.unwrap_or(1.0);
|
||||
// 获取光标所在显示器的 DPI,将物理坐标转为逻辑坐标(DIP)
|
||||
let dpi = get_dpi_for_point(mx, my).unwrap_or(96);
|
||||
let scale = dpi as f64 / 96.0;
|
||||
|
||||
let w_phys = (w * scale) as i32;
|
||||
let h_phys = (h * scale) as i32;
|
||||
let mx_l = mx as f64 / scale;
|
||||
let my_l = my as f64 / scale;
|
||||
let wa_left_l = wa_left as f64 / scale;
|
||||
let wa_top_l = wa_top as f64 / scale;
|
||||
let wa_right_l = wa_right as f64 / scale;
|
||||
let wa_bottom_l = wa_bottom as f64 / scale;
|
||||
|
||||
// 物理坐标 clamping
|
||||
let x = mx.max(0).min(screen_w as i32 - w_phys);
|
||||
let y = my.max(0).min(screen_h as i32 - h_phys);
|
||||
// 逻辑坐标 clamping(窗口尺寸 w/h 也是逻辑像素)
|
||||
let x = mx_l.max(wa_left_l).min(wa_right_l - w);
|
||||
let y = my_l.max(wa_top_l).min(wa_bottom_l - h);
|
||||
|
||||
// 窗口已存在:移动 + 显示 + 请求焦点
|
||||
if let Some(win) = app.get_webview_window(POPUP_LABEL) {
|
||||
let _ = win.set_position(tauri::Position::Physical(tauri::PhysicalPosition {
|
||||
let _ = win.set_position(tauri::Position::Logical(tauri::LogicalPosition {
|
||||
x,
|
||||
y,
|
||||
}));
|
||||
@@ -126,7 +124,7 @@ pub fn show_popup(app: &AppHandle) {
|
||||
)
|
||||
.title("剪贴板")
|
||||
.inner_size(w, h)
|
||||
.position(x as f64 / scale, y as f64 / scale)
|
||||
.position(x, y)
|
||||
.decorations(false)
|
||||
.transparent(true)
|
||||
.shadow(true)
|
||||
@@ -233,7 +231,7 @@ mod win_api {
|
||||
use windows_sys::Win32::Foundation::POINT;
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{GetCursorPos, SystemParametersInfoW, SPI_GETWORKAREA};
|
||||
|
||||
/// 获取鼠标位置(屏幕坐标,逻辑像素)
|
||||
/// 获取鼠标位置(屏幕坐标,物理像素)
|
||||
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
let mut pt = POINT { x: 0, y: 0 };
|
||||
unsafe {
|
||||
@@ -245,7 +243,7 @@ mod win_api {
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取屏幕工作区(排除任务栏,逻辑像素)
|
||||
/// 获取主屏工作区尺寸(排除任务栏,物理像素)
|
||||
pub fn get_work_area() -> Option<(f64, f64)> {
|
||||
use windows_sys::Win32::Foundation::RECT;
|
||||
let mut rect = RECT { left: 0, top: 0, right: 0, bottom: 0 };
|
||||
@@ -257,12 +255,52 @@ mod win_api {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取指定点所在显示器的工作区(排除任务栏),返回 (left, top, right, bottom) 物理像素。
|
||||
/// 使用 MonitorFromPoint 支持多显示器环境。
|
||||
pub fn get_work_area_at_point(x: i32, y: i32) -> Option<(i32, i32, i32, i32)> {
|
||||
use windows_sys::Win32::Graphics::Gdi::{
|
||||
GetMonitorInfoW, MonitorFromPoint, MONITORINFO, MONITOR_DEFAULTTONEAREST,
|
||||
};
|
||||
let pt = POINT { x, y };
|
||||
let hmon = unsafe { MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST) };
|
||||
let mut mi: MONITORINFO = unsafe { std::mem::zeroed() };
|
||||
mi.cbSize = std::mem::size_of::<MONITORINFO>() as u32;
|
||||
unsafe {
|
||||
if GetMonitorInfoW(hmon, &mut mi) != 0 {
|
||||
let rc = mi.rcWork;
|
||||
Some((rc.left, rc.top, rc.right, rc.bottom))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取指定点所在显示器的有效 DPI。
|
||||
/// scale factor = dpi / 96。
|
||||
pub fn get_dpi_for_point(x: i32, y: i32) -> Option<u32> {
|
||||
use windows_sys::Win32::Graphics::Gdi::{MonitorFromPoint, MONITOR_DEFAULTTONEAREST};
|
||||
use windows_sys::Win32::UI::HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI};
|
||||
let pt = POINT { x, y };
|
||||
let hmon = unsafe { MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST) };
|
||||
let mut dpi_x: u32 = 0;
|
||||
let mut dpi_y: u32 = 0;
|
||||
unsafe {
|
||||
if GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == 0 {
|
||||
Some(dpi_x)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
mod win_api {
|
||||
pub fn get_cursor_pos() -> Option<(i32, i32)> { None }
|
||||
pub fn get_work_area() -> Option<(f64, f64)> { None }
|
||||
pub fn get_work_area_at_point(_x: i32, _y: i32) -> Option<(i32, i32, i32, i32)> { None }
|
||||
pub fn get_dpi_for_point(_x: i32, _y: i32) -> Option<u32> { None }
|
||||
}
|
||||
|
||||
pub use win_api::{get_cursor_pos, get_work_area};
|
||||
pub use win_api::{get_cursor_pos, get_work_area, get_work_area_at_point, get_dpi_for_point};
|
||||
|
||||
@@ -232,10 +232,19 @@ pub fn dib_info(dib: &[u8]) -> Option<(u32, u32)> {
|
||||
Some((width as u32, height.abs() as u32))
|
||||
}
|
||||
|
||||
/// 将 CF_DIB 字节转换为 PNG 字节(仅支持 24/32bpp BI_RGB)。
|
||||
/// 将 CF_DIB 字节转换为 PNG 字节(支持 24/32bpp BI_RGB / BI_BITFIELDS)。
|
||||
pub fn dib_to_png(dib: &[u8]) -> Option<Vec<u8>> {
|
||||
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;
|
||||
}
|
||||
@@ -244,7 +253,14 @@ pub fn dib_to_png(dib: &[u8]) -> Option<Vec<u8>> {
|
||||
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 {
|
||||
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 {
|
||||
@@ -269,7 +285,8 @@ pub fn dib_to_png(dib: &[u8]) -> Option<Vec<u8>> {
|
||||
rgba[dp] = dib[sp + 2]; // R
|
||||
rgba[dp + 1] = dib[sp + 1]; // G
|
||||
rgba[dp + 2] = dib[sp]; // B
|
||||
rgba[dp + 3] = 255; // A
|
||||
// 32bpp 保留 alpha 通道(截图工具常用);24bpp 不透明
|
||||
rgba[dp + 3] = if bpp == 32 { dib[sp + 3] } else { 255 };
|
||||
}
|
||||
}
|
||||
let mut buf = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user