Implement mark

This commit is contained in:
Takashi Kokubun 2021-12-31 21:43:16 -08:00
parent 646a1dd53d
commit c49a9b5cde
No known key found for this signature in database
GPG Key ID: 6FFC433B12EE23DD
6 changed files with 168 additions and 44 deletions

View File

@ -19,6 +19,7 @@
* Remap a key to two different keys depending on whether it's pressed alone or held. * Remap a key to two different keys depending on whether it's pressed alone or held.
* Application-specific remapping. Even if it's not supported by your application, xremap can. * Application-specific remapping. Even if it's not supported by your application, xremap can.
* Automatically remap newly connected devices by starting xremap with `--watch`. * Automatically remap newly connected devices by starting xremap with `--watch`.
* Support [Emacs-like key remapping](example/emacs.yml), including the mark mode.
## Prerequisite ## Prerequisite
@ -146,6 +147,10 @@ keymap:
# execute a command # execute a command
MOD1-KEY_XXX: MOD1-KEY_XXX:
launch: ["bash", "-c", "echo hello > /tmp/test"] launch: ["bash", "-c", "echo hello > /tmp/test"]
# let `with_mark` also press a Shift key (useful for Emacs emulation)
MOD1-KEY_XXX: { set_mark: true } # use { set_mark: false } to disable it
# also press Shift only when { set_mark: true } is used before
MOD1-KEY_XXX: { with_mark: MOD2-KEY_YYY }
application: # Optional application: # Optional
not: [Application, ...] not: [Application, ...]
# or # or

60
example/emacs.yml Normal file
View File

@ -0,0 +1,60 @@
# Credit: https://github.com/mooz/xkeysnail/blob/bf3c93b4fe6efd42893db4e6588e5ef1c4909cfb/example/config.py#L62-L125
keymap:
- name: Emacs
remap:
# Cursor
C-b: { with_mark: left }
C-f: { with_mark: right }
C-p: { with_mark: up }
C-n: { with_mark: down }
# Forward/Backward word
M-b: { with_mark: C-left }
M-f: { with_mark: C-right }
# Beginning/End of line
C-a: { with_mark: home }
C-e: { with_mark: end }
# Page up/down
M-v: { with_mark: pageup }
C-v: { with_mark: pagedown }
# Beginning/End of file
M-Shift-comma: { with_mark: C-home }
M-Shift-dot: { with_mark: C-end }
# Newline
C-m: enter
C-j: enter
C-o: [enter, left]
# Copy
C-w: [C-x, { set_mark: false }]
M-w: [C-c, { set_mark: false }]
C-y: [C-v, { set_mark: false }]
# Delete
C-d: [delete, { set_mark: false }]
M-d: [C-delete, { set_mark: false }]
# Kill line
C-k: [Shift-end, C-x, { set_mark: false }]
# Undo
C-slash: [C-z, { set_mark: false }]
C-Shift-ro: C-z
# Mark
C-space: { set_mark: true }
# Search
C-s: F3
C-r: Shift-F3
M-Shift-5: C-h
# Cancel
C-g: [esc, { set_mark: false }]
# C-x YYY
C-x:
remap:
# C-x h (select all)
h: [C-home, C-a, { set_mark: true }]
# C-x C-f (open)
C-f: C-o
# C-x C-s (save)
C-s: C-s
# C-x k (kill tab)
k: C-f4
# C-x C-c (exit)
C-c: C-q
# C-x u (undo)
u: [C-z, { set_mark: false }]

View File

@ -14,6 +14,10 @@ pub enum Action {
Remap(HashMap<KeyPress, Vec<Action>>), Remap(HashMap<KeyPress, Vec<Action>>),
#[serde(deserialize_with = "deserialize_launch")] #[serde(deserialize_with = "deserialize_launch")]
Launch(Vec<String>), Launch(Vec<String>),
#[serde(deserialize_with = "deserialize_with_mark")]
WithMark(KeyPress),
#[serde(deserialize_with = "deserialize_set_mark")]
SetMark(bool),
} }
fn deserialize_remap<'de, D>(deserializer: D) -> Result<HashMap<KeyPress, Vec<Action>>, D::Error> fn deserialize_remap<'de, D>(deserializer: D) -> Result<HashMap<KeyPress, Vec<Action>>, D::Error>
@ -42,6 +46,32 @@ where
Err(de::Error::custom("not a map with a single \"launch\" key")) Err(de::Error::custom("not a map with a single \"launch\" key"))
} }
fn deserialize_with_mark<'de, D>(deserializer: D) -> Result<KeyPress, D::Error>
where
D: Deserializer<'de>,
{
let mut action = HashMap::<String, KeyPress>::deserialize(deserializer)?;
if let Some(key_press) = action.remove("with_mark") {
if action.is_empty() {
return Ok(key_press);
}
}
Err(de::Error::custom("not a map with a single \"with_mark\" key"))
}
fn deserialize_set_mark<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
let mut action = HashMap::<String, bool>::deserialize(deserializer)?;
if let Some(set) = action.remove("set_mark") {
if action.is_empty() {
return Ok(set);
}
}
Err(de::Error::custom("not a map with a single \"set_mark\" key"))
}
// Used only for deserializing Vec<Action> // Used only for deserializing Vec<Action>
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(untagged)] #[serde(untagged)]

