windows compile support

This commit is contained in:
2026-08-07 20:28:40 +08:00
parent a561a6fe94
commit 3b112b71da
4 changed files with 89 additions and 83 deletions
Generated
+37 -32
View File
@@ -425,6 +425,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.1" version = "1.11.1"
@@ -1899,7 +1905,7 @@ dependencies = [
"js-sys", "js-sys",
"log", "log",
"wasm-bindgen", "wasm-bindgen",
"windows-core 0.62.2", "windows-core 0.61.2",
] ]
[[package]] [[package]]
@@ -2030,6 +2036,19 @@ dependencies = [
"icu_properties", "icu_properties",
] ]
[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"moxcms",
"num-traits",
"png 0.18.1",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
@@ -2398,6 +2417,16 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "moxcms"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
dependencies = [
"num-traits",
"pxfm",
]
[[package]] [[package]]
name = "muda" name = "muda"
version = "0.19.2" version = "0.19.2"
@@ -3102,6 +3131,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "pxfm"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.39.4" version = "0.39.4"
@@ -5567,19 +5602,6 @@ dependencies = [
"windows-strings 0.4.2", "windows-strings 0.4.2",
] ]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement 0.60.2",
"windows-interface 0.59.3",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]] [[package]]
name = "windows-future" name = "windows-future"
version = "0.2.1" version = "0.2.1"
@@ -5675,15 +5697,6 @@ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "windows-strings" name = "windows-strings"
version = "0.1.0" version = "0.1.0"
@@ -5703,15 +5716,6 @@ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.45.0" version = "0.45.0"
@@ -6055,6 +6059,7 @@ dependencies = [
"ed25519-dalek", "ed25519-dalek",
"futures-util", "futures-util",
"getrandom 0.4.3", "getrandom 0.4.3",
"image",
"lazy_static", "lazy_static",
"objc2", "objc2",
"objc2-app-kit", "objc2-app-kit",
+12 -1
View File
@@ -41,7 +41,18 @@ wyd-common = { path = "../src-common" }
tokio = { version = "1.53.1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.53.1", features = ["macros", "rt-multi-thread"] }
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
windows = "0.58" image = { version = "0.25", default-features = false, features = ["png"] }
windows = { version = "0.58", features = [
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Gdi",
"Win32_Storage_FileSystem",
"Win32_System_ProcessStatus",
"Win32_System_Threading",
"Win32_UI_Accessibility",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging",
] }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6.3" objc2 = "0.6.3"
+3 -9
View File
@@ -184,24 +184,18 @@ async fn init_cursor_tracking_i(
println!("Device event handler created successfully"); println!("Device event handler created successfully");
println!("Setting up mouse move handler for event broadcasting..."); println!("Setting up mouse move handler for event broadcasting...");
// #[cfg(target_os = "windows")] #[cfg(not(target_os = "windows"))]
let scale_factor = monitor.scale_factor(); let scale_factor = monitor.scale_factor();
// The producer closure moves `tx` into it. // The producer closure moves `tx` into it.
// device_query runs this closure on its own thread. // device_query runs this closure on its own thread.
let _guard = device_state.on_mouse_move(move |position: &(i32, i32)| { let _guard = device_state.on_mouse_move(move |position: &(i32, i32)| {
// `device_query` crate appears to behave
// differently on Windows vs other platforms.
//
// It doesn't take into account the monitor scale
// factor on Windows, so we handle it manually.
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let raw = CursorPosition { let raw = CursorPosition {
x: position.0 as f64 / scale_factor, x: position.0 as f64,
y: position.1 as f64 / scale_factor, y: position.1 as f64,
}; };
// NOTE: Inclusion of scale factor was not necessary in the past, but now required. Confirm behaviours.
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
let raw = CursorPosition { let raw = CursorPosition {
x: position.0 as f64 * scale_factor, x: position.0 as f64 * scale_factor,
+37 -41
View File
@@ -1,10 +1,9 @@
use super::types::AppMetadata; use super::types::AppMeta;
use std::ffi::OsString; use std::ffi::OsString;
use std::iter; use std::iter;
use std::os::windows::ffi::OsStringExt; use std::os::windows::ffi::OsStringExt;
use std::path::Path; use std::path::Path;
use std::ptr; use std::ptr;
use tracing::{info, warn};
use windows::core::PCWSTR; use windows::core::PCWSTR;
use windows::Win32::Foundation::HWND; use windows::Win32::Foundation::HWND;
use windows::Win32::Globalization::GetUserDefaultLangID; use windows::Win32::Globalization::GetUserDefaultLangID;
@@ -18,7 +17,7 @@ use windows::Win32::UI::WindowsAndMessaging::{
pub fn listen_for_active_app_changes<F>(callback: F) pub fn listen_for_active_app_changes<F>(callback: F)
where where
F: Fn(AppMetadata) + Send + 'static, F: Fn(AppMeta) + Send + 'static,
{ {
// Run the hook on a background thread so we don't block the caller. // Run the hook on a background thread so we don't block the caller.
std::thread::spawn(move || unsafe { std::thread::spawn(move || unsafe {
@@ -52,7 +51,7 @@ where
// RefCell is safe here because the callback is stored and accessed only within the dedicated event loop thread. // RefCell is safe here because the callback is stored and accessed only within the dedicated event loop thread.
// No concurrent access occurs since WinEventHook runs on a single background thread. // No concurrent access occurs since WinEventHook runs on a single background thread.
thread_local! { thread_local! {
static CALLBACK: std::cell::RefCell<Option<Box<dyn Fn(AppMetadata) + Send + 'static>>> = static CALLBACK: std::cell::RefCell<Option<Box<dyn Fn(AppMeta) + Send + 'static>>> =
std::cell::RefCell::new(None); std::cell::RefCell::new(None);
} }
@@ -76,35 +75,35 @@ unsafe extern "system" fn win_event_proc<F>(
} }
/// Retrieves metadata for the active application on Windows, optionally using a provided window handle. /// Retrieves metadata for the active application on Windows, optionally using a provided window handle.
pub fn get_active_app_metadata_windows(hwnd_override: Option<HWND>) -> AppMetadata { pub fn get_active_app_metadata_windows(hwnd_override: Option<HWND>) -> AppMeta {
unsafe { unsafe {
let hwnd = hwnd_override.unwrap_or_else(|| GetForegroundWindow()); let hwnd = hwnd_override.unwrap_or_else(|| GetForegroundWindow());
if hwnd.0 == std::ptr::null_mut() { if hwnd.0 == std::ptr::null_mut() {
warn!("No foreground window found"); eprintln!("No foreground window found");
return AppMetadata { return AppMeta {
localized: None, local: None,
unlocalized: None, unlocal: None,
app_icon_b64: None, ico: None,
}; };
} }
let mut pid: u32 = 0; let mut pid: u32 = 0;
GetWindowThreadProcessId(hwnd, Some(&mut pid)); GetWindowThreadProcessId(hwnd, Some(&mut pid));
if pid == 0 { if pid == 0 {
warn!("Failed to get process ID for foreground window"); eprintln!("Failed to get process ID for foreground window");
return AppMetadata { return AppMeta {
localized: None, local: None,
unlocalized: None, unlocal: None,
app_icon_b64: None, ico: None,
}; };
} }
let process_handle = match OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) { let process_handle = match OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) {
Ok(h) if !h.is_invalid() => h, Ok(h) if !h.is_invalid() => h,
_ => { _ => {
warn!("Failed to open process {} for querying", pid); eprintln!("Failed to open process {} for querying", pid);
return AppMetadata { return AppMeta {
localized: None, local: None,
unlocalized: None, unlocal: None,
app_icon_b64: None, ico: None,
}; };
} }
}; };
@@ -114,11 +113,11 @@ pub fn get_active_app_metadata_windows(hwnd_override: Option<HWND>) -> AppMetada
let exe_path = if size > 0 { let exe_path = if size > 0 {
OsString::from_wide(&buffer[..size as usize]) OsString::from_wide(&buffer[..size as usize])
} else { } else {
warn!("Failed to get module file name for process {}", pid); eprintln!("Failed to get module file name for process {}", pid);
return AppMetadata { return AppMeta {
localized: None, local: None,
unlocalized: None, unlocal: None,
app_icon_b64: None, ico: None,
}; };
}; };
let exe_path_str = exe_path.to_string_lossy(); let exe_path_str = exe_path.to_string_lossy();
@@ -143,10 +142,10 @@ pub fn get_active_app_metadata_windows(hwnd_override: Option<HWND>) -> AppMetada
let app_icon_b64 = get_active_app_icon_b64(&exe_path_str); let app_icon_b64 = get_active_app_icon_b64(&exe_path_str);
AppMetadata { AppMeta {
localized, local: localized,
unlocalized, unlocal: unlocalized,
app_icon_b64, ico: app_icon_b64,
} }
} }
} }
@@ -281,7 +280,7 @@ struct IconHandle(windows::Win32::UI::WindowsAndMessaging::HICON);
impl Drop for IconHandle { impl Drop for IconHandle {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
windows::Win32::UI::WindowsAndMessaging::DestroyIcon(self.0); let _ = windows::Win32::UI::WindowsAndMessaging::DestroyIcon(self.0);
} }
} }
} }
@@ -316,7 +315,7 @@ fn get_active_app_icon_b64(exe_path: &str) -> Option<String> {
); );
if result == 0 || file_info.hIcon.is_invalid() { if result == 0 || file_info.hIcon.is_invalid() {
warn!("Failed to get icon for {}", exe_path); eprintln!("Failed to get icon for {}", exe_path);
return None; return None;
} }
@@ -325,7 +324,7 @@ fn get_active_app_icon_b64(exe_path: &str) -> Option<String> {
// Get icon info to extract bitmap // Get icon info to extract bitmap
let mut icon_info: ICONINFO = std::mem::zeroed(); let mut icon_info: ICONINFO = std::mem::zeroed();
if GetIconInfo(icon_handle.0, &mut icon_info).is_err() { if GetIconInfo(icon_handle.0, &mut icon_info).is_err() {
warn!("Failed to get icon info for {}", exe_path); eprintln!("Failed to get icon info for {}", exe_path);
return None; return None;
} }
@@ -339,13 +338,10 @@ fn get_active_app_icon_b64(exe_path: &str) -> Option<String> {
{ {
let _ = DeleteObject(icon_info.hbmColor); let _ = DeleteObject(icon_info.hbmColor);
let _ = DeleteObject(icon_info.hbmMask); let _ = DeleteObject(icon_info.hbmMask);
warn!("Failed to get bitmap object for {}", exe_path); eprintln!("Failed to get bitmap object for {}", exe_path);
return None; return None;
} }
let width = bitmap.bmWidth;
let height = bitmap.bmHeight;
// Create device contexts // Create device contexts
let hdc_screen = windows::Win32::Graphics::Gdi::GetDC(HWND(ptr::null_mut())); let hdc_screen = windows::Win32::Graphics::Gdi::GetDC(HWND(ptr::null_mut()));
let hdc_mem = CreateCompatibleDC(hdc_screen); let hdc_mem = CreateCompatibleDC(hdc_screen);
@@ -402,7 +398,7 @@ fn get_active_app_icon_b64(exe_path: &str) -> Option<String> {
let _ = DeleteObject(icon_info.hbmMask); let _ = DeleteObject(icon_info.hbmMask);
if result == 0 { if result == 0 {
warn!("Failed to get bitmap bits for {}", exe_path); eprintln!("Failed to get bitmap bits for {}", exe_path);
return None; return None;
} }
@@ -419,7 +415,7 @@ fn get_active_app_icon_b64(exe_path: &str) -> Option<String> {
) { ) {
Some(img) => img, Some(img) => img,
None => { None => {
warn!("Failed to create image from buffer for {}", exe_path); eprintln!("Failed to create image from buffer for {}", exe_path);
return None; return None;
} }
}; };
@@ -429,13 +425,13 @@ fn get_active_app_icon_b64(exe_path: &str) -> Option<String> {
&mut std::io::Cursor::new(&mut png_buffer), &mut std::io::Cursor::new(&mut png_buffer),
image::ImageFormat::Png, image::ImageFormat::Png,
) { ) {
warn!("Failed to encode PNG for {}: {}", exe_path, e); eprintln!("Failed to encode PNG for {}: {}", exe_path, e);
return None; return None;
} }
let encoded = STANDARD.encode(&png_buffer); let encoded = STANDARD.encode(&png_buffer);
info!( println!(
"App icon captured: {}, PNG bytes: {}, base64 size: {} bytes", "App icon captured: {}, PNG bytes: {}, base64 size: {} bytes",
exe_path, exe_path,
png_buffer.len(), png_buffer.len(),
@@ -443,7 +439,7 @@ fn get_active_app_icon_b64(exe_path: &str) -> Option<String> {
); );
if encoded.len() > super::types::MAX_ICON_B64_SIZE { if encoded.len() > super::types::MAX_ICON_B64_SIZE {
warn!( eprintln!(
"Icon for {} exceeds size limit ({} > {} bytes), skipping", "Icon for {} exceeds size limit ({} > {} bytes), skipping",
exe_path, exe_path,
encoded.len(), encoded.len(),