Window-based modmap/keymap switcher for GNOME

gnome-window
Takashi Kokubun 2 years ago
parent 3cc9af8a08
commit 6826338335
No known key found for this signature in database
GPG Key ID: 6FFC433B12EE23DD

@ -59,6 +59,28 @@ impl Client for GnomeClient {
}
None
}
fn current_window(&mut self) -> Option<String> {
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::<String>() {
if let Ok(window) = serde_json::from_str::<ActiveWindow>(&json) {
return Some(window.title);
}
}
}
None
}
}
#[derive(Serialize, Deserialize)]

@ -1,6 +1,7 @@
trait Client {
fn supported(&mut self) -> bool;
fn current_application(&mut self) -> Option<String>;
fn current_window(&mut self) -> Option<String>;
}
pub struct WMClient {
@ -8,6 +9,7 @@ pub struct WMClient {
client: Box<dyn Client>,
supported: Option<bool>,
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<String> {
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")]

@ -10,4 +10,8 @@ impl Client for NullClient {
fn current_application(&mut self) -> Option<String> {
None
}
fn current_window(&mut self) -> Option<String> {
None
}
}

@ -59,6 +59,10 @@ impl Client for SwayClient {
}
None
}
fn current_window(&mut self) -> Option<String> {
None
}
}
// e.g. "/run/user/1000/sway-ipc.1000.2575.sock"

@ -107,4 +107,8 @@ impl Client for X11Client {
}
Some(wm_class)
}
fn current_window(&mut self) -> Option<String> {
None
}
}

@ -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<Vec<String>>,
#[serde(default, deserialize_with = "deserialize_string_or_vec")]

@ -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<KeyPress, Vec<Action>>,
pub application: Option<Application>,
pub application: Option<OnlyOrNot>,
pub window: Option<OnlyOrNot>,
}
fn deserialize_remap<'de, D>(deserializer: D) -> Result<HashMap<KeyPress, Vec<Action>>, D::Error>

@ -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<Key, KeyAction>,
pub application: Option<Application>,
pub application: Option<OnlyOrNot>,
pub window: Option<OnlyOrNot>,
}
fn deserialize_remap<'de, D>(deserializer: D) -> Result<HashMap<Key, KeyAction>, D::Error>

@ -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<Config, Error> = serde_yaml::from_str(yaml);
if let Err(e) = result {

@ -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<String>,
window_cache: Option<String>,
// State machine for multi-purpose keys
multi_purpose_keys: HashMap<Key, MultiPurposeKeyState>,
// 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<dyn Error>> {
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)

Loading…
Cancel
Save