From 4728d1a3d9719ae039ff90f2ecf9d8db0f20e1b1 Mon Sep 17 00:00:00 2001 From: rpop0 <38384209+rpop0@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:45:56 +0300 Subject: [PATCH] feat: hide and show windows via set_cloak from COM lib Previous method of hiding the windows (SW_HIDE and SWP_HIDEWINDOW) would completely remove the window from the taskbar, which would lead to unexpected behavior for some people. set_cloak allows the hidden windows to still be present in the task bar. Other discussions on the topic: https://github.com/Ciantic/AltTabAccessor/issues/1 --- Cargo.lock | 3 +- packages/wm/Cargo.toml | 1 + packages/wm/src/common/com/interfaces.rs | 244 ++++++++++++++++++ packages/wm/src/common/com/mod.rs | 89 +++++++ packages/wm/src/common/mod.rs | 1 + .../wm/src/common/platform/native_window.rs | 30 ++- 6 files changed, 357 insertions(+), 11 deletions(-) create mode 100644 packages/wm/src/common/com/interfaces.rs create mode 100644 packages/wm/src/common/com/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 7d3e9b96..a7fe9867 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -2293,6 +2293,7 @@ dependencies = [ "tray-icon", "uuid", "windows", + "windows-interface", ] [[package]] diff --git a/packages/wm/Cargo.toml b/packages/wm/Cargo.toml index 59e67f8a..c37408a7 100644 --- a/packages/wm/Cargo.toml +++ b/packages/wm/Cargo.toml @@ -61,3 +61,4 @@ windows = { version = "0.52", features = [ "Win32_UI_TextServices", "Win32_UI_WindowsAndMessaging", ] } +windows-interface = { version = "*"} diff --git a/packages/wm/src/common/com/interfaces.rs b/packages/wm/src/common/com/interfaces.rs new file mode 100644 index 00000000..5bb69d50 --- /dev/null +++ b/packages/wm/src/common/com/interfaces.rs @@ -0,0 +1,244 @@ +use std::{ffi::c_void, ops::Deref}; +use windows::{ + core::{IUnknown, IUnknown_Vtbl, GUID, HSTRING, HRESULT}, + Win32::{UI::Shell::Common::IObjectArray, Foundation::HWND}, +}; + + +type DesktopID = GUID; + +// Idea here is that the cloned ComIn instance lifetime is within the original ComIn instance lifetime +#[repr(transparent)] +pub struct ComIn<'a, T> { + data: T, + _phantom: std::marker::PhantomData<&'a T>, +} + +impl<'a, T: Clone> ComIn<'a, T> { + pub fn new(t: &'a T) -> Self { + Self { + data: t.clone(), + _phantom: std::marker::PhantomData, + } + } + + pub unsafe fn unsafe_new_no_clone(t: T) -> Self { + Self { + data: t, + _phantom: std::marker::PhantomData, + } + } +} + +impl<'a, T> Deref for ComIn<'a, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.data + } +} + +pub const CLSID_IMMERSIVE_SHELL: GUID = GUID { + data1: 0xC2F03A33, + data2: 0x21F5, + data3: 0x47FA, + data4: [0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39], +}; + + +type BOOL = i32; +type DWORD = u32; +type INT = i32; +type LPVOID = *mut c_void; +type UINT = u32; +type ULONG = u32; +type WCHAR = u16; +type PCWSTR = *const WCHAR; +type PWSTR = *mut WCHAR; +type ULONGLONG = u64; +type LONG = i32; + +type IAsyncCallback = UINT; +type IImmersiveMonitor = UINT; +type IApplicationViewOperation = UINT; +type IApplicationViewPosition = UINT; +type IImmersiveApplication = UINT; +type IApplicationViewChangeListener = UINT; +#[allow(non_camel_case_types)] +type APPLICATION_VIEW_COMPATIBILITY_POLICY = UINT; +#[allow(non_camel_case_types)] +type APPLICATION_VIEW_CLOAK_TYPE = UINT; + +#[allow(dead_code)] +pub struct RECT { + left: LONG, + top: LONG, + right: LONG, + bottom: LONG, +} + +#[allow(dead_code)] +pub struct SIZE { + cx: LONG, + cy: LONG, +} +#[windows_interface::interface("6D5140C1-7436-11CE-8034-00AA006009FA")] +pub unsafe trait IServiceProvider: IUnknown { + pub unsafe fn query_service( + &self, + guid_service: *const GUID, + riid: *const GUID, + ppv_object: *mut *mut c_void, + ) -> HRESULT; +} + +#[windows_interface::interface("372E1D3B-38D3-42E4-A15B-8AB2B178F513")] +pub unsafe trait IApplicationView: IUnknown { + /* IInspecateble */ + pub unsafe fn get_iids( + &self, + out_iid_count: *mut ULONG, + out_opt_iid_array_ptr: *mut *mut GUID, + ) -> HRESULT; + pub unsafe fn get_runtime_class_name(&self, out_opt_class_name: *mut HSTRING) -> HRESULT; + pub unsafe fn get_trust_level(&self, ptr_trust_level: LPVOID) -> HRESULT; + + /* IApplicationView methods */ + pub unsafe fn set_focus(&self) -> HRESULT; + pub unsafe fn switch_to(&self) -> HRESULT; + + pub unsafe fn try_invoke_back(&self, ptr_async_callback: IAsyncCallback) -> HRESULT; + pub unsafe fn get_thumbnail_window(&self, out_hwnd: *mut HWND) -> HRESULT; + pub unsafe fn get_monitor(&self, out_monitors: *mut *mut IImmersiveMonitor) -> HRESULT; + pub unsafe fn get_visibility(&self, out_int: LPVOID) -> HRESULT; + pub unsafe fn set_cloak( + &self, + application_view_cloak_type: APPLICATION_VIEW_CLOAK_TYPE, + unknown: INT, + ) -> HRESULT; + pub unsafe fn get_position( + &self, + unknowniid: *const GUID, + unknown_array_ptr: LPVOID, + ) -> HRESULT; + pub unsafe fn set_position(&self, view_position: *mut IApplicationViewPosition) -> HRESULT; + pub unsafe fn insert_after_window(&self, window: HWND) -> HRESULT; + pub unsafe fn get_extended_frame_position(&self, rect: *mut RECT) -> HRESULT; + pub unsafe fn get_app_user_model_id(&self, id: *mut PWSTR) -> HRESULT; // Proc17 + pub unsafe fn set_app_user_model_id(&self, id: PCWSTR) -> HRESULT; + pub unsafe fn is_equal_by_app_user_model_id(&self, id: PCWSTR, out_result: *mut INT) + -> HRESULT; + + /*** IApplicationView methods ***/ + pub unsafe fn get_view_state(&self, out_state: *mut UINT) -> HRESULT; // Proc20 + pub unsafe fn set_view_state(&self, state: UINT) -> HRESULT; // Proc21 + pub unsafe fn get_neediness(&self, out_neediness: *mut INT) -> HRESULT; // Proc22 + pub unsafe fn get_last_activation_timestamp(&self, out_timestamp: *mut ULONGLONG) -> HRESULT; + pub unsafe fn set_last_activation_timestamp(&self, timestamp: ULONGLONG) -> HRESULT; + pub unsafe fn get_virtual_desktop_id(&self, out_desktop_guid: *mut DesktopID) -> HRESULT; + pub unsafe fn set_virtual_desktop_id(&self, desktop_guid: *const DesktopID) -> HRESULT; + pub unsafe fn get_show_in_switchers(&self, out_show: *mut INT) -> HRESULT; + pub unsafe fn set_show_in_switchers(&self, show: INT) -> HRESULT; + pub unsafe fn get_scale_factor(&self, out_scale_factor: *mut INT) -> HRESULT; + pub unsafe fn can_receive_input(&self, out_can: *mut BOOL) -> HRESULT; + pub unsafe fn get_compatibility_policy_type( + &self, + out_policy_type: *mut APPLICATION_VIEW_COMPATIBILITY_POLICY, + ) -> HRESULT; + pub unsafe fn set_compatibility_policy_type( + &self, + policy_type: APPLICATION_VIEW_COMPATIBILITY_POLICY, + ) -> HRESULT; + + pub unsafe fn get_size_constraints( + &self, + monitor: *mut IImmersiveMonitor, + out_size1: *mut SIZE, + out_size2: *mut SIZE, + ) -> HRESULT; + pub unsafe fn get_size_constraints_for_dpi( + &self, + dpi: UINT, + out_size1: *mut SIZE, + out_size2: *mut SIZE, + ) -> HRESULT; + pub unsafe fn set_size_constraints_for_dpi( + &self, + dpi: *const UINT, + size1: *const SIZE, + size2: *const SIZE, + ) -> HRESULT; + + pub unsafe fn on_min_size_preferences_updated(&self, window: HWND) -> HRESULT; + pub unsafe fn apply_operation(&self, operation: *mut IApplicationViewOperation) -> HRESULT; + pub unsafe fn is_tray(&self, out_is: *mut BOOL) -> HRESULT; + pub unsafe fn is_in_high_zorder_band(&self, out_is: *mut BOOL) -> HRESULT; + pub unsafe fn is_splash_screen_presented(&self, out_is: *mut BOOL) -> HRESULT; + pub unsafe fn flash(&self) -> HRESULT; + pub unsafe fn get_root_switchable_owner(&self, app_view: *mut IApplicationView) -> HRESULT; // proc45 + pub unsafe fn enumerate_ownership_tree(&self, objects: *mut IObjectArray) -> HRESULT; // proc46 + + pub unsafe fn get_enterprise_id(&self, out_id: *mut PWSTR) -> HRESULT; // proc47 + pub unsafe fn is_mirrored(&self, out_is: *mut BOOL) -> HRESULT; // + + pub unsafe fn unknown1(&self, arg: *mut INT) -> HRESULT; + pub unsafe fn unknown2(&self, arg: *mut INT) -> HRESULT; + pub unsafe fn unknown3(&self, arg: *mut INT) -> HRESULT; + pub unsafe fn unknown4(&self, arg: INT) -> HRESULT; + pub unsafe fn unknown5(&self, arg: *mut INT) -> HRESULT; + pub unsafe fn unknown6(&self, arg: INT) -> HRESULT; + pub unsafe fn unknown7(&self) -> HRESULT; + pub unsafe fn unknown8(&self, arg: *mut INT) -> HRESULT; + pub unsafe fn unknown9(&self, arg: INT) -> HRESULT; + pub unsafe fn unknown10(&self, arg: INT, arg2: INT) -> HRESULT; + pub unsafe fn unknown11(&self, arg: INT) -> HRESULT; + pub unsafe fn unknown12(&self, arg: *mut SIZE) -> HRESULT; +} + + +#[windows_interface::interface("1841c6d7-4f9d-42c0-af41-8747538f10e5")] +pub unsafe trait IApplicationViewCollection: IUnknown { + pub unsafe fn get_views(&self, out_views: *mut IObjectArray) -> HRESULT; + + pub unsafe fn get_views_by_zorder(&self, out_views: *mut IObjectArray) -> HRESULT; + + pub unsafe fn get_views_by_app_user_model_id( + &self, + id: PCWSTR, + out_views: *mut IObjectArray, + ) -> HRESULT; + + pub unsafe fn get_view_for_hwnd( + &self, + window: HWND, + out_view: *mut Option, + ) -> HRESULT; + + pub unsafe fn get_view_for_application( + &self, + app: ComIn, + out_view: *mut IApplicationView, + ) -> HRESULT; + + pub unsafe fn get_view_for_app_user_model_id( + &self, + id: PCWSTR, + out_view: *mut IApplicationView, + ) -> HRESULT; + + pub unsafe fn get_view_in_focus(&self, out_view: *mut IApplicationView) -> HRESULT; + + pub unsafe fn try_get_last_active_visible_view( + &self, + out_view: *mut IApplicationView, + ) -> HRESULT; + + pub unsafe fn refresh_collection(&self) -> HRESULT; + + pub unsafe fn register_for_application_view_changes( + &self, + listener: ComIn, + out_id: *mut DWORD, + ) -> HRESULT; + + pub unsafe fn unregister_for_application_view_changes(&self, id: DWORD) -> HRESULT; +} \ No newline at end of file diff --git a/packages/wm/src/common/com/mod.rs b/packages/wm/src/common/com/mod.rs new file mode 100644 index 00000000..6928dbab --- /dev/null +++ b/packages/wm/src/common/com/mod.rs @@ -0,0 +1,89 @@ +use std::ffi::c_void; +use windows::core::{ComInterface, Interface}; +use windows::Win32::Foundation::HWND; +use windows::Win32::System::Com::{CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_APARTMENTTHREADED}; +use crate::common::com::interfaces::{IApplicationViewCollection, IServiceProvider, CLSID_IMMERSIVE_SHELL}; + +mod interfaces; + +pub enum CloakVisibility { + HIDDEN, + VISIBLE +} + +struct ComInit(); + +impl ComInit { + pub fn new() -> Self { + unsafe { + // Apparently only COINIT_APARTMENTTHREADED works correctly. + // Initialize the COM Library + // Handle the error differently maybe? + CoInitializeEx(None, COINIT_APARTMENTTHREADED).unwrap(); + } + Self() + } +} + +impl Drop for ComInit { + fn drop(&mut self) { + unsafe { + CoUninitialize(); + } + } +} + + +fn get_iservice_provider() -> IServiceProvider { + COM_INIT.with(|_| unsafe { + CoCreateInstance(&CLSID_IMMERSIVE_SHELL, None, CLSCTX_ALL).unwrap() + }) +} + +fn get_iapplication_view_collection(provider: &IServiceProvider) -> IApplicationViewCollection { + COM_INIT.with(|_| { + let mut obj = std::ptr::null_mut::(); + unsafe { + provider + .query_service( + &IApplicationViewCollection::IID, + &IApplicationViewCollection::IID, + &mut obj, + ) + .unwrap(); + } + + assert!(!obj.is_null()); + + unsafe { IApplicationViewCollection::from_raw(obj) } + }) +} + +// Each thread that accesses the COM_INIT variable gets a local instance of the variable. +// This is needed since the COM library requires the CoInitializeEx needs to be initialized per thread. +thread_local! { + static COM_INIT: ComInit = ComInit::new(); +} + +pub fn set_cloak(hwnd: HWND, cloak_visibility: &CloakVisibility) { + COM_INIT.with(|_| { + let provider = get_iservice_provider(); + let view_collection = get_iapplication_view_collection(&provider); + let mut view = None; + unsafe { + view_collection.get_view_for_hwnd(hwnd, &mut view).unwrap() + }; + let view = view.unwrap(); + + + unsafe { + // https://github.com/Ciantic/AltTabAccessor/issues/1#issuecomment-1426877843 + let flag = match cloak_visibility { + CloakVisibility::VISIBLE => 0, + CloakVisibility::HIDDEN => 2 + }; + view.set_cloak(1, flag).unwrap() + } + }); + +} diff --git a/packages/wm/src/common/mod.rs b/packages/wm/src/common/mod.rs index 78fe98eb..a5155bd5 100644 --- a/packages/wm/src/common/mod.rs +++ b/packages/wm/src/common/mod.rs @@ -12,6 +12,7 @@ mod rect_delta; mod tiling_direction; mod try_warn; mod vec_deque_ext; +mod com; pub use color::*; pub use direction::*; diff --git a/packages/wm/src/common/platform/native_window.rs b/packages/wm/src/common/platform/native_window.rs index 04d19611..8b0f13c0 100644 --- a/packages/wm/src/common/platform/native_window.rs +++ b/packages/wm/src/common/platform/native_window.rs @@ -23,10 +23,9 @@ use windows::{ SetForegroundWindow, SetWindowLongPtrW, SetWindowPos, ShowWindowAsync, GWL_EXSTYLE, GWL_STYLE, GW_OWNER, HWND_NOTOPMOST, HWND_TOPMOST, SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, - SWP_HIDEWINDOW, SWP_NOACTIVATE, SWP_NOCOPYBITS, SWP_NOMOVE, + SWP_NOACTIVATE, SWP_NOCOPYBITS, SWP_NOMOVE, SWP_NOOWNERZORDER, SWP_NOSENDCHANGING, SWP_NOSIZE, SWP_NOZORDER, - SWP_SHOWWINDOW, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, - SW_SHOWNA, WINDOW_EX_STYLE, WINDOW_STYLE, WM_CLOSE, WS_CAPTION, + SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, WINDOW_EX_STYLE, WINDOW_STYLE, WM_CLOSE, WS_CAPTION, WS_CHILD, WS_DLGFRAME, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_MAXIMIZEBOX, WS_THICKFRAME, }, @@ -39,6 +38,7 @@ use crate::{ user_config::CornerStyle, windows::WindowState, }; +use crate::common::com::{set_cloak, CloakVisibility}; #[derive(Debug, Clone)] pub struct NativeWindow { @@ -508,12 +508,19 @@ impl NativeWindow { } pub fn show(&self) -> anyhow::Result<()> { - unsafe { ShowWindowAsync(HWND(self.handle), SW_SHOWNA) }.ok()?; - Ok(()) + self.set_visibility(true) } pub fn hide(&self) -> anyhow::Result<()> { - unsafe { ShowWindowAsync(HWND(self.handle), SW_HIDE) }.ok()?; + self.set_visibility(false) + } + + pub fn set_visibility(&self, visible: bool) -> anyhow::Result<()> { + let visibility_status = match visible { + true => CloakVisibility::VISIBLE, + false => CloakVisibility::HIDDEN, + }; + set_cloak(HWND(self.handle), &visibility_status); Ok(()) } @@ -550,10 +557,10 @@ impl NativeWindow { | SWP_ASYNCWINDOWPOS; // Whether to show or hide the window. - match is_visible { - true => swp_flags |= SWP_SHOWWINDOW, - false => swp_flags |= SWP_HIDEWINDOW, - }; + // match is_visible { + // true => swp_flags |= SWP_SHOWWINDOW, + // false => swp_flags |= SWP_HIDEWINDOW, + // }; // Whether the window should be shown above all other windows. let z_order = match state { @@ -594,6 +601,7 @@ impl NativeWindow { swp_flags, ) }?; + self.set_visibility(is_visible)?; } _ => { swp_flags |= SWP_FRAMECHANGED; @@ -609,6 +617,7 @@ impl NativeWindow { swp_flags, ) }?; + self.set_visibility(is_visible)?; // When there's a mismatch between the DPI of the monitor and the // window, the window might be sized incorrectly after the first @@ -626,6 +635,7 @@ impl NativeWindow { swp_flags, ) }?; + self.set_visibility(is_visible)?; } } };