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: on-idle-fakekey action #512

Merged
merged 4 commits into from
Aug 5, 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
4 changes: 0 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ rustc-hash = "1.1.0"
miette = { version = "5.7.0", features = ["fancy"] }
dirs = "5.0.1"

kanata-keyberon = "0.20.0"
kanata-parser = "0.20.0"
# kanata-keyberon = "0.20.0"
# kanata-parser = "0.20.0"
# Uncomment below and comment out above for testing local changes.
# Otherwise any changes to the local files will not reflect in the compiled
# binary.
# kanata-keyberon = { path = "keyberon" }
# kanata-parser = { path = "parser" }
kanata-keyberon = { path = "keyberon" }
kanata-parser = { path = "parser" }

[target.'cfg(target_os = "linux")'.dependencies]
evdev = "=0.12.0"
Expand Down
20 changes: 14 additions & 6 deletions cfg_samples/kanata.kbd
Original file line number Diff line number Diff line change
Expand Up @@ -746,18 +746,25 @@ If you need help, you are welcome to ask.
;; Press and release fake keys.
;;
;; Fake keys can't be pressed by any physical keyboard buttons and can only be
;; acted upon by the actions on-press-fakekey and on-release-fakekey. The
;; purpose of fake keys is for a use case such as holding modifier keys for
;; any number of keypresses and then releasing the modifiers when desired.
;; acted upon by the actions:
;; - on-press-fakekey
;; - on-release-fakekey
;; - on-idle-fakekey
;;
;; One use case of fake keys is for holding modifier keys
;; for any number of keypresses and then releasing the modifiers when desired.
;;
;; The actions associated with fake keys in deffakekeys are parsed before
;; aliases, so you can't use aliases within deffakekeys. Other than the lack
;; of alias support, fake keys can do any action that a normal key can,
;; including doing operations on previously defined fake keys.
;;
;; Operations on fake keys can occur either on press (on-press-fakekey) or
;; on release (on-release-fakekey). The use cases for the on-release variant
;; are left up to your own creativity.
;; Operations on fake keys can occur either on press (on-press-fakekey),
;; on release (on-release-fakekey), or on idle for a specified time
;; (on-idle-fakekey).
;;
;; Fake keys are flexible in usage but can be obscure to discover how they
;; can be useful to you.
(deflayer fakekeys
_ @fcp @fsp @fmp @pal _ _ _ _ _ _ _ _ _
_ @fcr @fsr @fap @ral _ _ _ _ _ _ _ _ _
Expand Down Expand Up @@ -802,6 +809,7 @@ If you need help, you are welcome to ask.
)
pal (on-press-fakekey pal tap)
ral (on-press-fakekey ral tap)
rdl (on-idle-fakekey ral tap 1000)

;; Test of on-press-fakekey and on-release-fakekey in a macro
t1 (macro-release-cancel @fsp 5 a b c @fsr 5 c b a)
Expand Down
6 changes: 5 additions & 1 deletion docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1735,6 +1735,9 @@ physical key presses and can only be activated via these actions:
action when pressing the key mapped to this action.
* `+(on-release-fakekey <fake key name> <key action>)+`: Activate a fake key
action when releasing the key mapped to this action.
* `+(on-idle-fakekey <fake key name> <key action> <idle time>)+`:
Activate a fake key action
when the keyboard is idle for `idle time` milliseconds

A fake key can be defined in a `+deffakekeys+` configuration entry. Configuring
this entry is similar to `+defalias+`, but you cannot make use of aliases
Expand Down Expand Up @@ -1780,10 +1783,11 @@ The aforementioned `+<key action>+` can be one of three values:

pal (on-press-fakekey pal tap)
ral (on-press-fakekey ral tap)
rsf (on-idle-fakekey ral tap 1000)
)

(deflayer use-fake-keys
@psf @rsf @pal @ral a s d f
@psf @rsf @pal @ral a s d @rsf
)
----

Expand Down
4 changes: 2 additions & 2 deletions parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ rustc-hash = "1.1.0"
miette = { version = "5.7.0", features = ["fancy"] }
thiserror = "1.0.38"

kanata-keyberon = "0.20.0"
# kanata-keyberon = "0.20.0"
# Uncomment below and comment out above for testing local changes.
# Otherwise any changes to the local files will not reflect in the compiled
# binary.
# kanata-keyberon = { path = "../keyberon" }
kanata-keyberon = { path = "../keyberon" }

