Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat+fix: unshift, key repeat on unmod+unshift #638

Merged
merged 3 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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