From 6826338335c469a5d2d1f93fb04ecd3ac121ec8d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 16 May 2022 21:46:20 -0700 Subject: [PATCH] Window-based modmap/keymap switcher for GNOME --- src/client/gnome_client.rs | 22 ++++++++++++++++++++++ src/client/mod.rs | 23 +++++++++++++++++++++++ src/client/null_client.rs | 4 ++++ src/client/sway_client.rs | 4 ++++ src/client/x11_client.rs | 4 ++++ src/config/application.rs | 2 +- src/config/keymap.rs | 5 +++-- src/config/modmap.rs | 5 +++-- src/config/tests.rs | 18 ++++++++++++++++++ src/event_handler.rs | 36 +++++++++++++++++++++++++++++++++--- 10 files changed, 115 insertions(+), 8 deletions(-) diff --git a/src/client/gnome_client.rs b/src/client/gnome_client.rs index ee0c3c7..a356abe 100644 --- a/src/client/gnome_client.rs +++ b/src/client/gnome_client.rs @@ -59,6 +59,28 @@ impl Client for GnomeClient { } None } + + fn current_window(&mut self) -> Option { + self.connect(); + let connection = match &mut self.connection { + Some(connection) => connection, + None => return None, + }; + if let Ok(message) = connection.call_method( + Some("org.gnome.Shell"), + "/com/k0kubun/Xremap", + Some("com.k0kubun.Xremap"), + "ActiveWindow", + &(), + ) { + if let Ok(json) = message.body::() { + if let Ok(window) = serde_json::from_str::(&json) { + return Some(window.title); + } + } + } + None + } } #[derive(Serialize, Deserialize)] diff --git a/src/client/mod.rs b/src/client/mod.rs index 6958f05..f6e1779 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,6 +1,7 @@ trait Client { fn supported(&mut self) -> bool; fn current_application(&mut self) -> Option; + fn current_window(&mut self) -> Option; } pub struct WMClient { @@ -8,6 +9,7 @@ pub struct WMClient { client: Box, supported: Option, last_application: String, + last_window: String, } impl WMClient { @@ -17,6 +19,7 @@ impl WMClient { client, supported: None, last_application: String::new(), + last_window: String::new(), } } @@ -39,6 +42,26 @@ impl WMClient { } result } + + pub fn current_window(&mut self) -> Option { + if self.supported.is_none() { + let supported = self.client.supported(); + self.supported = Some(supported); + println!("application-client: {} (supported: {})", self.name, supported); + } + if !self.supported.unwrap() { + return None; + } + + let result = self.client.current_window(); + if let Some(window) = &result { + if &self.last_window != window { + self.last_window = window.clone(); + println!("window: {}", window); + } + } + result + } } #[cfg(feature = "gnome")] diff --git a/src/client/null_client.rs b/src/client/null_client.rs index 7a0d573..4ccfd97 100644 --- a/src/client/null_client.rs +++ b/src/client/null_client.rs @@ -10,4 +10,8 @@ impl Client for NullClient { fn current_application(&mut self) -> Option { None } + + fn current_window(&mut self) -> Option { + None + } } diff --git a/src/client/sway_client.rs b/src/client/sway_client.rs index e36385c..4f24a14 100644 --- a/src/client/sway_client.rs +++ b/src/client/sway_client.rs @@ -59,6 +59,10 @@ impl Client for SwayClient { } None } + + fn current_window(&mut self) -> Option { + None + } } // e.g. "/run/user/1000/sway-ipc.1000.2575.sock" diff --git a/src/client/x11_client.rs b/src/client/x11_client.rs index 0e99beb..c6d921f 100644 --- a/src/client/x11_client.rs +++ b/src/client/x11_client.rs @@ -107,4 +107,8 @@ impl Client for X11Client { } Some(wm_class) } + + fn current_window(&mut self) -> Option { + None + } } diff --git a/src/config/application.rs b/src/config/application.rs index aae16d9..c68bd21 100644 --- a/src/config/application.rs +++ b/src/config/application.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Deserializer}; // TODO: Use trait to allow only either `only` or `not` #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Application { +pub struct OnlyOrNot { #[serde(default, deserialize_with = "deserialize_string_or_vec")] pub only: Option>, #[serde(default, deserialize_with = "deserialize_string_or_vec")] diff --git a/src/config/keymap.rs b/src/config/keymap.rs index 69209e4..d108888 100644 --- a/src/config/keymap.rs +++ b/src/config/keymap.rs @@ -1,5 +1,5 @@ use crate::config::action::{Action, Actions}; -use crate::config::application::Application; +use crate::config::application::OnlyOrNot; use crate::config::key_press::{KeyPress, Modifier, ModifierState}; use serde::{Deserialize, Deserializer}; use std::collections::HashMap; @@ -11,7 +11,8 @@ pub struct Keymap { pub name: String, #[serde(deserialize_with = "deserialize_remap")] pub remap: HashMap>, - pub application: Option, + pub application: Option, + pub window: Option, } fn deserialize_remap<'de, D>(deserializer: D) -> Result>, D::Error> diff --git a/src/config/modmap.rs b/src/config/modmap.rs index 18c6ec3..2e8837b 100644 --- a/src/config/modmap.rs +++ b/src/config/modmap.rs @@ -1,4 +1,4 @@ -use crate::config::application::Application; +use crate::config::application::OnlyOrNot; use crate::config::key::deserialize_key; use crate::config::key_action::KeyAction; use evdev::Key; @@ -12,7 +12,8 @@ pub struct Modmap { pub name: String, #[serde(deserialize_with = "deserialize_remap")] pub remap: HashMap, - pub application: Option, + pub application: Option, + pub window: Option, } fn deserialize_remap<'de, D>(deserializer: D) -> Result, D::Error> diff --git a/src/config/tests.rs b/src/config/tests.rs index 56f4ff0..71f289c 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -139,6 +139,24 @@ fn test_keymap_mark() { "}) } +#[test] +fn test_window() { + assert_parse(indoc! {" + modmap: + - remap: + Alt_L: Ctrl_L + window: + not: + - Gnome-terminal + keymap: + - remap: + Alt-S: Ctrl-S + application: + only: + - Gnome-terminal + "}) +} + fn assert_parse(yaml: &str) { let result: Result = serde_yaml::from_str(yaml); if let Err(e) = result { diff --git a/src/event_handler.rs b/src/event_handler.rs index 2935a59..6ed1e54 100644 --- a/src/event_handler.rs +++ b/src/event_handler.rs @@ -1,6 +1,6 @@ use crate::client::{build_client, WMClient}; use crate::config::action::Action; -use crate::config::application::Application; +use crate::config::application::OnlyOrNot; use crate::config::key_action::KeyAction; use crate::config::key_press::{KeyPress, Modifier, ModifierState}; use crate::config::keymap::expand_modifiers; @@ -30,6 +30,7 @@ pub struct EventHandler { // Check the currently active application application_client: WMClient, application_cache: Option, + window_cache: Option, // State machine for multi-purpose keys multi_purpose_keys: HashMap, // Current nested remaps @@ -57,6 +58,7 @@ impl EventHandler { pressed_keys: HashMap::new(), application_client: build_client(), application_cache: None, + window_cache: None, multi_purpose_keys: HashMap::new(), override_remap: None, override_timeout_key: None, @@ -69,7 +71,10 @@ impl EventHandler { // Handle EventType::KEY pub fn on_event(&mut self, event: InputEvent, config: &Config) -> Result<(), Box> { - self.application_cache = None; // expire cache + // expire cache + self.application_cache = None; + self.window_cache = None; + let key = Key::new(event.code()); debug!("=> {}: {:?}", event.value(), &key); @@ -239,6 +244,11 @@ impl EventHandler { continue; } } + if let Some(window_matcher) = &keymap.window { + if !self.match_window(window_matcher) { + continue; + } + } return Ok(Some(actions.to_vec())); } } @@ -413,7 +423,7 @@ impl EventHandler { } } - fn match_application(&mut self, application_matcher: &Application) -> bool { + fn match_application(&mut self, application_matcher: &OnlyOrNot) -> bool { // Lazily fill the wm_class cache if self.application_cache.is_none() { match self.application_client.current_application() { @@ -433,6 +443,26 @@ impl EventHandler { false } + fn match_window(&mut self, window_matcher: &OnlyOrNot) -> bool { + // Lazily fill the wm_class cache + if self.window_cache.is_none() { + match self.application_client.current_window() { + Some(window) => self.window_cache = Some(window), + None => self.window_cache = Some(String::new()), + } + } + + if let Some(window) = &self.window_cache { + if let Some(window_only) = &window_matcher.only { + return window_only.contains(window); + } + if let Some(window_not) = &window_matcher.not { + return !window_not.contains(window); + } + } + false + } + fn update_modifier(&mut self, code: u16, value: i32) { if code == Key::KEY_LEFTSHIFT.code() { self.shift.left = is_pressed(value)