From 987dc2b8dd1e94153268f436272b903e1da8a45a Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Wed, 27 Oct 2021 17:00:27 -0700 Subject: [PATCH] feat(wm): add cmds for titlebar removal This commit introduces some basic commands to read from/add to a whitelist of applications which that user has determined that are safe to remove the titlebar from. Titlebars and removed and added by removing and adding the WS_CAPTION and WS_THICKFRAME styles (this is the approach that Workspacer also takes) from the windows. Wherever possible, the user should prefer native preferences and settings that remove the titlebar in popular apps (Windows Terminal, Firefox etc). re #57 --- komorebi-core/src/lib.rs | 2 ++ komorebi/src/main.rs | 6 +++++- komorebi/src/process_command.rs | 15 ++++++++++++++- komorebi/src/window.rs | 26 +++++++++++++++++++++++++- komorebi/src/window_manager.rs | 15 ++++++++++++++- komorebi/src/windows_api.rs | 6 +++--- komorebi/src/workspace.rs | 12 ++++++++++++ komorebic/src/main.rs | 23 +++++++++++++++++++++++ 8 files changed, 98 insertions(+), 7 deletions(-) diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 9376f7a4..7b6a9348 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -89,6 +89,8 @@ pub enum SocketMessage { ManageRule(ApplicationIdentifier, String), IdentifyTrayApplication(ApplicationIdentifier, String), IdentifyBorderOverflow(ApplicationIdentifier, String), + RemoveTitleBar(ApplicationIdentifier, String), + ToggleTitleBars, State, Query(StateQuery), FocusFollowsMouse(FocusFollowsMouseImplementation, bool), diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index f3dceb7a..659154ce 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -87,9 +87,13 @@ lazy_static! { ])); static ref SUBSCRIPTION_PIPES: Arc>> = Arc::new(Mutex::new(HashMap::new())); + // Use app-specific titlebar removal options where possible + // eg. Windows Terminal, IntelliJ IDEA, Firefox + static ref NO_TITLEBAR: Arc>> = Arc::new(Mutex::new(vec![])); } pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false); +pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false); fn setup() -> Result<(WorkerGuard, WorkerGuard)> { if std::env::var("RUST_LIB_BACKTRACE").is_err() { @@ -327,7 +331,7 @@ fn main() -> Result<()> { tracing::error!("received ctrl-c, restoring all hidden windows and terminating process"); - wm.lock().restore_all_windows(); + wm.lock().restore_all_windows()?; std::process::exit(130); } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 4f281e39..84411f40 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -30,6 +30,8 @@ use crate::BORDER_OVERFLOW_IDENTIFIERS; use crate::CUSTOM_FFM; use crate::FLOAT_IDENTIFIERS; use crate::MANAGE_IDENTIFIERS; +use crate::NO_TITLEBAR; +use crate::REMOVE_TITLEBARS; use crate::SUBSCRIPTION_PIPES; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; use crate::WORKSPACE_RULES; @@ -210,7 +212,7 @@ impl WindowManager { tracing::info!( "received stop command, restoring all hidden windows and terminating process" ); - self.restore_all_windows(); + self.restore_all_windows()?; std::process::exit(0) } SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => { @@ -453,6 +455,17 @@ impl WindowManager { let mut pipes = SUBSCRIPTION_PIPES.lock(); pipes.remove(&subscriber); } + SocketMessage::RemoveTitleBar(_, id) => { + let mut identifiers = NO_TITLEBAR.lock(); + if !identifiers.contains(&id) { + identifiers.push(id); + } + } + SocketMessage::ToggleTitleBars => { + let current = REMOVE_TITLEBARS.load(Ordering::SeqCst); + REMOVE_TITLEBARS.store(!current, Ordering::SeqCst); + self.update_focused_workspace(false)?; + } }; tracing::info!("processed"); diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 1a18117f..9564f2d5 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -21,6 +21,7 @@ use crate::FLOAT_IDENTIFIERS; use crate::HIDDEN_HWNDS; use crate::LAYERED_EXE_WHITELIST; use crate::MANAGE_IDENTIFIERS; +use crate::NO_TITLEBAR; use crate::WSL2_UI_PROCESSES; #[derive(Debug, Clone, Copy)] @@ -211,6 +212,20 @@ impl Window { WindowsApi::set_focus(self.hwnd()) } + pub fn remove_title_bar(self) -> Result<()> { + let mut style = self.style()?; + style.remove(GwlStyle::CAPTION); + style.remove(GwlStyle::THICKFRAME); + self.update_style(style) + } + + pub fn add_title_bar(self) -> Result<()> { + let mut style = self.style()?; + style.insert(GwlStyle::CAPTION); + style.insert(GwlStyle::THICKFRAME); + self.update_style(style) + } + #[allow(dead_code)] pub fn update_style(self, style: GwlStyle) -> Result<()> { WindowsApi::update_style(self.hwnd(), isize::try_from(style.bits())?) @@ -295,10 +310,19 @@ impl Window { wsl2_ui_processes.contains(&exe_name) }; + let allow_titlebar_removed = { + let titlebars_removed = NO_TITLEBAR.lock(); + titlebars_removed.contains(&exe_name) + }; + let style = self.style()?; let ex_style = self.ex_style()?; - if (allow_wsl2_gui || style.contains(GwlStyle::CAPTION) && ex_style.contains(GwlExStyle::WINDOWEDGE)) + if ( + allow_wsl2_gui + || allow_titlebar_removed + || style.contains(GwlStyle::CAPTION) && ex_style.contains(GwlExStyle::WINDOWEDGE) + ) && !ex_style.contains(GwlExStyle::DLGMODALFRAME) // Get a lot of dupe events coming through that make the redrawing go crazy // on FocusChange events if I don't filter out this one. But, if we are diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 5a816444..5e56ca5f 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -2,6 +2,7 @@ use std::collections::VecDeque; use std::io::ErrorKind; use std::num::NonZeroUsize; use std::path::PathBuf; +use std::sync::atomic::Ordering; use std::sync::Arc; use std::thread; @@ -38,6 +39,8 @@ use crate::BORDER_OVERFLOW_IDENTIFIERS; use crate::FLOAT_IDENTIFIERS; use crate::LAYERED_EXE_WHITELIST; use crate::MANAGE_IDENTIFIERS; +use crate::NO_TITLEBAR; +use crate::REMOVE_TITLEBARS; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; use crate::WORKSPACE_RULES; @@ -63,6 +66,7 @@ pub struct State { pub work_area_offset: Option, pub focus_follows_mouse: Option, pub has_pending_raise_op: bool, + pub remove_titlebars: bool, pub float_identifiers: Vec, pub manage_identifiers: Vec, pub layered_exe_whitelist: Vec, @@ -79,6 +83,7 @@ impl From<&WindowManager> for State { work_area_offset: wm.work_area_offset, focus_follows_mouse: wm.focus_follows_mouse.clone(), has_pending_raise_op: wm.has_pending_raise_op, + remove_titlebars: REMOVE_TITLEBARS.load(Ordering::SeqCst), float_identifiers: FLOAT_IDENTIFIERS.lock().clone(), manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(), layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().clone(), @@ -664,18 +669,26 @@ impl WindowManager { } #[tracing::instrument(skip(self))] - pub fn restore_all_windows(&mut self) { + pub fn restore_all_windows(&mut self) -> Result<()> { tracing::info!("restoring all hidden windows"); + let no_titlebar = NO_TITLEBAR.lock(); + for monitor in self.monitors_mut() { for workspace in monitor.workspaces_mut() { for containers in workspace.containers_mut() { for window in containers.windows_mut() { + if no_titlebar.contains(&window.exe()?) { + window.add_title_bar()?; + } + window.restore(); } } } } + + Ok(()) } #[tracing::instrument(skip(self))] diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 6a88ea83..2f224b10 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -408,9 +408,9 @@ impl WindowsApi { } fn window_long_ptr_w(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> Result { - Result::from(WindowsResult::from(unsafe { - GetWindowLongPtrW(hwnd, index) - })) + // Can return 0, which does not always mean that an error has occurred + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw + Result::from(unsafe { WindowsResult::Ok(GetWindowLongPtrW(hwnd, index)) }) } #[allow(dead_code)] diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index b841e3ce..1a10a526 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -1,5 +1,6 @@ use std::collections::VecDeque; use std::num::NonZeroUsize; +use std::sync::atomic::Ordering; use color_eyre::eyre::anyhow; use color_eyre::Result; @@ -20,6 +21,8 @@ use crate::container::Container; use crate::ring::Ring; use crate::window::Window; use crate::windows_api::WindowsApi; +use crate::NO_TITLEBAR; +use crate::REMOVE_TITLEBARS; #[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)] pub struct Workspace { @@ -184,9 +187,18 @@ impl Workspace { self.resize_dimensions(), ); + let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst); + let no_titlebar = { NO_TITLEBAR.lock().clone() }; + let windows = self.visible_windows_mut(); for (i, window) in windows.into_iter().enumerate() { if let (Some(window), Some(layout)) = (window, layouts.get(i)) { + if should_remove_titlebars && no_titlebar.contains(&window.exe()?) { + window.remove_title_bar()?; + } else if no_titlebar.contains(&window.exe()?) { + window.add_title_bar()?; + } + window.set_position(layout, invisible_borders, false)?; } } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index cd111f15..c9f1dc30 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -261,6 +261,7 @@ gen_application_target_subcommand_args! { ManageRule, IdentifyTrayApplication, IdentifyBorderOverflow, + RemoveTitleBar, } #[derive(Clap, AhkFunction)] @@ -493,6 +494,11 @@ enum SubCommand { /// Identify an application that has overflowing borders #[clap(setting = AppSettings::ArgRequiredElseHelp)] IdentifyBorderOverflow(IdentifyBorderOverflow), + /// Whitelist an application for title bar removal + #[clap(setting = AppSettings::ArgRequiredElseHelp)] + RemoveTitleBar(RemoveTitleBar), + /// Toggle title bars for whitelisted applications + ToggleTitleBars, /// Enable or disable focus follows mouse for the operating system #[clap(setting = AppSettings::ArgRequiredElseHelp)] FocusFollowsMouse(FocusFollowsMouse), @@ -895,6 +901,23 @@ fn main() -> Result<()> { &*SocketMessage::IdentifyBorderOverflow(target.identifier, target.id).as_bytes()?, )?; } + SubCommand::RemoveTitleBar(target) => { + match target.identifier { + ApplicationIdentifier::Exe => {} + _ => { + return Err(anyhow!( + "this command requires applications to be identified by their exe" + )) + } + } + + send_message( + &*SocketMessage::RemoveTitleBar(target.identifier, target.id).as_bytes()?, + )?; + } + SubCommand::ToggleTitleBars => { + send_message(&*SocketMessage::ToggleTitleBars.as_bytes()?)?; + } SubCommand::Manage => { send_message(&*SocketMessage::ManageFocusedWindow.as_bytes()?)?; }