[features]
cmd = []
Expand Down
74 changes: 63 additions & 11 deletions parser/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,7 @@ fn parse_action_list(ac: &[SExpr], s: &ParsedState) -> Result<&'static KanataAct
"on-release-fakekey" => parse_on_release_fake_key_op(&ac[1..], s),
"on-press-fakekey-delay" => parse_fake_key_delay(&ac[1..], s),
"on-release-fakekey-delay" => parse_on_release_fake_key_delay(&ac[1..], s),
"on-idle-fakekey" => parse_on_idle_fakekey(&ac[1..], s),
"mwheel-up" => parse_mwheel(&ac[1..], MWheelDirection::Up, s),
"mwheel-down" => parse_mwheel(&ac[1..], MWheelDirection::Down, s),
"mwheel-left" => parse_mwheel(&ac[1..], MWheelDirection::Left, s),
Expand Down Expand Up @@ -2189,29 +2190,31 @@ fn parse_fake_key_op_coord_action(
let y = match s.fake_keys.get(ac_params[0].atom(s.vars()).ok_or_else(|| {
anyhow_expr!(
&ac_params[0],
"{ERR_MSG}\nA list is not allowed for a fake key name",
"{ERR_MSG}\nInvalid first parameter: a fake key name cannot be a list",
)
})?) {
Some((y, _)) => *y as u8, // cast should be safe; checked in `parse_fake_keys`
None => bail_expr!(&ac_params[0], "unknown fake key name {:?}", &ac_params[0]),
Some((y, _)) => *y as u16, // cast should be safe; checked in `parse_fake_keys`
None => bail_expr!(
&ac_params[0],
"{ERR_MSG}\nInvalid first parameter: unknown fake key name {:?}",
&ac_params[0]
),
};
let action = ac_params[1]
.atom(s.vars())
.map(|a| match a {
"tap" => Ok(FakeKeyAction::Tap),
"press" => Ok(FakeKeyAction::Press),
"release" => Ok(FakeKeyAction::Release),
_ => bail_expr!(
&ac_params[1],
"{ERR_MSG}\nInvalid second parameter, it must be one of: tap, press, release",
),
"tap" => Some(FakeKeyAction::Tap),
"press" => Some(FakeKeyAction::Press),
"release" => Some(FakeKeyAction::Release),
_ => None,
})
.flatten()
.ok_or_else(|| {
anyhow_expr!(
&ac_params[1],
"{ERR_MSG}\nInvalid second parameter, it must be one of: tap, press, release",
)
})??;
})?;
let (x, y) = get_fake_key_coords(y);
Ok((Coord { x, y }, action))
}
Expand Down Expand Up @@ -2887,6 +2890,55 @@ fn parse_switch_case_bool(
}
}

fn parse_on_idle_fakekey(ac_params: &[SExpr], s: &ParsedState) -> Result<&'static KanataAction> {
const ERR_MSG: &str =
"on-idle-fakekey expects three parameters:\n<fake key name> <(tap|press|release)> <idle time>\n";
if ac_params.len() != 3 {
bail!("{ERR_MSG}");
}
let y = match s.fake_keys.get(ac_params[0].atom(s.vars()).ok_or_else(|| {
anyhow_expr!(
&ac_params[0],
"{ERR_MSG}\nInvalid first parameter: a fake key name cannot be a list",
)
})?) {
Some((y, _)) => *y as u16, // cast should be safe; checked in `parse_fake_keys`
None => bail_expr!(
&ac_params[0],
"{ERR_MSG}\nInvalid first parameter: unknown fake key name {:?}",
&ac_params[0]
),
};
let action = ac_params[1]
.atom(s.vars())
.map(|a| match a {
"tap" => Some(FakeKeyAction::Tap),
"press" => Some(FakeKeyAction::Press),
"release" => Some(FakeKeyAction::Release),
_ => None,
})
.flatten()
.ok_or_else(|| {
anyhow_expr!(
&ac_params[1],
"{ERR_MSG}\nInvalid second parameter, it must be one of: tap, press, release",
)
})?;
let idle_duration = parse_u16(&ac_params[2], s, "idle time").map_err(|mut e| {
e.help_msg = format!("{ERR_MSG}\nInvalid third parameter: {}", e.help_msg);
e
})?;
let (x, y) = get_fake_key_coords(y);
let coord = Coord { x, y };
Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(
CustomAction::FakeKeyOnIdle(FakeKeyOnIdle {
coord,
action,
idle_duration,
}),
)))))
}

