Skip to content

Commit

Permalink
feat+fix: unshift, key repeat on unmod+unshift (#638)
Browse files Browse the repository at this point in the history
Implement unshift and make unmod+unshift key repeat work. Implements
#615.
  • Loading branch information
jtroo authored Nov 24, 2023
1 parent ba235f3 commit a870431
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 35 deletions.
5 changes: 5 additions & 0 deletions cfg_samples/kanata.kbd
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,11 @@ If you need help, please feel welcome to ask in the GitHub discussions.
;; dead keys é (as opposed to using AltGr) that outputs É when shifted
dké (macro (unmod ') e)

;; unshift is like unmod but only releases shifts
;; In ISO German QWERTZ, force unshifted symbols even if shift is held
de{ (unshift ralt 7)
de[ (unshift ralt 8)

;; unicode accepts a single unicode character. The unicode character will
;; not be automatically repeated by holding the key down. The alias name
;; is the unicode character itself and is referenced by @🙁 in deflayer.
Expand Down
21 changes: 16 additions & 5 deletions docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1716,17 +1716,28 @@ and what the extra non-terminal+non-capitalized keys should be (3rd parameter).
=== unmod[[unmod]]
<<table-of-contents,Back to ToC>>

The `unmod` action will release all modifiers temporarily and send a key.
The `unmod` action will release all modifiers temporarily
and send one or more keys.
After the `unmod` key is released, the released modifiers are pressed again.
The modifiers affected are: `lsft,rsft,lctl,rctl,lmet,rmet,lalt,ralt`.

A variant of `unmod` is `unshift`.
This action only releases the `lsft,rsft` keys.
This can be useful for forcing unshifted keys while AltGr is still held.

.Example:
[source]
----
;; holding shift and tapping a @um1 key will still output 1.
um1 (unmod 1)
;; dead keys é (as opposed to using AltGr) that outputs É when shifted
dké (macro (unmod ') e)
(defalias
;; holding shift and tapping a @um1 key will still output 1.
um1 (unmod 1)
;; dead keys é (as opposed to using AltGr) that outputs É when shifted
dké (macro (unmod ') e)
;; In ISO German QWERTZ, force unshifted symbols even if shift is held
{ (unshift ralt 7)
[ (unshift ralt 8)
)
----

[[cmd]]
Expand Down
4 changes: 3 additions & 1 deletion parser/src/cfg/list_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ pub const DYNAMIC_MACRO_RECORD_STOP_TRUNCATE: &str = "dynamic-macro-record-stop-
pub const SWITCH: &str = "switch";
pub const SEQUENCE: &str = "sequence";
pub const UNMOD: &str = "unmod";
pub const UNSHIFT: &str = "unshift";

pub fn is_list_action(ac: &str) -> bool {
const LIST_ACTIONS: [&str; 56] = [
const LIST_ACTIONS: [&str; 57] = [
LAYER_SWITCH,
LAYER_TOGGLE,
LAYER_WHILE_HELD,
Expand Down Expand Up @@ -117,6 +118,7 @@ pub fn is_list_action(ac: &str) -> bool {
SWITCH,
SEQUENCE,
UNMOD,
UNSHIFT,
];
LIST_ACTIONS.contains(&ac)
}
58 changes: 43 additions & 15 deletions parser/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,8 @@ fn parse_action_list(ac: &[SExpr], s: &ParsedState) -> Result<&'static KanataAct
DYNAMIC_MACRO_RECORD_STOP_TRUNCATE => parse_macro_record_stop_truncate(&ac[1..], s),
SWITCH => parse_switch(&ac[1..], s),
SEQUENCE => parse_sequence_start(&ac[1..], s),
UNMOD => parse_unmod(&ac[1..], s),
UNMOD => parse_unmod(UNMOD, &ac[1..], s),
UNSHIFT => parse_unmod(UNSHIFT, &ac[1..], s),
_ => unreachable!(),
}
}
Expand Down Expand Up @@ -3061,19 +3062,35 @@ fn parse_on_idle_fakekey(ac_params: &[SExpr], s: &ParsedState) -> Result<&'stati
)))))
}

fn parse_unmod(ac_params: &[SExpr], s: &ParsedState) -> Result<&'static KanataAction> {
const ERR_MSG: &str = "unmod expects one param: key";
if ac_params.len() != 1 {
bail!("{ERR_MSG}\nfound {} items", ac_params.len());
fn parse_unmod(
unmod_type: &str,
ac_params: &[SExpr],
s: &ParsedState,
) -> Result<&'static KanataAction> {
const ERR_MSG: &str = "expects expects at least one key name";
if ac_params.len() < 1 {
bail!("{unmod_type} {ERR_MSG}\nfound {} items", ac_params.len());
}
let mut keys: Vec<KeyCode> = ac_params.iter().try_fold(Vec::new(), |mut keys, param| {
keys.push(
param
.atom(s.vars())
.and_then(str_to_oscode)
.ok_or_else(|| anyhow_expr!(&ac_params[0], "{unmod_type} {ERR_MSG}"))?
.into(),
);
Ok::<_, ParseError>(keys)
})?;
keys.shrink_to_fit();
match unmod_type {
UNMOD => Ok(s.a.sref(Action::Custom(
s.a.sref(s.a.sref_slice(CustomAction::Unmodded { keys })),
))),
UNSHIFT => Ok(s.a.sref(Action::Custom(
s.a.sref(s.a.sref_slice(CustomAction::Unshifted { keys })),
))),
_ => panic!("Unknown unmod type {unmod_type}"),
}
let key = ac_params[0]
.atom(s.vars())
.and_then(str_to_oscode)
.ok_or_else(|| anyhow_expr!(&ac_params[0], "{ERR_MSG}"))?
.into();
Ok(s.a.sref(Action::Custom(
s.a.sref(s.a.sref_slice(CustomAction::Unmodded { key })),
)))
}

/// Creates a `KeyOutputs` from `layers::LAYERS`.
Expand Down Expand Up @@ -3152,6 +3169,18 @@ fn add_key_output_from_action_to_key_pos(
add_key_output_from_action_to_key_pos(osc_slot, case.1, outputs, overrides);
}
}
Action::Custom(cacs) => {
for ac in cacs.iter() {
match ac {
CustomAction::Unmodded { keys } | CustomAction::Unshifted { keys } => {
for k in keys.iter() {
add_kc_output(osc_slot, k.into(), outputs, overrides);
}
}
_ => {}
}
}
}
Action::NoOp
| Action::Trans
| Action::Repeat
Expand All @@ -3160,8 +3189,7 @@ fn add_key_output_from_action_to_key_pos(
| Action::Sequence { .. }
| Action::RepeatableSequence { .. }
| Action::CancelSequences
| Action::ReleaseState(_)
| Action::Custom(_) => {}
| Action::ReleaseState(_) => {}
};
}

Expand Down
29 changes: 29 additions & 0 deletions parser/src/cfg/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,35 @@ fn parse_all_defcfg() {
.expect("succeeds");
}

#[test]
fn parse_unmod() {
let _lk = match CFG_PARSE_LOCK.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};

let source = r#"
(defsrc a b c d)
(deflayer base
(unmod a)
(unmod a b)
(unshift a)
(unshift a b)
)
"#;
let mut s = ParsedState::default();
parse_cfg_raw_string(
source,
&mut s,
&PathBuf::from("test"),
&mut FileContentProvider {
get_file_content_fn: &mut |_| unimplemented!(),
},
DEF_LOCAL_KEYS,
)
.expect("succeeds");
}

#[test]
fn using_parentheses_in_deflayer_directly_fails_with_custom_message() {
let _lk = match CFG_PARSE_LOCK.lock() {
Expand Down
5 changes: 4 additions & 1 deletion parser/src/custom_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ pub enum CustomAction {
y: u16,
},
Unmodded {
key: KeyCode,
keys: Vec<KeyCode>,
},
Unshifted {
keys: Vec<KeyCode>,
},
}

Expand Down
54 changes: 41 additions & 13 deletions src/kanata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,10 @@ pub struct Kanata {
/// Configured maximum for dynamic macro recording, to protect users from themselves if they
/// have accidentally left it on.
dynamic_macro_max_presses: u16,
/// Keys that should be unmodded. If empty, any modifier should be cleared.
/// Keys that should be unmodded. If non-empty, any modifier should be cleared.
unmodded_keys: Vec<KeyCode>,
/// Keys that should be unshifted. If non-empty, left+right shift keys should be cleared.
unshifted_keys: Vec<KeyCode>,
/// Keep track of last pressed key for [`CustomAction::Repeat`].
last_pressed_key: KeyCode,
}
Expand Down Expand Up @@ -341,6 +343,7 @@ impl Kanata {
ticks_since_idle: 0,
movemouse_buffer: None,
unmodded_keys: vec![],
unshifted_keys: vec![],
last_pressed_key: KeyCode::No,
})
}
Expand Down Expand Up @@ -703,15 +706,27 @@ impl Kanata {
match custom_event {
CustomEvent::Press(custacts) => {
for custact in custacts.iter() {
if let CustomAction::Unmodded { key } = custact {
self.unmodded_keys.push(*key);
match custact {
CustomAction::Unmodded { keys } => {
self.unmodded_keys.extend(keys);
}
CustomAction::Unshifted { keys } => {
self.unshifted_keys.extend(keys);
}
_ => {}
}
}
}
CustomEvent::Release(custacts) => {
for custact in custacts.iter() {
if let CustomAction::Unmodded { key } = custact {
self.unmodded_keys.retain(|k| k != key);
match custact {
CustomAction::Unmodded { keys } => {
self.unmodded_keys.retain(|k| !keys.contains(k));
}
CustomAction::Unshifted { keys } => {
self.unshifted_keys.retain(|k| !keys.contains(k));
}
_ => {}
}
}
}
Expand All @@ -733,6 +748,10 @@ impl Kanata {
});
cur_keys.extend(self.unmodded_keys.iter());
}
if !self.unshifted_keys.is_empty() {
cur_keys.retain(|k| !matches!(k, KeyCode::LShift | KeyCode::RShift));
cur_keys.extend(self.unshifted_keys.iter());
}

// Release keys that do not exist in the current state but exist in the previous state.
// This used to use a HashSet but it was changed to a Vec because the order of operations
Expand Down Expand Up @@ -1290,6 +1309,7 @@ impl Kanata {
CustomAction::FakeKeyOnRelease { .. }
| CustomAction::DelayOnRelease(_)
| CustomAction::Unmodded { .. }
| CustomAction::Unshifted { .. }
| CustomAction::CancelMacroOnRelease => {}
}
}
Expand Down Expand Up @@ -1435,10 +1455,14 @@ impl Kanata {
// Prioritize checking the active layer in case a layer-while-held is active.
if let Some(outputs_for_key) = self.key_outputs[current_layer].get(&event.code) {
log::debug!("key outs for active layer-while-held: {outputs_for_key:?};");
for kc in outputs_for_key.iter().rev() {
if self.cur_keys.contains(&kc.into()) {
log::debug!("repeat {:?}", KeyCode::from(*kc));
if let Err(e) = self.kbd_out.write_key(*kc, KeyValue::Repeat) {
for osc in outputs_for_key.iter().rev().copied() {
let kc = osc.into();
if self.cur_keys.contains(&kc)
|| self.unshifted_keys.contains(&kc)
|| self.unmodded_keys.contains(&kc)
{
log::debug!("repeat {:?}", KeyCode::from(osc));
if let Err(e) = self.kbd_out.write_key(osc, KeyValue::Repeat) {
bail!("could not write key {:?}", e)
}
return Ok(());
Expand All @@ -1460,10 +1484,14 @@ impl Kanata {
Some(v) => v,
};
log::debug!("key outs for default layer: {outputs_for_key:?};");
for kc in outputs_for_key.iter().rev() {
if self.cur_keys.contains(&kc.into()) {
log::debug!("repeat {:?}", KeyCode::from(*kc));
if let Err(e) = self.kbd_out.write_key(*kc, KeyValue::Repeat) {
for osc in outputs_for_key.iter().rev().copied() {
let kc = osc.into();
if self.cur_keys.contains(&kc)
|| self.unshifted_keys.contains(&kc)
|| self.unmodded_keys.contains(&kc)
{
log::debug!("repeat {:?}", KeyCode::from(osc));
if let Err(e) = self.kbd_out.write_key(osc, KeyValue::Repeat) {
bail!("could not write key {:?}", e)
}
return Ok(());
Expand Down

0 comments on commit a870431

Please sign in to comment.