diff --git a/glide.default.toml b/glide.default.toml index e8a64b0..c9bc3fa 100644 --- a/glide.default.toml +++ b/glide.default.toml @@ -35,6 +35,15 @@ group_bars.vertical_placement = "right" # Enables the status icon. status_icon.enable = true +# Whether to include Glide's default key bindings. +# +# Note: Default keys are always included if there is no keys section of the +# config. You can disable this by adding an empty `[keys]` section to your +# config file. +# +# Individual key bindings can be disabled by setting the key to "disabled". +default_keys = false + [keys] # Note: Modifier and key names must be capitalized. diff --git a/src/actor/mouse.rs b/src/actor/mouse.rs index d61e3db..34639ca 100644 --- a/src/actor/mouse.rs +++ b/src/actor/mouse.rs @@ -147,8 +147,9 @@ impl Mouse { state.screens = frames; state.converter = converter; } - Request::ConfigUpdated(config) => { - *self.config.borrow_mut() = config; + Request::ConfigUpdated(new_config) => { + drop(config); + *self.config.borrow_mut() = new_config; self.apply_config(); } } diff --git a/src/config.rs b/src/config.rs index e1310a6..5dad219 100644 --- a/src/config.rs +++ b/src/config.rs @@ -51,7 +51,20 @@ pub struct Config { #[serde(default)] struct ConfigPartial { settings: SettingsPartial, - keys: Option>, + keys: Option>, +} + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +enum WmCommandOrDisable { + WmCommand(WmCommand), + Disable(Disabled), +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +enum Disabled { + Disable, } #[derive(PartialConfig!)] @@ -66,6 +79,7 @@ pub struct Settings { pub focus_follows_mouse: bool, pub outer_gap: f64, pub inner_gap: f64, + pub default_keys: bool, #[derive_args(GroupBarsPartial)] pub group_bars: GroupBars, #[derive_args(StatusIconPartial)] @@ -143,6 +157,10 @@ impl ConfigPartial { fn validate(self) -> Result { let mut keys = Vec::new(); for (key, cmd) in self.keys.unwrap_or_default() { + let cmd = match cmd { + WmCommandOrDisable::WmCommand(wm_command) => wm_command, + WmCommandOrDisable::Disable(_) => continue, + }; let Ok(key) = Hotkey::from_str(&key) else { return Err(SpannedError { message: format!("Could not parse hotkey: {key}"), @@ -158,9 +176,16 @@ impl ConfigPartial { } fn merge(low: Self, high: Self) -> Self { + let mut keys = + if high.settings.default_keys.unwrap_or(Config::default().settings.default_keys) { + low.keys.unwrap_or_default() + } else { + Default::default() + }; + keys.extend(high.keys.unwrap_or_default()); Self { settings: SettingsPartial::merge(low.settings, high.settings), - keys: high.keys.or(low.keys), + keys: Some(keys), } } } @@ -247,4 +272,90 @@ mod tests { fn default_settings_match_unspecified_setting_values() { assert_eq!(Config::default().settings, Config::parse("").unwrap().settings); } + + #[test] + fn default_keys_false_excludes_default_bindings() { + let config = Config::parse( + r#" + [settings] + default_keys = false + + [keys] + "Alt + Q" = "debug" + "#, + ) + .unwrap(); + + // Should only have our custom key, not the defaults + assert_eq!(config.keys.len(), 1); + let (hotkey, _cmd) = &config.keys[0]; + assert_eq!(hotkey.to_string(), "Alt + KeyQ"); + } + + #[test] + fn default_keys_true_includes_default_bindings() { + let config = Config::parse( + r#" + [settings] + default_keys = true + + [keys] + "Alt + Q" = "debug" + "#, + ) + .unwrap(); + + // Should have default keys plus our custom key + let default_key_count = Config::default().keys.len(); + assert_eq!(config.keys.len(), default_key_count + 1); + + // Our custom key should be present + assert!(config.keys.iter().any(|(hk, _)| hk.to_string() == "Alt + KeyQ")); + } + + #[test] + fn disable_removes_key_binding() { + let config = Config::parse( + r#" + [settings] + default_keys = false + + [keys] + "Alt + Q" = "debug" + "Alt + W" = "disable" + "#, + ) + .unwrap(); + + // "disable" key should not appear in final config + assert_eq!(config.keys.len(), 1); + assert!(config.keys.iter().any(|(hk, _)| hk.to_string() == "Alt + KeyQ")); + assert!(!config.keys.iter().any(|(hk, _)| hk.to_string() == "Alt + KeyW")); + } + + #[test] + fn disable_can_override_default_key() { + // First verify Alt+H exists in defaults + let default_config = Config::default(); + assert!( + default_config.keys.iter().any(|(hk, _)| hk.to_string() == "Alt + KeyH"), + "Alt+H should be a default key binding" + ); + + let config = Config::parse( + r#" + [settings] + default_keys = true + + [keys] + "Alt + H" = "disable" + "#, + ) + .unwrap(); + + // Alt+H should be removed even though it's in defaults + assert!(!config.keys.iter().any(|(hk, _)| hk.to_string() == "Alt + KeyH")); + // But other default keys should still be present + assert!(config.keys.iter().any(|(hk, _)| hk.to_string() == "Alt + KeyJ")); + } }