/// Creates a `KeyOutputs` from `layers::LAYERS`.
fn create_key_outputs(layers: &KanataLayers, overrides: &Overrides) -> KeyOutputs {
let mut outs = KeyOutputs::new();
Expand Down
146 changes: 144 additions & 2 deletions parser/src/cfg/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,9 +644,151 @@ fn parse_switch_exceed_depth() {
)
"#;
parse_cfg_raw_string(source, &mut s, "test")
.map_err(|e| {
eprintln!("{:?}", error_with_source(e));
.map_err(|_e| {
// uncomment to see what this looks like when running test
// eprintln!("{:?}", error_with_source(_e));
""
})
.unwrap_err();
}

#[test]
fn parse_on_idle_fakekey() {
let _lk = match CFG_PARSE_LOCK.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let mut s = ParsedState::default();
let source = r#"
(defvar var1 a)
(defsrc a)
(deffakekeys hello a)
(deflayer base
(on-idle-fakekey hello tap 200)
)
"#;
let res = parse_cfg_raw_string(source, &mut s, "test")
.map_err(|_e| {
// uncomment to see what this looks like when running test
eprintln!("{:?}", error_with_source(_e));
""
})
.unwrap();
assert_eq!(
res.3[0][0][OsCode::KEY_A.as_u16() as usize],
Action::Custom(
&[&CustomAction::FakeKeyOnIdle(FakeKeyOnIdle {
coord: Coord { x: 1, y: 0 },
action: FakeKeyAction::Tap,
idle_duration: 200
})]
.as_ref()
),
);
}

#[test]
fn parse_on_idle_fakekey_errors() {
let _lk = match CFG_PARSE_LOCK.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let mut s = ParsedState::default();
let source = r#"
(defvar var1 a)
(defsrc a)
(deffakekeys hello a)
(deflayer base
(on-idle-fakekey hello bap 200)
)
"#;
parse_cfg_raw_string(source, &mut s, "test")
.map_err(|_e| {
// comment out to see what this looks like when running test
// eprintln!("{:?}", error_with_source(_e));
""
})
.unwrap_err();

let source = r#"
(defvar var1 a)
(defsrc a)
(deffakekeys hello a)
(deflayer base
(on-idle-fakekey jello tap 200)
)
"#;
parse_cfg_raw_string(source, &mut s, "test")
.map_err(|_e| {
// uncomment to see what this looks like when running test
// eprintln!("{:?}", error_with_source(_e));
""
})
.unwrap_err();

let source = r#"
(defvar var1 a)
(defsrc a)
(deffakekeys hello a)
(deflayer base
(on-idle-fakekey (hello) tap 200)
)
"#;
parse_cfg_raw_string(source, &mut s, "test")
.map_err(|_e| {
// uncomment to see what this looks like when running test
// eprintln!("{:?}", error_with_source(_e));
""
})
.unwrap_err();

let source = r#"
(defvar var1 a)
(defsrc a)
(deffakekeys hello a)
(deflayer base
(on-idle-fakekey hello tap -1)
)
"#;
parse_cfg_raw_string(source, &mut s, "test")
.map_err(|_e| {
// uncomment to see what this looks like when running test
// eprintln!("{:?}", error_with_source(_e));
""
})
.unwrap_err();
}

#[test]
fn parse_fake_keys_errors_on_too_many() {
let mut s = ParsedState::default();
let mut checked_for_err = false;
for n in 0..1000 {
let exprs = [&vec![
SExpr::Atom(Spanned {
t: "deffakekeys".to_string(),
span: Default::default(),
}),
SExpr::Atom(Spanned {
t: "a".repeat(n),
span: Default::default(),
}),
SExpr::Atom(Spanned {
t: "a".to_string(),
span: Default::default(),
}),
]];
if n < 500 {
// fill up fake keys, expect first bunch to succeed
parse_fake_keys(&exprs, &mut s).unwrap();
} else if n < 999 {
// at some point they start failing, ignore result
let _ = parse_fake_keys(&exprs, &mut s);
} else {
// last iteration, check for error. probably happened before this, but just check here
let _ = parse_fake_keys(&exprs, &mut s).unwrap_err();
checked_for_err = true;
}
}
assert!(checked_for_err);
}
9 changes: 9 additions & 0 deletions parser/src/custom_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub enum CustomAction {
coord: Coord,
action: FakeKeyAction,
},
FakeKeyOnIdle(FakeKeyOnIdle),
Delay(u16),
DelayOnRelease(u16),
MWheel {
Expand Down Expand Up @@ -83,6 +84,14 @@ pub enum FakeKeyAction {
Tap,
}

/// An active waiting-for-idle state.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FakeKeyOnIdle {
pub coord: Coord,
pub action: FakeKeyAction,
pub idle_duration: u16,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MWheelDirection {
Up,
Expand Down
Loading