diff --git a/Cargo.lock b/Cargo.lock index 1d5fdb5..fa63a48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "copy_to_output" version = "2.1.0" @@ -18,6 +30,19 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "embed-resource" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f1e82a60222fc67bfd50d752a9c89da5cce4c39ed39decc84a443b07bbd69a" +dependencies = [ + "cc", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + [[package]] name = "equivalent" version = "1.0.0" @@ -41,6 +66,7 @@ name = "grout-wm" version = "0.1.0" dependencies = [ "copy_to_output", + "embed-resource", "glob", "log", "serde", @@ -89,6 +115,12 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "proc-macro2" version = "1.0.63" @@ -113,12 +145,27 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.164" @@ -139,6 +186,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.22" @@ -185,6 +241,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "toml" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.9" @@ -197,6 +287,26 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "winapi" version = "0.3.9" @@ -284,3 +394,22 @@ name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" +dependencies = [ + "cfg-if", + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml index ac8e720..73db13f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,5 @@ windows = { version = "0.48.0", features = ["Win32_Foundation", "Win32_Graphics_ [build-dependencies] copy_to_output = "2.1.0" +embed-resource = "2.2.0" glob = "0.3.1" diff --git a/build.rs b/build.rs index 452d10c..35f4e33 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,9 @@ use copy_to_output::copy_to_output; use std::env; +extern crate embed_resource; fn main() { + embed_resource::compile("resources/res.rc", embed_resource::NONE); copy_to_output("default.yaml", &env::var("PROFILE").unwrap()).expect("Could not copy"); copy_to_output("user.yaml", &env::var("PROFILE").unwrap()).expect("Could not copy"); } diff --git a/resources/app.ico b/resources/app.ico new file mode 100644 index 0000000..0c8ce10 Binary files /dev/null and b/resources/app.ico differ diff --git a/resources/res.rc b/resources/res.rc new file mode 100644 index 0000000..f7beb20 --- /dev/null +++ b/resources/res.rc @@ -0,0 +1 @@ +appicon ICON "app.ico" diff --git a/src/appwindow.rs b/src/appwindow.rs index 9c414a8..8d246ab 100644 --- a/src/appwindow.rs +++ b/src/appwindow.rs @@ -1,19 +1,26 @@ -use std::{ffi::c_void, sync::OnceLock}; +use std::{ + ffi::{c_uchar, c_void}, + sync::OnceLock, +}; use log::{debug, error, info}; use windows::{ w, Win32::{ Foundation::{HWND, LPARAM, LRESULT, WPARAM}, - Graphics::Gdi::{COLOR_WINDOW, HBRUSH}, + Graphics::{ + Dwm::{DWMWA_FORCE_ICONIC_REPRESENTATION, DWMWA_HAS_ICONIC_BITMAP}, + Gdi::{BITMAPINFO, BITMAPINFOHEADER, COLOR_WINDOW, HBRUSH}, + }, UI::{ Accessibility::{UnhookWinEvent, HWINEVENTHOOK}, WindowsAndMessaging::{ CreateWindowExW, DeregisterShellHookWindow, CHILDID_SELF, CREATESTRUCTA, CW_USEDEFAULT, EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED, EVENT_SYSTEM_MINIMIZEEND, EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MOVESIZEEND, - EVENT_SYSTEM_MOVESIZESTART, GWLP_USERDATA, OBJID_WINDOW, WINDOW_EX_STYLE, WM_APP, - WM_CREATE, WM_DESTROY, WM_USER, WNDCLASSW, WS_OVERLAPPEDWINDOW, + EVENT_SYSTEM_MOVESIZESTART, GWLP_USERDATA, OBJID_WINDOW, SC_RESTORE, + WINDOW_EX_STYLE, WM_APP, WM_CREATE, WM_DESTROY, WM_DWMSENDICONICTHUMBNAIL, + WM_SYSCOMMAND, WM_USER, WNDCLASSW, WS_OVERLAPPEDWINDOW, WM_QUERYOPEN, }, }, }, @@ -22,13 +29,29 @@ use windows::{ use grout_wm::Result; use crate::{ - win32, + win32::{ + self, def_window_proc, get_module_handle, get_window_long_ptr, get_working_area, load_icon, + post_quit_message, register_class, register_shell_hook_window, register_window_messagew, + set_win_event_hook, set_window_long_ptr, show_window, + }, windowmanager::{ WindowManager, MSG_CLOAKED, MSG_MINIMIZEEND, MSG_MINIMIZESTART, MSG_MOVESIZEEND, - MSG_UNCLOAKED, + MSG_UNCLOAKED, SHELL_HOOK_ID, }, }; +macro_rules! LOWORD { + ($w:expr) => { + $w & 0xFFFF + }; +} + +macro_rules! HIWORD { + ($w:expr) => { + ($w >> 16) & 0xFFFF + }; +} + static MY_HWND: OnceLock = OnceLock::new(); pub struct AppWindow { @@ -38,8 +61,93 @@ pub struct AppWindow { movesize_event_hook: HWINEVENTHOOK, } -impl Drop for AppWindow { - fn drop(&mut self) { +impl AppWindow { + pub fn new_window(wm: &mut WindowManager) -> Result { + let instance = get_module_handle()?; + let window_class = w!("grout-wm.window"); + let wc = WNDCLASSW { + hInstance: instance, + lpszClassName: window_class, + hIcon: load_icon(instance, w!("appicon"))?, + hbrBackground: HBRUSH((COLOR_WINDOW.0 + 1) as isize), + lpfnWndProc: Some(Self::wnd_proc), + ..Default::default() + }; + if register_class(&wc) == 0 { + error!("Could not register class"); + return Err("Could not register class".into()); + } + let hwnd = unsafe { + CreateWindowExW( + WINDOW_EX_STYLE::default(), + window_class, + w!("grout-wm"), + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + None, + None, + instance, + Some(wm as *mut _ as *mut c_void), + ) + }; + if hwnd.0 == 0 { + error!("Could not create window"); + return Err("Could not create window".into()); + } + let _ = MY_HWND.set(hwnd); + Ok(Self { + hwnd, + cloaked_event_hook: Default::default(), + minimized_event_hook: Default::default(), + movesize_event_hook: Default::default(), + }) + } + + pub fn show_window(self) -> Result { + show_window(self.hwnd); + Ok(Self { + hwnd: self.hwnd, + cloaked_event_hook: self.cloaked_event_hook, + minimized_event_hook: self.minimized_event_hook, + movesize_event_hook: self.movesize_event_hook, + }) + } + + pub fn register_hooks(self) -> Result { + let shell_hook_res = register_shell_hook_window(self.hwnd); + if !shell_hook_res { + error!("Could not register shell hook window"); + return Err("Could not register shell hook window".into()); + } + let shell_hook_id = register_window_messagew(w!("SHELLHOOK")); + let _ = SHELL_HOOK_ID.set(shell_hook_id); + let cloaked_event_hook = set_win_event_hook( + EVENT_OBJECT_CLOAKED, + EVENT_OBJECT_UNCLOAKED, + Some(Self::wnd_event_proc), + ); + let minimized_event_hook = set_win_event_hook( + EVENT_SYSTEM_MINIMIZESTART, + EVENT_SYSTEM_MINIMIZEEND, + Some(Self::wnd_event_proc), + ); + let movesize_event_hook = set_win_event_hook( + EVENT_SYSTEM_MOVESIZESTART, + EVENT_SYSTEM_MOVESIZEEND, + Some(Self::wnd_event_proc), + ); + Ok(Self { + hwnd: self.hwnd, + cloaked_event_hook, + minimized_event_hook, + movesize_event_hook, + }) + } + + pub fn cleanup(&self) -> Self { info!("Cleaning up handles"); unsafe { DeregisterShellHookWindow(self.hwnd); @@ -47,80 +155,11 @@ impl Drop for AppWindow { UnhookWinEvent(self.minimized_event_hook); UnhookWinEvent(self.movesize_event_hook); } - } -} - -impl AppWindow { - pub fn new(wm: &mut WindowManager) -> Result { - let instance_res = win32::get_module_handle(); - if let Ok(instance) = instance_res { - let windows_class = w!("grout-wm.window"); - let wc = WNDCLASSW { - hInstance: instance, - hbrBackground: HBRUSH((COLOR_WINDOW.0 + 1) as isize), - lpszClassName: windows_class, - lpfnWndProc: Some(Self::wnd_proc), - ..Default::default() - }; - if win32::register_class(&wc) == 0 { - error!("Could not register class"); - return Err("Could not register class".into()); - } - let hwnd = unsafe { - CreateWindowExW( - WINDOW_EX_STYLE::default(), - windows_class, - w!("grout-wm"), - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - None, - None, - instance, - Some(wm as *mut _ as *mut c_void), - ) - }; - if hwnd.0 == 0 { - error!("Could not create window"); - return Err("Could not create window".into()); - } - let _ = MY_HWND.set(hwnd); - win32::show_window(hwnd); - wm.manage(hwnd); - wm.arrange(); - let shell_hook_res = win32::register_shell_hook_window(hwnd); - if !shell_hook_res { - error!("Could not register shell hook window"); - return Err("Could not register shell hook window".into()); - } - let shell_hook_id = win32::register_window_messagew(w!("SHELLHOOK")); - wm.set_shell_hook_id(shell_hook_id); - let cloaked_event_hook = win32::set_win_event_hook( - EVENT_OBJECT_CLOAKED, - EVENT_OBJECT_UNCLOAKED, - Some(Self::wnd_event_proc), - ); - let minimized_event_hook = win32::set_win_event_hook( - EVENT_SYSTEM_MINIMIZESTART, - EVENT_SYSTEM_MINIMIZEEND, - Some(Self::wnd_event_proc), - ); - let movesize_event_hook = win32::set_win_event_hook( - EVENT_SYSTEM_MOVESIZESTART, - EVENT_SYSTEM_MOVESIZEEND, - Some(Self::wnd_event_proc), - ); - Ok(Self { - hwnd, - cloaked_event_hook, - minimized_event_hook, - movesize_event_hook, - }) - } else { - error!("Could not get instace"); - Err("Could not get instance".into()) + Self { + hwnd: self.hwnd, + cloaked_event_hook: Default::default(), + minimized_event_hook: Default::default(), + movesize_event_hook: Default::default(), } } @@ -151,7 +190,6 @@ impl AppWindow { return; } if let Some(&my_hwnd) = MY_HWND.get() { - debug!("event: {event}"); let msg = match event { EVENT_OBJECT_CLOAKED => MSG_CLOAKED, EVENT_OBJECT_UNCLOAKED => MSG_UNCLOAKED, @@ -160,7 +198,6 @@ impl AppWindow { EVENT_SYSTEM_MOVESIZEEND => MSG_MOVESIZEEND, _ => event, }; - debug!("msg: {msg}"); if msg >= WM_USER || msg < WM_APP { win32::post_message(my_hwnd, msg, WPARAM(0), LPARAM(hwnd.0)); } @@ -171,23 +208,108 @@ impl AppWindow { match msg { WM_DESTROY => { info!("Received WM_DESTROY message"); - win32::post_quit_message(0); + post_quit_message(0); LRESULT(0) } WM_CREATE => { info!("Creating application window"); let create_struct = lparam.0 as *const CREATESTRUCTA; let wm = unsafe { (*create_struct).lpCreateParams as *mut WindowManager }; - win32::set_window_long_ptr(hwnd, GWLP_USERDATA, wm as _); + set_window_long_ptr(hwnd, GWLP_USERDATA, wm as _); + let _ = win32::dwm::set_window_attribute(hwnd, DWMWA_HAS_ICONIC_BITMAP); + let _ = win32::dwm::set_window_attribute(hwnd, DWMWA_FORCE_ICONIC_REPRESENTATION); + let _ = win32::dwm::invalidate_iconic_bitmaps(hwnd); + LRESULT(0) + } + WM_DWMSENDICONICTHUMBNAIL => { + debug!("WM_DWMSENDICONICTHUMBNAIL"); + let width = HIWORD!(lparam.0); + let height = LOWORD!(lparam.0); + let _ = set_screenshot_as_iconic_thumbnail(hwnd, width, height); + LRESULT(0) + } + WM_SYSCOMMAND => { + debug!("WM_SYSCOMMAND {:?}\t{}", wparam, SC_RESTORE); + if wparam.0 as u32 == SC_RESTORE { + return LRESULT(0); + } + def_window_proc(hwnd, msg, wparam, lparam) + } + WM_QUERYOPEN => { LRESULT(0) } _ => { - let wm = win32::get_window_long_ptr(hwnd, GWLP_USERDATA) as *mut WindowManager; + let wm = get_window_long_ptr(hwnd, GWLP_USERDATA) as *mut WindowManager; if !wm.is_null() { return unsafe { (*wm).message_loop(hwnd, msg, wparam, lparam) }; } - win32::def_window_proc(hwnd, msg, wparam, lparam) + def_window_proc(hwnd, msg, wparam, lparam) + } + } + } +} + +fn set_screenshot_as_iconic_thumbnail(hwnd: HWND, thumb_w: isize, thumb_h: isize) -> Result<()> { + let working_area = get_working_area()?; + let w = working_area.right - working_area.left; + let h = working_area.bottom - working_area.top; + let hdc_mem = unsafe { windows::Win32::Graphics::Gdi::CreateCompatibleDC(None) }; + if !hdc_mem.is_invalid() { + let mut bmi: BITMAPINFO = unsafe { std::mem::zeroed() }; + bmi.bmiHeader.biSize = std::mem::size_of::() as u32; + bmi.bmiHeader.biWidth = thumb_w as i32; + bmi.bmiHeader.biHeight = thumb_h as i32; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + let mut pb_ds = std::ptr::null_mut::>(); + let hbm_res = unsafe { + windows::Win32::Graphics::Gdi::CreateDIBSection( + hdc_mem, + &bmi as *const _ as *const _, + windows::Win32::Graphics::Gdi::DIB_RGB_COLORS, + &mut pb_ds as *mut _ as *mut _, + None, + 0, + ) + }; + if let Ok(hbitmap) = hbm_res { + let hscreen = unsafe { windows::Win32::Graphics::Gdi::GetDC(None) }; + let hdc = unsafe { windows::Win32::Graphics::Gdi::CreateCompatibleDC(hscreen) }; + unsafe { + windows::Win32::Graphics::Gdi::SetStretchBltMode( + hdc, + windows::Win32::Graphics::Gdi::HALFTONE, + ) + }; + let old_obj = unsafe { windows::Win32::Graphics::Gdi::SelectObject(hdc, hbitmap) }; + let _bret = unsafe { + windows::Win32::Graphics::Gdi::StretchBlt( + hdc, + 0, + 0, + thumb_w as i32, + thumb_h as i32, + hscreen, + 0, + 0, + w, + h, + windows::Win32::Graphics::Gdi::SRCCOPY, + ) + }; + let dwm_res = + unsafe { windows::Win32::Graphics::Dwm::DwmSetIconicThumbnail(hwnd, hbitmap, 0) }; + if let Err(e) = dwm_res { + dbg!(e); + } + // cleanup + unsafe { + windows::Win32::Graphics::Gdi::SelectObject(hdc, old_obj); + windows::Win32::Graphics::Gdi::DeleteDC(hdc); + windows::Win32::Graphics::Gdi::ReleaseDC(None, hscreen); + windows::Win32::Graphics::Gdi::DeleteObject(hbitmap); } } } + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 37d7b71..2d1b53f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,14 +30,18 @@ fn main() -> Result<()> { #[cfg(debug_assertions)] simple_logging::log_to_stderr(LevelFilter::Debug); info!("{} {} - starting", app_name, app_version); - let _win32 = win32::Win32Com::new().unwrap_or_else(|_e| { + let _win32 = win32::com::Win32Com::new().unwrap_or_else(|_e| { error!("Can not initialize com"); std::process::exit(1); }); let config = Config::load_default()?.load_or_create_user_config()?; let mut binding = WindowManager::new(config)?; let wm = binding.enum_windows()?; - let _appwindow = AppWindow::new(wm)?.handle_messages(); + let _appwindow = AppWindow::new_window(wm)? + .show_window()? + .register_hooks()? + .handle_messages()? + .cleanup(); info!("quitting"); win32::release_mutex(mutex_handle); Ok(()) diff --git a/src/win32/com.rs b/src/win32/com.rs new file mode 100644 index 0000000..f364802 --- /dev/null +++ b/src/win32/com.rs @@ -0,0 +1,24 @@ +use log::info; + +pub struct Win32Com; + +impl Win32Com { + pub fn new() -> grout_wm::Result { + use windows::Win32::System::Com::CoInitialize; + info!("Initialize COM"); + unsafe { + CoInitialize(None)?; + } + Ok(Win32Com) + } +} + +impl Drop for Win32Com { + fn drop(&mut self) { + use windows::Win32::System::Com::CoUninitialize; + info!("Uninitializing COM"); + unsafe { + CoUninitialize(); + } + } +} diff --git a/src/win32/dwm.rs b/src/win32/dwm.rs new file mode 100644 index 0000000..a2c51a0 --- /dev/null +++ b/src/win32/dwm.rs @@ -0,0 +1,69 @@ +use std::ffi::c_void; +use std::mem::{size_of, zeroed}; +use windows::Win32::{ + Foundation::{BOOL, HWND, RECT, TRUE}, + Graphics::Dwm::{ + DwmGetWindowAttribute, DwmInvalidateIconicBitmaps, DwmSetWindowAttribute, DWMWA_CLOAKED, + DWMWA_EXTENDED_FRAME_BOUNDS, DWMWINDOWATTRIBUTE, DWM_CLOAKED_APP, DWM_CLOAKED_INHERITED, + DWM_CLOAKED_SHELL, + }, + UI::WindowsAndMessaging::GetWindowRect, +}; + +pub fn get_window_extended_frame_bounds(hwnd: HWND) -> RECT { + let mut rect: RECT = unsafe { zeroed() }; + let mut frame: RECT = unsafe { zeroed() }; + unsafe { + GetWindowRect(hwnd, &mut rect); + let _ = DwmGetWindowAttribute( + hwnd, + DWMWA_EXTENDED_FRAME_BOUNDS, + &mut frame as *mut RECT as *mut c_void, + size_of::().try_into().unwrap(), + ); + } + RECT { + left: frame.left - rect.left, + top: frame.top - rect.top, + right: frame.right - rect.right, + bottom: frame.bottom - rect.bottom, + } +} + +pub fn is_cloaked(hwnd: HWND) -> bool { + let mut cloaked: u32 = 0; + let res = unsafe { + DwmGetWindowAttribute( + hwnd, + DWMWA_CLOAKED, + (&mut cloaked as *mut u32).cast(), + size_of::().try_into().unwrap(), + ) + }; + match res { + Ok(_) => matches!( + cloaked, + DWM_CLOAKED_APP | DWM_CLOAKED_SHELL | DWM_CLOAKED_INHERITED + ), + _ => false, + } +} + +pub fn set_window_attribute( + hwnd: HWND, + dwattribute: DWMWINDOWATTRIBUTE, +) -> windows::core::Result<()> { + let is_true: BOOL = TRUE; + unsafe { + DwmSetWindowAttribute( + hwnd, + dwattribute, + &is_true as *const BOOL as *const _, + size_of::() as u32, + ) + } +} + +pub fn invalidate_iconic_bitmaps(hwnd: HWND) -> windows::core::Result<()> { + unsafe { DwmInvalidateIconicBitmaps(hwnd) } +} diff --git a/src/win32.rs b/src/win32/mod.rs similarity index 69% rename from src/win32.rs rename to src/win32/mod.rs index 1fec0d0..4f91e66 100644 --- a/src/win32.rs +++ b/src/win32/mod.rs @@ -4,7 +4,7 @@ use std::{ path::PathBuf, }; -use log::{debug, info}; +use log::debug; use windows::{ core::PCWSTR, w, @@ -13,15 +13,8 @@ use windows::{ CloseHandle, GetLastError, BOOL, ERROR_ALREADY_EXISTS, FALSE, HANDLE, HMODULE, HWND, LPARAM, LRESULT, MAX_PATH, POINT, RECT, TRUE, WPARAM, }, - Graphics::{ - Dwm::{ - DwmGetWindowAttribute, DWMWA_CLOAKED, DWMWA_EXTENDED_FRAME_BOUNDS, DWM_CLOAKED_APP, - DWM_CLOAKED_INHERITED, DWM_CLOAKED_SHELL, - }, - Gdi::PtInRect, - }, + Graphics::Gdi::PtInRect, System::{ - Com::{CoCreateInstance, CoInitialize, CoUninitialize, CLSCTX_ALL}, LibraryLoader::GetModuleHandleA, ProcessStatus::{ EnumProcessModules, GetModuleBaseNameW, GetModuleInformation, MODULEINFO, @@ -32,19 +25,16 @@ use windows::{ }, UI::{ Accessibility::{SetWinEventHook, HWINEVENTHOOK, WINEVENTPROC}, - Shell::{ - FOLDERID_LocalAppData, IVirtualDesktopManager, SHGetKnownFolderPath, - VirtualDesktopManager as VirtualDesktopManager_ID, KF_FLAG_DEFAULT, - }, + Shell::{FOLDERID_LocalAppData, SHGetKnownFolderPath, KF_FLAG_DEFAULT}, WindowsAndMessaging::{ DefWindowProcW, EnumWindows, FindWindowW, GetClassNameW, GetCursorPos, - GetSystemMetrics, GetWindow, GetWindowLongPtrW, GetWindowRect, - GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId, IsIconic, - IsWindowVisible, PostMessageW, PostQuitMessage, RegisterClassW, - RegisterShellHookWindow, RegisterWindowMessageW, SetWindowLongPtrW, SetWindowPos, - ShowWindow, SystemParametersInfoW, GET_WINDOW_CMD, GWL_EXSTYLE, GWL_STYLE, - HWND_TOP, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, - SM_YVIRTUALSCREEN, SPI_GETWORKAREA, SWP_NOACTIVATE, SW_SHOWMINNOACTIVE, + GetSystemMetrics, GetWindow, GetWindowLongPtrW, GetWindowTextLengthW, + GetWindowTextW, GetWindowThreadProcessId, IsIconic, IsWindowVisible, LoadIconW, + PostMessageW, PostQuitMessage, RegisterClassW, RegisterShellHookWindow, + RegisterWindowMessageW, SetWindowLongPtrW, SetWindowPos, ShowWindow, + SystemParametersInfoW, GET_WINDOW_CMD, GWL_EXSTYLE, GWL_STYLE, HICON, HWND_TOP, + SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, + SPI_GETWORKAREA, SWP_NOACTIVATE, SW_SHOWMINNOACTIVE, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WINDOW_LONG_PTR_INDEX, WINEVENT_OUTOFCONTEXT, WNDCLASSW, WNDENUMPROC, }, @@ -54,44 +44,9 @@ use windows::{ use grout_wm::Result; -pub struct Win32Com; - -impl Win32Com { - pub fn new() -> Result { - info!("Initialize COM"); - unsafe { - CoInitialize(None)?; - } - Ok(Win32Com) - } -} - -impl Drop for Win32Com { - fn drop(&mut self) { - info!("Uninitializing COM"); - unsafe { - CoUninitialize(); - } - } -} -pub fn is_cloaked(hwnd: HWND) -> bool { - let mut cloaked: u32 = 0; - let res = unsafe { - DwmGetWindowAttribute( - hwnd, - DWMWA_CLOAKED, - (&mut cloaked as *mut u32).cast(), - size_of::().try_into().unwrap(), - ) - }; - match res { - Ok(_) => matches!( - cloaked, - DWM_CLOAKED_APP | DWM_CLOAKED_SHELL | DWM_CLOAKED_INHERITED - ), - _ => false, - } -} +pub(crate) mod com; +pub(crate) mod dwm; +pub(crate) mod virtualdesktop; pub fn is_iconic(hwnd: HWND) -> bool { unsafe { IsIconic(hwnd).into() } @@ -145,7 +100,7 @@ pub fn get_working_area() -> Result { } pub fn set_window_pos(hwnd: HWND, r: RECT) { - let margin = get_window_extended_frame_bounds(hwnd); // should be: { left: 7, top: 0, right:-7, bottom -7 } + let margin = dwm::get_window_extended_frame_bounds(hwnd); // should be: { left: 7, top: 0, right:-7, bottom -7 } debug!("{margin:?}"); unsafe { SetWindowPos( @@ -304,26 +259,6 @@ pub fn get_local_appdata_path() -> Result { Ok(path) } -pub fn get_window_extended_frame_bounds(hwnd: HWND) -> RECT { - let mut rect: RECT = unsafe { zeroed() }; - let mut frame: RECT = unsafe { zeroed() }; - unsafe { - GetWindowRect(hwnd, &mut rect); - let _ = DwmGetWindowAttribute( - hwnd, - DWMWA_EXTENDED_FRAME_BOUNDS, - &mut frame as *mut RECT as *mut c_void, - size_of::().try_into().unwrap(), - ); - } - RECT { - left: frame.left - rect.left, - top: frame.top - rect.top, - right: frame.right - rect.right, - bottom: frame.bottom - rect.bottom, - } -} - pub fn get_cursor_pos() -> POINT { let mut p: POINT = unsafe { zeroed() }; unsafe { @@ -336,25 +271,6 @@ pub fn point_in_rect(lprc: RECT, pt: POINT) -> bool { unsafe { PtInRect(&lprc, pt).into() } } -// ===== -// Virtual desktop -// ===== - -pub struct VirtualDesktopManager(IVirtualDesktopManager); - -impl VirtualDesktopManager { - pub fn new() -> Result { - info!("Instantiate VirtualDesktopManager"); - unsafe { - CoInitialize(None)?; - } - let virtual_desktop_managr = - unsafe { CoCreateInstance(&VirtualDesktopManager_ID, None, CLSCTX_ALL)? }; - Ok(Self(virtual_desktop_managr)) - } - - pub fn is_window_on_current_desktop(&self, hwnd: HWND) -> windows::core::Result { - let is_on_desktop = unsafe { self.0.IsWindowOnCurrentVirtualDesktop(hwnd)? }; - Ok(is_on_desktop.as_bool()) - } +pub fn load_icon(hinstance: HMODULE, lpiconname: PCWSTR) -> windows::core::Result { + unsafe { LoadIconW(hinstance, lpiconname) } } diff --git a/src/win32/virtualdesktop.rs b/src/win32/virtualdesktop.rs new file mode 100644 index 0000000..2e124dd --- /dev/null +++ b/src/win32/virtualdesktop.rs @@ -0,0 +1,24 @@ +use log::info; +use windows::Win32::{ + Foundation::HWND, + UI::Shell::{IVirtualDesktopManager, VirtualDesktopManager as VirtualDesktopManager_ID}, +}; + +use grout_wm::Result; + +pub struct VirtualDesktopManager(IVirtualDesktopManager); + +impl VirtualDesktopManager { + pub fn new() -> Result { + use windows::Win32::System::Com::{CoCreateInstance, CLSCTX_ALL}; + info!("Instantiate VirtualDesktopManager"); + let virtual_desktop_managr = + unsafe { CoCreateInstance(&VirtualDesktopManager_ID, None, CLSCTX_ALL)? }; + Ok(Self(virtual_desktop_managr)) + } + + pub fn is_window_on_current_desktop(&self, hwnd: HWND) -> windows::core::Result { + let is_on_desktop = unsafe { self.0.IsWindowOnCurrentVirtualDesktop(hwnd)? }; + Ok(is_on_desktop.as_bool()) + } +} diff --git a/src/windowmanager.rs b/src/windowmanager.rs index d5f5bae..7bac244 100644 --- a/src/windowmanager.rs +++ b/src/windowmanager.rs @@ -1,3 +1,5 @@ +use std::sync::OnceLock; + use log::{debug, error, info}; use windows::Win32::{ Foundation::{BOOL, HWND, LPARAM, LRESULT, RECT, TRUE, WPARAM}, @@ -7,10 +9,9 @@ use windows::Win32::{ }, }; +use crate::win32; use crate::{ - arrange::spiral_subdivide, - config::Config, - win32::{self, VirtualDesktopManager}, + arrange::spiral_subdivide, config::Config, win32::virtualdesktop::VirtualDesktopManager, window::Window, }; use grout_wm::{any, has_flag, Result}; @@ -21,10 +22,11 @@ pub const MSG_MINIMIZEEND: u32 = WM_USER + 0x0003; pub const MSG_MINIMIZESTART: u32 = WM_USER + 0x0004; pub const MSG_MOVESIZEEND: u32 = WM_USER + 0x0006; +pub static SHELL_HOOK_ID: OnceLock = OnceLock::new(); + pub struct WindowManager { managed_windows: Vec, working_area: RECT, - shell_hook_id: u32, config: Config, virtual_desktop: VirtualDesktopManager, } @@ -37,7 +39,6 @@ impl WindowManager { Ok(WindowManager { managed_windows: Default::default(), working_area, - shell_hook_id: Default::default(), config, virtual_desktop: VirtualDesktopManager::new()?, }) @@ -66,7 +67,7 @@ impl WindowManager { let style = win32::get_window_style(hwnd); let exstyle = win32::get_window_exstyle(hwnd); let is_child = has_flag!(style, WS_CHILD.0); - let is_cloaked = win32::is_cloaked(hwnd); + let is_cloaked = win32::dwm::is_cloaked(hwnd); let is_disabled = has_flag!(style, WS_DISABLED.0); let is_tool = has_flag!(exstyle, WS_EX_TOOLWINDOW.0); let is_visible = win32::is_window_visible(hwnd); @@ -105,10 +106,6 @@ impl WindowManager { retval } - pub fn set_shell_hook_id(&mut self, shell_hook_id: u32) { - self.shell_hook_id = shell_hook_id; - } - fn unmanage(&mut self, hwnd: HWND) { if !any!(self.managed_windows, hwnd) { return; @@ -119,6 +116,8 @@ impl WindowManager { .unwrap_or(false); if is_on_desktop { self.managed_windows.retain(|w| w.0 != hwnd); + } else { + self.arrange(); } } @@ -151,6 +150,7 @@ impl WindowManager { let handle = HWND(lparam.0); let managed_window = self.get_window(handle); let wmsg = wparam.0 as u32 & 0x7FFF; + let shell_hook_id = SHELL_HOOK_ID.get().unwrap_or(&0); match (msg, wmsg) { (MSG_CLOAKED, _) => { if managed_window.is_some() { @@ -192,14 +192,14 @@ impl WindowManager { self.arrange(); } } - (id, HSHELL_WINDOWCREATED) if id == self.shell_hook_id => { + (id, HSHELL_WINDOWCREATED) if id == *shell_hook_id => { if managed_window.is_none() && self.is_manageable(handle) { debug!("{handle:?} is created"); self.manage(handle); self.arrange(); } } - (id, HSHELL_WINDOWDESTROYED) if id == self.shell_hook_id => { + (id, HSHELL_WINDOWDESTROYED) if id == *shell_hook_id => { if managed_window.is_some() { debug!("{handle:?} is destroyed"); self.unmanage(handle); @@ -214,6 +214,7 @@ impl WindowManager { pub fn enum_windows(&mut self) -> Result<&mut Self> { let self_ptr = LPARAM(self as *mut Self as isize); if win32::enum_windows(Some(Self::scan), self_ptr) { + self.arrange(); Ok(self) } else { error!("Can not enum windows");