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

Add throttle delays to send_combo normal keystroke #134

Merged
merged 16 commits into from
Feb 25, 2023
Merged
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Keyszer works at quite a low-level. It grabs input directly from the kernel's [
- configurable `DIAGNOSTIC` hotkey
- fully supports running as semi-privileged user (using `root` is now deprecated)
- adds `include` to allow config to pull in other Python files
- adds `throttle_delays` to allow control of output speed of macros/combos
- adds `immediately` to nested keymaps
- adds `Meta`, `Command` and `Cmd` aliases for Super/Meta modifier
- add `C` combo helper (eventually to replace `K`)
Expand Down Expand Up @@ -226,7 +227,7 @@ A successful startup should resemble:

**Limiting Devices**

Limit remapping to specify devices with `--devices`:
Limit remapping to specific devices with `--devices`:

keyszer --devices /dev/input/event3 'Topre Corporation HHKB Professional'

Expand All @@ -249,6 +250,7 @@ For an example configuration please see [`example/config.py`](https://github.com
The configuration API:

- `timeouts(multipurpose, suspend)`
- `throttle_delays(key_pre_delay_ms, key_post_delay_ms)`
- `wm_class_match(re_str)`
- `not_wm_class_match(re_str)`
- `add_modifier(name, aliases, key/keys)`
Expand Down Expand Up @@ -287,6 +289,45 @@ timeouts(
)
```

### `throttle_delays(...)`

Configures the speed of virtual keyboard keystroke output to deal with issues that occur in various situations with the timing of modifier key presses and releases being misinterpreted.

- `key_pre_delay_ms` - The number of milliseconds to delay the press-release keystroke of the "normal" key after pressing modifier keys.
- `key_post_delay_ms` - The number of milliseconds to delay the next key event (modifier release) after the "normal" key press-release event.

Defaults:

```py
throttle_delays(
key_pre_delay_ms = 0, # default: 0 ms, range: 0 to 150 ms, suggested: 1-50 ms
key_post_delay_ms = 0, # default: 0 ms, range: 0 to 150 ms, suggested: 1-100 ms
)
```

Use the throttle delays if you are having the following kinds of problems:

- Shortcut combos seeming to behave unreliably, sometimes as if the unmapped shortcut (or part of the unmapped shortcut) is being pressed at the same time.
- Macros of sets of keystrokes, or strings or Unicode sequences processed by the keymapper into keystrokes, having various kinds of failures, such as:
- Missing characters
- Premature termination of macro
- Shifted or uppercase characters coming out as unshifted/lowercase
- Unshifted or lowercase characters coming out as shifted/uppercase
- Unicode sequences failing to complete and create the desired Unicode character

Suggested values to try if you are in a virtual machine and having major problems with even common shortcut combos:

- key_pre_delay_ms: 40
- key_post_delay_ms: 70

The post delay seems a little more effective in testing, but your situation may be different. For a bare-metal install where you are just having a few glitches in macro output, try much smaller delays:

- key_pre_delay_ms: 0.1
- key_post_delay_ms: 0.5

These are just examples that have worked fairly well in current testing on machines that have had these issues.


### `dump_diagnostics_key(key)`

Configures a key that when hit will dump additional diagnostic information to STDOUT.
Expand Down
27 changes: 25 additions & 2 deletions src/keyszer/config_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import inspect
from inspect import signature

from .lib.logger import error
from .lib.logger import error, debug
from .models.action import Action
from .models.combo import Combo, ComboHint
from .models.trigger import Trigger
Expand Down Expand Up @@ -41,6 +41,29 @@
# multipurpose timeout
_TIMEOUTS = TIMEOUT_DEFAULTS

# global dict of delay values used to mitigate Unicode entry sequence and macro or combo failures
THROTTLE_DELAY_DEFAULTS = {
'key_pre_delay_ms': 0,
'key_post_delay_ms': 0,
}
_THROTTLES = THROTTLE_DELAY_DEFAULTS


def clamp(num, min_value, max_value):
return max(min(num, max_value), min_value)


def throttle_delays(key_pre_delay_ms=0, key_post_delay_ms=0):
ms_min, ms_max = 0.0, 150.0
if any([not(ms_min <= e <= ms_max) for e in [key_pre_delay_ms, key_post_delay_ms]]):
error(f'Throttle delay value out of range. Clamping to valid range: {ms_min} to {ms_max}.')
_THROTTLES.update({ 'key_pre_delay_ms' : clamp(key_pre_delay_ms, ms_min, ms_max),
'key_post_delay_ms': clamp(key_post_delay_ms, ms_min, ms_max) })
debug(f'THROTTLES:\
\n\tPre-key : {_THROTTLES["key_pre_delay_ms"]}\
\n\tPost-key : {_THROTTLES["key_post_delay_ms"]}')


# keymaps
_KEYMAPS = []

Expand Down Expand Up @@ -198,7 +221,7 @@ def unicode_keystrokes(n):
for digit in _digits(n, 16)
for hexdigit in hex(digit)[2:].upper()
],
Key.ENTER
Key.ENTER,
]

return combo_list
Expand Down
13 changes: 11 additions & 2 deletions src/keyszer/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
from evdev import ecodes
from evdev.uinput import UInput


from .lib.logger import debug
from .models.action import PRESS, RELEASE
from .models.combo import Combo
from .models.modifier import Modifier
from .config_api import _THROTTLES

VIRT_DEVICE_PREFIX = "Keyszer (virtual)"

VIRT_DEVICE_PREFIX = "Keyszer (virtual)"

# Remove all buttons so udev doesn't think keyszer is a joystick
_KEYBOARD_KEYS = ecodes.keys.keys() - ecodes.BTN
Expand All @@ -36,6 +36,13 @@
_uinput = None


# for use with throttle delays
def sleep_ms(msec):
if msec == 0:
return
return time.sleep(msec / 1000)


def real_uinput():
return UInput(
name=f"{VIRT_DEVICE_PREFIX} Keyboard",
Expand Down Expand Up @@ -127,8 +134,10 @@ def send_combo(self, combo):
pressed_mod_keys.append(key)

# normal key portion of the combo
sleep_ms(_THROTTLES['key_pre_delay_ms'])
self.send_key_action(combo.key, PRESS)
self.send_key_action(combo.key, RELEASE)
sleep_ms(_THROTTLES['key_post_delay_ms'])

for modifier in reversed(pressed_mod_keys):
self.send_key_action(modifier, RELEASE)
Expand Down