diff --git a/Cargo.lock b/Cargo.lock index f2841e4c..7080a3ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -528,6 +528,8 @@ dependencies = [ "uds_windows", "which", "windows", + "windows-implement", + "windows-interface", "winput", "winreg", ] @@ -1552,9 +1554,33 @@ version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ + "windows-implement", + "windows-interface", "windows-targets", ] +[[package]] +name = "windows-implement" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce87ca8e3417b02dc2a8a22769306658670ec92d78f1bd420d6310a67c245c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-sys" version = "0.42.0" diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index a3878ee8..49fd9f9b 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -211,6 +211,7 @@ pub enum MoveBehaviour { pub enum HidingBehaviour { Hide, Minimize, + Cloak, } #[derive( diff --git a/komorebi/Cargo.toml b/komorebi/Cargo.toml index 8cc345c9..1feb8baa 100644 --- a/komorebi/Cargo.toml +++ b/komorebi/Cargo.toml @@ -41,10 +41,14 @@ uds_windows = "1" which = "4" winput = "0.2" winreg = "0.10" - +windows-interface = { version = "0.44" } +windows-implement = { version = "0.44" } [dependencies.windows] version = "0.44" features = [ + "implement", + "Win32_System_Com", + "Win32_UI_Shell_Common", # for IObjectArray "Win32_Foundation", "Win32_Graphics_Dwm", "Win32_Graphics_Gdi", diff --git a/komorebi/src/com/interfaces.rs b/komorebi/src/com/interfaces.rs new file mode 100644 index 00000000..1d4afe1d --- /dev/null +++ b/komorebi/src/com/interfaces.rs @@ -0,0 +1,246 @@ +// This code is largely taken verbatim from this repository: https://github.com/Ciantic/AltTabAccessor +// which the author Jari Pennanen (Ciantic) has kindly made available with the MIT license, available +// in full here: https://github.com/Ciantic/AltTabAccessor/blob/main/LICENSE.txt + +#![allow(clippy::use_self)] + +use std::ffi::c_void; +use std::ops::Deref; +use windows::core::IUnknown; +use windows::core::IUnknown_Vtbl; +use windows::core::GUID; +use windows::core::HRESULT; +use windows::core::HSTRING; +use windows::core::PCWSTR; +use windows::core::PWSTR; +use windows::Win32::Foundation::BOOL; +use windows::Win32::Foundation::HWND; +use windows::Win32::Foundation::RECT; +use windows::Win32::Foundation::SIZE; +use windows::Win32::UI::Shell::Common::IObjectArray; + +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 const 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 + } +} + +#[allow(non_upper_case_globals)] +pub const CLSID_ImmersiveShell: GUID = GUID { + data1: 0xC2F0_3A33, + data2: 0x21F5, + data3: 0x47FA, + data4: [0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39], +}; + +#[allow(clippy::upper_case_acronyms)] +type DWORD = u32; +#[allow(clippy::upper_case_acronyms)] +type INT = i32; +#[allow(clippy::upper_case_acronyms)] +type LPVOID = *mut c_void; +#[allow(clippy::upper_case_acronyms)] +type UINT = u32; +#[allow(clippy::upper_case_acronyms)] +type ULONG = u32; +#[allow(clippy::upper_case_acronyms)] +type ULONGLONG = u64; + +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; + +#[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; +} diff --git a/komorebi/src/com/mod.rs b/komorebi/src/com/mod.rs new file mode 100644 index 00000000..5ec89c81 --- /dev/null +++ b/komorebi/src/com/mod.rs @@ -0,0 +1,104 @@ +// This code is largely taken verbatim from this repository: https://github.com/Ciantic/AltTabAccessor +// which the author Jari Pennanen (Ciantic) has kindly made available with the MIT license, available +// in full here: https://github.com/Ciantic/AltTabAccessor/blob/main/LICENSE.txt + +mod interfaces; + +use interfaces::CLSID_ImmersiveShell; +use interfaces::IApplicationViewCollection; +use interfaces::IServiceProvider; + +use std::ffi::c_void; + +use windows::core::Interface; +use windows::core::Vtable; +use windows::Win32::Foundation::HWND; +use windows::Win32::System::Com::CoCreateInstance; +use windows::Win32::System::Com::CoInitializeEx; +use windows::Win32::System::Com::CoUninitialize; +use windows::Win32::System::Com::CLSCTX_ALL; +use windows::Win32::System::Com::COINIT_APARTMENTTHREADED; + +struct ComInit(); + +impl ComInit { + pub fn new() -> Self { + unsafe { + // Notice: Only COINIT_APARTMENTTHREADED works correctly! + // + // Not COINIT_MULTITHREADED or CoIncrementMTAUsage, they cause a seldom crashes in threading tests. + CoInitializeEx(None, COINIT_APARTMENTTHREADED).unwrap(); + } + Self() + } +} + +impl Drop for ComInit { + fn drop(&mut self) { + unsafe { + CoUninitialize(); + } + } +} + +thread_local! { + static COM_INIT: ComInit = ComInit::new(); +} + +fn get_iservice_provider() -> IServiceProvider { + COM_INIT.with(|_| unsafe { CoCreateInstance(&CLSID_ImmersiveShell, 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) } + }) +} + +#[no_mangle] +pub extern "C" fn SetCloak(hwnd: HWND, cloak_type: u32, flags: i32) { + COM_INIT.with(|_| { + let provider = get_iservice_provider(); + let view_collection = get_iapplication_view_collection(&provider); + let mut view = None; + unsafe { + if view_collection.get_view_for_hwnd(hwnd, &mut view).is_err() { + tracing::error!( + "could not get view for hwnd {} due to os error: {}", + hwnd.0, + std::io::Error::last_os_error() + ); + } + }; + + view.map_or_else( + || { + tracing::error!("no view was found for {}", hwnd.0,); + }, + |view| { + unsafe { + if view.set_cloak(cloak_type, flags).is_err() { + tracing::error!( + "could not change the cloaking status for hwnd {} due to os error: {}", + hwnd.0, + std::io::Error::last_os_error() + ); + } + }; + }, + ); + }); +} diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 4311bc05..362816c7 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -57,6 +57,7 @@ use crate::windows_api::WindowsApi; mod ring; mod border; +mod com; mod container; mod hidden; mod monitor; diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index fbf25ba5..32b9d16f 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -47,6 +47,7 @@ use crate::BORDER_COLOUR_CURRENT; use crate::BORDER_COLOUR_SINGLE; use crate::BORDER_COLOUR_STACK; use crate::BORDER_ENABLED; +use crate::BORDER_HIDDEN; use crate::BORDER_HWND; use crate::BORDER_OFFSET; use crate::BORDER_OVERFLOW_IDENTIFIERS; @@ -156,6 +157,7 @@ impl WindowManager { if self.focused_workspace()?.visible_windows().is_empty() { let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); border.hide()?; + BORDER_HIDDEN.store(true, Ordering::SeqCst); } } _ => {} diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index c41fdd2b..f0fe286a 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -490,7 +490,10 @@ impl WindowManager { } } } - WindowManagerEvent::DisplayChange(..) | WindowManagerEvent::MouseCapture(..) => {} + WindowManagerEvent::DisplayChange(..) + | WindowManagerEvent::MouseCapture(..) + | WindowManagerEvent::Cloak(..) + | WindowManagerEvent::Uncloak(..) => {} }; if *self.focused_workspace()?.tile() && BORDER_ENABLED.load(Ordering::SeqCst) { @@ -502,6 +505,8 @@ impl WindowManager { } WindowManagerEvent::MoveResizeEnd(_, window) | WindowManagerEvent::Show(_, window) + | WindowManagerEvent::Cloak(_, window) + | WindowManagerEvent::Uncloak(_, window) | WindowManagerEvent::FocusChange(_, window) | WindowManagerEvent::Hide(_, window) | WindowManagerEvent::Minimize(_, window) => { diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index fe575ecd..98e21b02 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -1,3 +1,4 @@ +use crate::com::SetCloak; use std::convert::TryFrom; use std::fmt::Display; use std::fmt::Formatter; @@ -153,17 +154,10 @@ impl Window { match *hiding_behaviour { HidingBehaviour::Hide => WindowsApi::hide_window(self.hwnd()), HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd()), + HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 2), } } - pub fn minimize(self) { - WindowsApi::minimize_window(self.hwnd()); - } - - pub fn close(self) -> Result<()> { - WindowsApi::close_window(self.hwnd()) - } - pub fn restore(self) { let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock(); if let Some(idx) = programmatically_hidden_hwnds @@ -173,7 +167,21 @@ impl Window { programmatically_hidden_hwnds.remove(idx); } - WindowsApi::restore_window(self.hwnd()); + let hiding_behaviour = HIDING_BEHAVIOUR.lock(); + match *hiding_behaviour { + HidingBehaviour::Hide | HidingBehaviour::Minimize => { + WindowsApi::restore_window(self.hwnd()); + } + HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 0), + } + } + + pub fn minimize(self) { + WindowsApi::minimize_window(self.hwnd()); + } + + pub fn close(self) -> Result<()> { + WindowsApi::close_window(self.hwnd()) } pub fn maximize(self) { @@ -385,8 +393,14 @@ impl Window { let is_cloaked = self.is_cloaked()?; let mut allow_cloaked = false; - if let Some(WindowManagerEvent::Hide(_, _)) = event { - allow_cloaked = true; + + if let Some(event) = event { + if matches!( + event, + WindowManagerEvent::Hide(_, _) | WindowManagerEvent::Cloak(_, _) + ) { + allow_cloaked = true; + } } match (allow_cloaked, is_cloaked) { diff --git a/komorebi/src/window_manager_event.rs b/komorebi/src/window_manager_event.rs index 62a045b4..59697ddf 100644 --- a/komorebi/src/window_manager_event.rs +++ b/komorebi/src/window_manager_event.rs @@ -14,8 +14,10 @@ pub enum WindowManagerEvent { Destroy(WinEvent, Window), FocusChange(WinEvent, Window), Hide(WinEvent, Window), + Cloak(WinEvent, Window), Minimize(WinEvent, Window), Show(WinEvent, Window), + Uncloak(WinEvent, Window), MoveResizeStart(WinEvent, Window), MoveResizeEnd(WinEvent, Window), MouseCapture(WinEvent, Window), @@ -47,12 +49,18 @@ impl Display for WindowManagerEvent { Self::Hide(winevent, window) => { write!(f, "Hide (WinEvent: {}, Window: {})", winevent, window) } + Self::Cloak(winevent, window) => { + write!(f, "Cloak (WinEvent: {}, Window: {})", winevent, window) + } Self::Minimize(winevent, window) => { write!(f, "Minimize (WinEvent: {}, Window: {})", winevent, window) } Self::Show(winevent, window) => { write!(f, "Show (WinEvent: {}, Window: {})", winevent, window) } + Self::Uncloak(winevent, window) => { + write!(f, "Uncloak (WinEvent: {}, Window: {})", winevent, window) + } Self::MoveResizeStart(winevent, window) => { write!( f, @@ -90,8 +98,10 @@ impl WindowManagerEvent { Self::Destroy(_, window) | Self::FocusChange(_, window) | Self::Hide(_, window) + | Self::Cloak(_, window) | Self::Minimize(_, window) | Self::Show(_, window) + | Self::Uncloak(_, window) | Self::MoveResizeStart(_, window) | Self::MoveResizeEnd(_, window) | Self::MouseCapture(_, window) @@ -106,16 +116,17 @@ impl WindowManagerEvent { match winevent { WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)), - WinEvent::ObjectCloaked | WinEvent::ObjectHide => { - Option::from(Self::Hide(winevent, window)) - } + WinEvent::ObjectHide => Option::from(Self::Hide(winevent, window)), + WinEvent::ObjectCloaked => Option::from(Self::Cloak(winevent, window)), WinEvent::SystemMinimizeStart => Option::from(Self::Minimize(winevent, window)), - WinEvent::ObjectShow | WinEvent::ObjectUncloaked | WinEvent::SystemMinimizeEnd => { + WinEvent::ObjectShow | WinEvent::SystemMinimizeEnd => { Option::from(Self::Show(winevent, window)) } + WinEvent::ObjectUncloaked => Option::from(Self::Uncloak(winevent, window)), + WinEvent::ObjectFocus | WinEvent::SystemForeground => { Option::from(Self::FocusChange(winevent, window)) }