feat(config): add matchers for removing titlebars

This commit adds a new field to the static config file, "remove_titlebar_applications", which allows
users to now use the full range of matching strategies to identify applications for which titlebars
should be removed. This is heavily discouraged for a number of reasons, and is unlikely to work with
a wide range of applications which now draw their own titlebar regions. The previous advice to use
in-application configuration settings to hide title bars if they exist is still valid.

resolve #805
This commit is contained in:
LGUG2Z
2024-12-27 11:37:00 -08:00
parent 692da90890
commit 79eda30f48
9 changed files with 160 additions and 12 deletions

View File

@@ -215,7 +215,7 @@ lazy_static! {
// Use app-specific titlebar removal options where possible
// eg. Windows Terminal, IntelliJ IDEA, Firefox
static ref NO_TITLEBAR: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
static ref NO_TITLEBAR: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![]));
static ref WINDOWS_BY_BAR_HWNDS: Arc<Mutex<HashMap<isize, VecDeque<isize>>>> =
Arc::new(Mutex::new(HashMap::new()));

View File

@@ -1667,10 +1667,24 @@ impl WindowManager {
reply.write_all(config.as_bytes())?;
}
SocketMessage::RemoveTitleBar(_, ref id) => {
SocketMessage::RemoveTitleBar(identifier, ref id) => {
let mut identifiers = NO_TITLEBAR.lock();
if !identifiers.contains(id) {
identifiers.push(id.clone());
let mut should_push = true;
for i in &*identifiers {
if let MatchingRule::Simple(i) = i {
if i.id.eq(id) {
should_push = false;
}
}
}
if should_push {
identifiers.push(MatchingRule::Simple(IdWithIdentifier {
kind: identifier,
id: id.clone(),
matching_strategy: Option::from(MatchingStrategy::Legacy),
}));
}
}
SocketMessage::ToggleTitleBars => {

View File

@@ -353,10 +353,11 @@ impl WindowManager {
if !workspace_contains_window && !needs_reconciliation {
let floating_applications = FLOATING_APPLICATIONS.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let mut should_float = false;
if !floating_applications.is_empty() {
let regex_identifiers = REGEX_IDENTIFIERS.lock();
if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) =
(window.title(), window.exe(), window.class(), window.path())
{

View File

@@ -46,6 +46,7 @@ use crate::IGNORE_IDENTIFIERS;
use crate::LAYERED_WHITELIST;
use crate::MANAGE_IDENTIFIERS;
use crate::MONITOR_INDEX_PREFERENCES;
use crate::NO_TITLEBAR;
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
use crate::REGEX_IDENTIFIERS;
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
@@ -373,6 +374,9 @@ pub struct StaticConfig {
#[serde(skip_serializing_if = "Option::is_none")]
// this option is a little special because it is only consumed by komorebic
pub bar_configurations: Option<Vec<PathBuf>>,
/// HEAVILY DISCOURAGED: Identify applications for which komorebi should forcibly remove title bars
#[serde(skip_serializing_if = "Option::is_none")]
pub remove_titlebar_applications: Option<Vec<MatchingRule>>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
@@ -627,6 +631,7 @@ impl From<&WindowManager> for StaticConfig {
),
slow_application_identifiers: Option::from(SLOW_APPLICATION_IDENTIFIERS.lock().clone()),
bar_configurations: None,
remove_titlebar_applications: Option::from(NO_TITLEBAR.lock().clone()),
}
}
}
@@ -773,6 +778,7 @@ impl StaticConfig {
let mut transparency_blacklist = TRANSPARENCY_BLACKLIST.lock();
let mut slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock();
let mut floating_applications = FLOATING_APPLICATIONS.lock();
let mut no_titlebar_applications = NO_TITLEBAR.lock();
if let Some(rules) = &mut self.ignore_rules {
populate_rules(rules, &mut ignore_identifiers, &mut regex_identifiers)?;
@@ -818,6 +824,10 @@ impl StaticConfig {
)?;
}
if let Some(rules) = &mut self.remove_titlebar_applications {
populate_rules(rules, &mut no_titlebar_applications, &mut regex_identifiers)?;
}
if let Some(stackbar) = &self.stackbar {
if let Some(height) = &stackbar.height {
STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);

View File

@@ -784,7 +784,7 @@ pub struct RuleDebug {
pub matches_layered_whitelist: Option<MatchingRule>,
pub matches_floating_applications: Option<MatchingRule>,
pub matches_wsl2_gui: Option<String>,
pub matches_no_titlebar: Option<String>,
pub matches_no_titlebar: Option<MatchingRule>,
}
#[allow(clippy::too_many_arguments)]
@@ -889,9 +889,19 @@ fn window_is_eligible(
allow
};
let allow_titlebar_removed = {
let titlebars_removed = NO_TITLEBAR.lock();
titlebars_removed.contains(exe_name)
let titlebars_removed = NO_TITLEBAR.lock();
let allow_titlebar_removed = if let Some(rule) = should_act(
title,
exe_name,
class,
path,
&titlebars_removed,
&regex_identifiers,
) {
debug.matches_no_titlebar = Some(rule);
true
} else {
false
};
{

View File

@@ -56,6 +56,7 @@ use crate::current_virtual_desktop;
use crate::load_configuration;
use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::should_act;
use crate::should_act_individual;
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
use crate::stackbar_manager::STACKBAR_LABEL;
@@ -1430,6 +1431,7 @@ impl WindowManager {
tracing::info!("restoring all hidden windows");
let no_titlebar = NO_TITLEBAR.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let known_transparent_hwnds = transparency_manager::known_hwnds();
let border_implementation = border_manager::IMPLEMENTATION.load();
@@ -1445,7 +1447,17 @@ impl WindowManager {
for containers in workspace.containers_mut() {
for window in containers.windows_mut() {
if no_titlebar.contains(&window.exe()?) {
let should_remove_titlebar_for_window = should_act(
&window.title().unwrap_or_default(),
&window.exe().unwrap_or_default(),
&window.class().unwrap_or_default(),
&window.path().unwrap_or_default(),
&no_titlebar,
&regex_identifiers,
)
.is_some();
if should_remove_titlebar_for_window {
window.add_title_bar()?;
}

View File

@@ -24,6 +24,7 @@ use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::container::Container;
use crate::ring::Ring;
use crate::should_act;
use crate::stackbar_manager;
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
use crate::static_config::WorkspaceConfig;
@@ -35,6 +36,7 @@ use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::NO_TITLEBAR;
use crate::REGEX_IDENTIFIERS;
use crate::REMOVE_TITLEBARS;
#[allow(clippy::struct_field_names)]
@@ -354,6 +356,7 @@ impl Workspace {
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
let no_titlebar = NO_TITLEBAR.lock().clone();
let regex_identifiers = REGEX_IDENTIFIERS.lock().clone();
let container_padding = self.container_padding().unwrap_or(0);
let containers = self.containers_mut();
@@ -383,9 +386,19 @@ impl Workspace {
.focused_window()
.is_some_and(|w| w.hwnd == window.hwnd)
{
if should_remove_titlebars && no_titlebar.contains(&window.exe()?) {
let should_remove_titlebar_for_window = should_act(
&window.title().unwrap_or_default(),
&window.exe().unwrap_or_default(),
&window.class().unwrap_or_default(),
&window.path().unwrap_or_default(),
&no_titlebar,
&regex_identifiers,
)
.is_some();
if should_remove_titlebars && should_remove_titlebar_for_window {
window.remove_title_bar()?;
} else if no_titlebar.contains(&window.exe()?) {
} else if should_remove_titlebar_for_window {
window.add_title_bar()?;
}

View File

@@ -1122,6 +1122,11 @@
}
]
},
"icon_scale": {
"description": "Scale of the icons relative to the font_size [[1.0-2.0]]. (default: 1.4)",
"type": "number",
"format": "float"
},
"left_widgets": {
"description": "Left side widgets (ordered left-to-right)",
"type": "array",

View File

@@ -1448,6 +1448,89 @@
]
}
},
"remove_titlebar_applications": {
"description": "HEAVILY DISCOURAGED: Identify applications for which komorebi should forcibly remove title bars",
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
},
{
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"kind"
],
"properties": {
"id": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"Exe",
"Class",
"Title",
"Path"
]
},
"matching_strategy": {
"type": "string",
"enum": [
"Legacy",
"Equals",
"StartsWith",
"EndsWith",
"Contains",
"Regex",
"DoesNotEndWith",
"DoesNotStartWith",
"DoesNotEqual",
"DoesNotContain"
]
}
}
}
}
]
}
},
"resize_delta": {
"description": "Delta to resize windows by (default 50)",
"type": "integer",