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()?)?; }