From d5dfbf456fa96fdfee0321051270a0f9631982b9 Mon Sep 17 00:00:00 2001 From: James Fitzgerald Date: Sun, 26 Jun 2022 19:46:16 -0400 Subject: [PATCH] Add RapidFire module --- docs/rapidfire.md | 43 +++++++++++++++++++++ kmk/modules/rapidfire.py | 82 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 docs/rapidfire.md create mode 100644 kmk/modules/rapidfire.py diff --git a/docs/rapidfire.md b/docs/rapidfire.md new file mode 100644 index 0000000..13a5e77 --- /dev/null +++ b/docs/rapidfire.md @@ -0,0 +1,43 @@ +# RapidFire + +The RapidFire module lets a user send repeated key presses while a key is held. + +Some instances where this may be useful are: + +- MMOs and other games where you are encouraged to repeatedly spam a key +- More responsive volume up and volume down +- Faster cursor key navigation +- Anywhere else you may need an ergonomic alternative to repetitive key tapping + +## Keycodes + +| Key | Description | +| :------ | :--------------------------------------------------- | +| `KC.RF` | Repeatedly sends the specified keycode while pressed | + +## Usage + +Each repeat counts as one full cycle of pressing and releasing. RapidFire works with chording (i.e., holding Shift plus a RapidFire key will repeatedly send the shifted version of that RapidFire key) and chaining (i.e., `KC.RF(KC.LSHIFT(KC.A))`. Multiple RapidFire keys can be held down at the same time, and their timers work independently of each other. + +The RapidFire keycode has a few different options: + +| Option | Default Value | Description | +| :-------------------: | :-----------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `repeat` | `100` | The delay between repeats. Note: `2` appears to be the minimum effective value. If you run into issues, try increasing this value. | +| `wait` | `200` | The delay before starting to repeat. Useful if you want to be able to type with keys that have a low `repeat` value. | +| `randomize_repeat` | `False` | Randomize the value of `repeat`. Useful for making the repetitive input look human in instances where you may be flagged as a bot otherwise. | +| `randomize_magnitude` | `15` | The magnitude of the randomization. If randomization is enabled, the repeat delay will be `repeat` plus or minus a random value up to this amount. | + +### Example Code + +```python +from kmk.modules.rapidfire import RapidFire + +keyboard.modules.append(RapidFire()) + +# After 200 milliseconds, repeatedly send Shift+A every 75-125 milliseconds while the RapidFire key is held +keyboard.keymap = [[ + KC.RF(KC.LSFT(KC.A), wait=200, repeat=100, randomize_repeat=True, randomize_magnitude=25) + ]] + +``` diff --git a/kmk/modules/rapidfire.py b/kmk/modules/rapidfire.py new file mode 100644 index 0000000..cf2979c --- /dev/null +++ b/kmk/modules/rapidfire.py @@ -0,0 +1,82 @@ +from kmk.keys import make_argumented_key +from kmk.modules import Module +from random import randint + + +class RapidFireMeta: + def __init__( + self, + kc, + repeat=100, + wait=200, + randomize_repeat=False, + randomize_magnitude=15, + ): + self.kc = kc + self.repeat = repeat + self.wait = wait + self.randomize_repeat = randomize_repeat + self.randomize_magnitude = randomize_magnitude + + +class RapidFire(Module): + _active_keys = {} + + def __init__(self): + make_argumented_key( + validator=RapidFireMeta, + names=("RF",), + on_press=self._rf_pressed, + on_release=self._rf_released, + ) + + def _get_repeat(self, key): + if key.meta.randomize_repeat: + return key.meta.repeat + randint( + -key.meta.randomize_magnitude, key.meta.randomize_magnitude + ) + return key.meta.repeat + + def _on_repeat_timeout(self, key, keyboard): + keyboard.tap_key(key.meta.kc) + repeat_timeout_key = keyboard.set_timeout( + self._get_repeat(key), + lambda: self._on_repeat_timeout(key, keyboard), + ) + self._active_keys[key] = repeat_timeout_key + + def _on_wait_timeout(self, key, keyboard): + self._on_repeat_timeout(key, keyboard) + + def _rf_pressed(self, key, keyboard, *args, **kwargs): + keyboard.tap_key(key.meta.kc) + wait_timeout_key = keyboard.set_timeout( + key.meta.wait, lambda: self._on_wait_timeout(key, keyboard) + ) + self._active_keys[key] = wait_timeout_key + + def _rf_released(self, key, keyboard, *args, **kwargs): + if key in self._active_keys: + keyboard.cancel_timeout(self._active_keys[key]) + self._active_keys.pop(key) + + def during_bootup(self, keyboard): + return + + def before_matrix_scan(self, keyboard): + return + + def before_hid_send(self, keyboard): + return + + def after_hid_send(self, keyboard): + return + + def on_powersave_enable(self, keyboard): + return + + def on_powersave_disable(self, keyboard): + return + + def after_matrix_scan(self, keyboard): + return