View File

@ -114,6 +114,18 @@ fn test_keymap_launch() {
"#}) "#})
} }
#[test]
fn test_keymap_mark() {
assert_parse(indoc! {"
keymap:
- remap:
C-space: { set_mark: true }
C-g: [esc, { set_mark: false }]
C-b: { with_mark: left }
M-b: { with_mark: C-left }
"})
}
fn assert_parse(yaml: &str) { fn assert_parse(yaml: &str) {
let result: Result<Config, Error> = serde_yaml::from_str(&yaml); let result: Result<Config, Error> = serde_yaml::from_str(&yaml);
if let Err(e) = result { if let Err(e) = result {

View File

@ -26,7 +26,7 @@ static MOUSE_BTNS: [&str; 13] = [
"BTN_RIGHT", "BTN_RIGHT",
]; ];
// Credit: https://github.com/mooz/xkeysnail/blob/master/xkeysnail/output.py#L10-L32 // Credit: https://github.com/mooz/xkeysnail/blob/bf3c93b4fe6efd42893db4e6588e5ef1c4909cfb/xkeysnail/output.py#L10-L32
pub fn output_device() -> Result<VirtualDevice, Box<dyn Error>> { pub fn output_device() -> Result<VirtualDevice, Box<dyn Error>> {
let mut keys: AttributeSet<Key> = AttributeSet::new(); let mut keys: AttributeSet<Key> = AttributeSet::new();
for code in Key::KEY_RESERVED.code()..Key::BTN_TRIGGER_HAPPY40.code() { for code in Key::KEY_RESERVED.code()..Key::BTN_TRIGGER_HAPPY40.code() {
@ -183,7 +183,7 @@ fn match_device(path: &str, device: &Device, device_opts: &Vec<String>) -> bool
} }
fn is_keyboard(device: &Device) -> bool { fn is_keyboard(device: &Device) -> bool {
// Credit: https://github.com/mooz/xkeysnail/blob/master/xkeysnail/input.py#L17-L32 // Credit: https://github.com/mooz/xkeysnail/blob/bf3c93b4fe6efd42893db4e6588e5ef1c4909cfb/xkeysnail/input.py#L17-L32
match device.supported_keys() { match device.supported_keys() {
Some(keys) => { Some(keys) => {
keys.contains(Key::KEY_SPACE) keys.contains(Key::KEY_SPACE)

View File

@ -21,11 +21,12 @@ pub struct EventHandler {
control: PressState, control: PressState,
alt: PressState, alt: PressState,
windows: PressState, windows: PressState,
wm_client: WMClient, application_client: WMClient,
application_cache: Option<String>, application_cache: Option<String>,
multi_purpose_keys: HashMap<Key, MultiPurposeKeyState>, multi_purpose_keys: HashMap<Key, MultiPurposeKeyState>,
override_remap: Option<HashMap<KeyPress, Vec<Action>>>, override_remap: Option<HashMap<KeyPress, Vec<Action>>>,
sigaction_set: bool, sigaction_set: bool,
mark_set: bool,
} }
impl EventHandler { impl EventHandler {
@ -36,11 +37,12 @@ impl EventHandler {
control: PressState::new(false), control: PressState::new(false),
alt: PressState::new(false), alt: PressState::new(false),
windows: PressState::new(false), windows: PressState::new(false),
wm_client: build_client(), application_client: build_client(),
application_cache: None, application_cache: None,
multi_purpose_keys: HashMap::new(), multi_purpose_keys: HashMap::new(),
override_remap: None, override_remap: None,
sigaction_set: false, sigaction_set: false,
mark_set: false,
} }
} }
@ -187,25 +189,7 @@ impl EventHandler {
fn dispatch_action(&mut self, action: &Action) -> Result<(), Box<dyn Error>> { fn dispatch_action(&mut self, action: &Action) -> Result<(), Box<dyn Error>> {
match action { match action {
Action::KeyPress(key_press) => { Action::KeyPress(key_press) => self.send_key_press(key_press)?,
let next_shift = self.build_state(Modifier::Shift, key_press.shift);
let next_control = self.build_state(Modifier::Control, key_press.control);
let next_alt = self.build_state(Modifier::Alt, key_press.alt);
let next_windows = self.build_state(Modifier::Windows, key_press.windows);
let prev_shift = self.send_modifier(Modifier::Shift, &next_shift)?;
let prev_control = self.send_modifier(Modifier::Control, &next_control)?;
let prev_alt = self.send_modifier(Modifier::Alt, &next_alt)?;
let prev_windows = self.send_modifier(Modifier::Windows, &next_windows)?;
self.send_key(&key_press.key, PRESS)?;
self.send_key(&key_press.key, RELEASE)?;
self.send_modifier(Modifier::Windows, &prev_windows)?;
self.send_modifier(Modifier::Alt, &prev_alt)?;
self.send_modifier(Modifier::Control, &prev_control)?;
self.send_modifier(Modifier::Shift, &prev_shift)?;
}
Action::Remap(remap) => { Action::Remap(remap) => {
let mut override_remap: HashMap<KeyPress, Vec<Action>> = HashMap::new(); let mut override_remap: HashMap<KeyPress, Vec<Action>> = HashMap::new();
for (key_press, actions) in remap.iter() { for (key_press, actions) in remap.iter() {
@ -214,31 +198,31 @@ impl EventHandler {
self.override_remap = Some(override_remap) self.override_remap = Some(override_remap)
} }
Action::Launch(command) => self.run_command(command.clone()), Action::Launch(command) => self.run_command(command.clone()),
Action::WithMark(key_press) => self.send_key_press(&self.with_mark(key_press))?,
Action::SetMark(set) => self.mark_set = *set,
} }
Ok(()) Ok(())
} }
fn run_command(&mut self, command: Vec<String>) { fn send_key_press(&mut self, key_press: &KeyPress) -> Result<(), Box<dyn Error>> {
if !self.sigaction_set { let next_shift = self.build_state(Modifier::Shift, key_press.shift);
// Avoid defunct processes let next_control = self.build_state(Modifier::Control, key_press.control);
let sig_action = SigAction::new(SigHandler::SigDfl, SaFlags::SA_NOCLDWAIT, SigSet::empty()); let next_alt = self.build_state(Modifier::Alt, key_press.alt);
unsafe { let next_windows = self.build_state(Modifier::Windows, key_press.windows);
sigaction(signal::SIGCHLD, &sig_action).expect("Failed to register SIGCHLD handler");
}
self.sigaction_set = true;
}
debug!("Running command: {:?}", command); let prev_shift = self.send_modifier(Modifier::Shift, &next_shift)?;
match Command::new(&command[0]) let prev_control = self.send_modifier(Modifier::Control, &next_control)?;
.args(&command[1..]) let prev_alt = self.send_modifier(Modifier::Alt, &next_alt)?;
.stdin(Stdio::null()) let prev_windows = self.send_modifier(Modifier::Windows, &next_windows)?;
.stdout(Stdio::null())
.stderr(Stdio::null()) self.send_key(&key_press.key, PRESS)?;
.spawn() self.send_key(&key_press.key, RELEASE)?;
{
Ok(child) => debug!("Process spawned: {:?}, pid {}", command, child.id()), self.send_modifier(Modifier::Windows, &prev_windows)?;
Err(e) => error!("Error running command: {:?}", e), self.send_modifier(Modifier::Alt, &prev_alt)?;
} self.send_modifier(Modifier::Control, &prev_control)?;
self.send_modifier(Modifier::Shift, &prev_shift)?;
Ok(())
} }
fn send_modifier(&mut self, modifier: Modifier, desired: &PressState) -> Result<PressState, Box<dyn Error>> { fn send_modifier(&mut self, modifier: Modifier, desired: &PressState) -> Result<PressState, Box<dyn Error>> {
@ -310,10 +294,43 @@ impl EventHandler {
} }
} }
fn with_mark(&self, key_press: &KeyPress) -> KeyPress {
KeyPress {
key: key_press.key.clone(),
shift: key_press.shift || self.mark_set,
control: key_press.shift,
alt: key_press.alt,
windows: key_press.windows,
}
}
fn run_command(&mut self, command: Vec<String>) {
if !self.sigaction_set {
// Avoid defunct processes
let sig_action = SigAction::new(SigHandler::SigDfl, SaFlags::SA_NOCLDWAIT, SigSet::empty());
unsafe {
sigaction(signal::SIGCHLD, &sig_action).expect("Failed to register SIGCHLD handler");
}
self.sigaction_set = true;
}
debug!("Running command: {:?}", command);
match Command::new(&command[0])
.args(&command[1..])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
{
Ok(child) => debug!("Process spawned: {:?}, pid {}", command, child.id()),
Err(e) => error!("Error running command: {:?}", e),
}
}
fn match_application(&mut self, application_matcher: &Application) -> bool { fn match_application(&mut self, application_matcher: &Application) -> bool {
// Lazily fill the wm_class cache // Lazily fill the wm_class cache
if let None = self.application_cache { if let None = self.application_cache {
match self.wm_client.current_application() { match self.application_client.current_application() {
Some(application) => self.application_cache = Some(application), Some(application) => self.application_cache = Some(application),
None => self.application_cache = Some(String::new()), None => self.application_cache = Some(String::new()),
} }