diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/input/mod.rs | 8 | ||||
| -rw-r--r-- | src/ui/hotkey_overlay.rs | 193 |
2 files changed, 183 insertions, 18 deletions
diff --git a/src/input/mod.rs b/src/input/mod.rs index d1d564cb..8d9e6d82 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -3009,6 +3009,7 @@ fn should_intercept_key( // But logically, nothing can inhibit its actions. Only opening it can be // inhibited. allow_inhibiting: false, + hotkey_overlay_title: None, }); } } @@ -3074,6 +3075,7 @@ fn find_bind( // It also makes no sense to inhibit the default power key handling. // Hardcoded binds must never be inhibited. allow_inhibiting: false, + hotkey_overlay_title: None, }); } @@ -3541,6 +3543,7 @@ mod tests { cooldown: None, allow_when_locked: false, allow_inhibiting: true, + hotkey_overlay_title: None, }]); let comp_mod = CompositorMod::Super; @@ -3726,6 +3729,7 @@ mod tests { cooldown: None, allow_when_locked: false, allow_inhibiting: true, + hotkey_overlay_title: None, }, Bind { key: Key { @@ -3737,6 +3741,7 @@ mod tests { cooldown: None, allow_when_locked: false, allow_inhibiting: true, + hotkey_overlay_title: None, }, Bind { key: Key { @@ -3748,6 +3753,7 @@ mod tests { cooldown: None, allow_when_locked: false, allow_inhibiting: true, + hotkey_overlay_title: None, }, Bind { key: Key { @@ -3759,6 +3765,7 @@ mod tests { cooldown: None, allow_when_locked: false, allow_inhibiting: true, + hotkey_overlay_title: None, }, Bind { key: Key { @@ -3770,6 +3777,7 @@ mod tests { cooldown: None, allow_when_locked: false, allow_inhibiting: true, + hotkey_overlay_title: None, }, ]); diff --git a/src/ui/hotkey_overlay.rs b/src/ui/hotkey_overlay.rs index 5a903a3c..c8165ede 100644 --- a/src/ui/hotkey_overlay.rs +++ b/src/ui/hotkey_overlay.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use std::iter::zip; use std::rc::Rc; -use niri_config::{Action, Config, Key, Modifiers, Trigger}; +use niri_config::{Action, Bind, Config, Key, Modifiers, Trigger}; use pangocairo::cairo::{self, ImageSurface}; use pangocairo::pango::{AttrColor, AttrInt, AttrList, AttrString, FontDescription, Weight}; use smithay::backend::renderer::element::Kind; @@ -125,6 +125,52 @@ impl HotkeyOverlay { } } +fn format_bind( + binds: &[Bind], + comp_mod: CompositorMod, + action: &Action, +) -> Option<(String, String)> { + let mut bind_with_non_null = None; + let mut bind_with_custom_title = None; + let mut found_null_title = false; + + for bind in binds { + if bind.action != *action { + continue; + } + + match &bind.hotkey_overlay_title { + Some(Some(_)) => { + bind_with_custom_title.get_or_insert(bind); + } + Some(None) => { + found_null_title = true; + } + None => { + bind_with_non_null.get_or_insert(bind); + } + } + } + + if bind_with_custom_title.is_none() && found_null_title { + return None; + } + + let mut title = None; + let key = if let Some(bind) = bind_with_custom_title.or(bind_with_non_null) { + if let Some(Some(custom)) = &bind.hotkey_overlay_title { + title = Some(custom.clone()); + } + + key_name(comp_mod, &bind.key) + } else { + String::from("(not bound)") + }; + let title = title.unwrap_or_else(|| action_name(action)); + + Some((format!(" {key} "), title)) +} + fn render( renderer: &mut GlesRenderer, config: &Config, @@ -212,8 +258,17 @@ fn render( actions.push(&Action::Screenshot); } + // Add actions with a custom hotkey-overlay-title. + for bind in binds { + if matches!(bind.hotkey_overlay_title, Some(Some(_))) { + // Avoid duplicate actions. + if !actions.contains(&&bind.action) { + actions.push(&bind.action); + } + } + } + // Add the spawn actions. - let mut spawn_actions = Vec::new(); for bind in binds.iter().filter(|bind| { matches!(bind.action, Action::Spawn(_)) // Only show binds with Mod or Super to filter out stuff like volume up/down. @@ -225,25 +280,14 @@ fn render( let action = &bind.action; // We only show one bind for each action, so we need to deduplicate the Spawn actions. - if !spawn_actions.contains(&action) { - spawn_actions.push(action); + if !actions.contains(&action) { + actions.push(action); } } - actions.extend(spawn_actions); let strings = actions .into_iter() - .map(|action| { - let key = config - .binds - .0 - .iter() - .find(|bind| bind.action == *action) - .map(|bind| key_name(comp_mod, &bind.key)) - .unwrap_or_else(|| String::from("(not bound)")); - - (format!(" {key} "), action_name(action)) - }) + .filter_map(|action| format_bind(binds, comp_mod, action)) .collect::<Vec<_>>(); let mut font = FontDescription::from_string(FONT); @@ -323,8 +367,16 @@ fn render( cr.rel_move_to((key_width + padding).into(), 0.); - layout.set_attributes(None); - layout.set_markup(action); + let (attrs, text) = match pango::parse_markup(action, '\0') { + Ok((attrs, text, _accel)) => (Some(attrs), text), + Err(err) => { + warn!("error parsing markup for key {key}: {err}"); + (None, action.into()) + } + }; + + layout.set_attributes(attrs.as_ref()); + layout.set_text(&text); pangocairo::functions::show_layout(&cr, &layout); cr.rel_move_to( @@ -473,3 +525,108 @@ fn prettify_keysym_name(name: &str) -> String { name.into() } } + +#[cfg(test)] +mod tests { + use insta::assert_snapshot; + + use super::*; + + #[track_caller] + fn check(config: &str, action: Action) -> String { + let config = Config::parse("test.kdl", config).unwrap(); + if let Some((key, title)) = format_bind(&config.binds.0, CompositorMod::Super, &action) { + format!("{key}: {title}") + } else { + String::from("None") + } + } + + #[test] + fn test_format_bind() { + // Not bound. + assert_snapshot!(check("", Action::Screenshot), @" (not bound) : Take a Screenshot"); + + // Bound with a default title. + assert_snapshot!( + check( + r#"binds { + Mod+P { screenshot; } + }"#, + Action::Screenshot, + ), + @" Super + P : Take a Screenshot" + ); + + // Custom title. + assert_snapshot!( + check( + r#"binds { + Mod+P hotkey-overlay-title="Hello" { screenshot; } + }"#, + Action::Screenshot, + ), + @" Super + P : Hello" + ); + + // Prefer first bind. + assert_snapshot!( + check( + r#"binds { + Mod+P { screenshot; } + Print { screenshot; } + }"#, + Action::Screenshot, + ), + @" Super + P : Take a Screenshot" + ); + + // Prefer bind with custom title. + assert_snapshot!( + check( + r#"binds { + Mod+P { screenshot; } + Print hotkey-overlay-title="My Cool Bind" { screenshot; } + }"#, + Action::Screenshot, + ), + @" PrtSc : My Cool Bind" + ); + + // Prefer first bind with custom title. + assert_snapshot!( + check( + r#"binds { + Mod+P hotkey-overlay-title="First" { screenshot; } + Print hotkey-overlay-title="My Cool Bind" { screenshot; } + }"#, + Action::Screenshot, + ), + @" Super + P : First" + ); + + // Any bind with null title hides it. + assert_snapshot!( + check( + r#"binds { + Mod+P { screenshot; } + Print hotkey-overlay-title=null { screenshot; } + }"#, + Action::Screenshot, + ), + @"None" + ); + + // Custom title takes preference over null. + assert_snapshot!( + check( + r#"binds { + Mod+P hotkey-overlay-title="Hello" { screenshot; } + Print hotkey-overlay-title=null { screenshot; } + }"#, + Action::Screenshot, + ), + @" Super + P : Hello" + ); + } +} |
