joshgoebel / keyszer

a smart, flexible keymapper for X11 (a fork/reboot of xkeysnail )
Other
69 stars 15 forks source link

Add throttle delays to send_combo normal keystroke #134

Closed RedBearAK closed 1 year ago

RedBearAK commented 1 year ago

Changes

Modifies config_api to add a new function (throttle_delays) that the user can optionally call from their config, to insert two possible sleep delays in output.

Usage from config:

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

Delay variables default to zero if there is no user input (or if input value is out of range), leaving no effect on the respective areas.

As shown in the comments in the example, the range is capped at 0 to 150 milliseconds for the keystroke delays to add to send_combo. A shorter "pre-keystroke" delay and longer "post-keystroke" delay may maximize effectiveness with the shortest possible delay for each situation.

The locations of the throttles are designed and tested to minimize the affect on using shortcut combos and regular typing.

Related issues

As noted in more than one issue previously, sometimes the output from the virtual keyboard seems to be getting misinterpreted by the receiving application window, leading to failing Unicode sequences, failing hotkeys/shortcut combos, and/or macro string output being corrupted in various ways. Mostly regarding the apparent timing of when the application believes the modifier keys are pressed or released. When this problem occurs, the only apparent solution to this appears to be throttling the speed of the emitted keystrokes until the issue disappears.

Checklist

RedBearAK commented 1 year ago

@joshgoebel

Check the new commits. A global dict stores the individual throttle variables and their default values (zero). Injected values from config update the matching key values in the dict, and output.py imports and directly uses the new dict for the keystroke delays, as well as importing sleep_ms from config_api.

Everything is much simpler.

The concept of just passing a global (unless it's an unchanging constant, which should work fine) between Python modules is a bust. Doesn't work well if the value needs to be kept in sync as it changes in the other module.

RedBearAK commented 1 year ago

As updated in the first post, this would be the new API form:

throttle_delays(
    unicode_delay_ms    = 10,    # default: 0 ms, range: 0 to 100 ms, suggested: 10 ms
    key_pre_delay_ms    = 40,    # default: 0 ms, range: 0 to 150 ms, suggested: 1-50 ms
    key_post_delay_ms   = 70,    # default: 0 ms, range: 0 to 150 ms, suggested: 1-100 ms
)
RedBearAK commented 1 year ago

Might be prudent to expand the range on the low end a bit. I heard some whisperings that this timing issue may be caused by IBus, and that does seem to be a big part of it. I tried ibus exit to shut down the IBus daemon, and the major issue in the VM is substantially mitigated. Not completely gone, it will still have macro failures with the delays disabled, and pretty bad ones sometimes (stopping in the middle of the macro). But hotkeys work with IBus shut down, and adding just 1ms of post keystroke delay allows the macros to recover.

Now I'm going to try and allow decimal values down to 0.001ms and see just how small a delay fixes the issue with IBus not running.

But, pretty sure without IBus the Unicode shortcut (Shift-Ctrl-u) won't work, just like it wouldn't work in Kubuntu until I set up IBus. I'll be testing that.

Edit: The Unicode shortcut definitely doesn't work in KWrite installed from Flathub, without IBus running. But it continues to work in GTK apps even without IBus active. Like it's a built-in capability of the GTK framework, in addition to being a feature of IBus.

Tried an alternative (Ctrl-Shift-Alt-U), but that would probably require fcitx to be configured as the input method. Does nothing in my current setup.

IBus is supposed to have an environment variable to choose the sync mode, and a different sync mode would theoretically mitigate the issue. But I haven't had any luck with that so far.

RedBearAK commented 1 year ago

This is pretty crazy. There is partial reliability of macros with even 0.001ms of delay, as long as IBus is disabled. Although that's probably below the limit of what time.sleep() can actually produce. But there does seem to be a valid case for allowing values smaller than 1ms, like 0.01 to 0.1ms. Good results can still be obtained in that range.

Really strange failure happened at 0.001ms though:

CapsLock วณapped!!!!!

There is no "z" anywhere in the macro. So how that particular failure is possible, especially without IBus being involved, is beyond me. It's like a key code was only partially sent and was misinterpreted as a different key.

I'm just going to remove the int requirement. Since I'm already allowing the value to be zero, that will allow the user to try decimal values below 1ms if they feel like it.

joshgoebel commented 1 year ago

Really strange failure happened at 0.001ms though:

For which knob?

RedBearAK commented 1 year ago

Really strange failure happened at 0.001ms though:

For which knob?

Wrong question, it was just too short of a delay. The others were set to zero at the time. The point is that even without IBus involved there is still a reliability issue with too many fast keystrokes.

Mystery solved with the "z", which I didn't notice is actually "Latin Small Letter Dz", a single Unicode character at U+01F3, which must have originated from the U+1F339 of the Unicode rose being cut off in the middle of the sequence. So, nothing too unusual actually happened there.

Had pretty good results with 0.1ms, but 1ms delay was very solid. (With IBus off.)

Tried adding the environment variable IBUS_ENABLE_SYNC_MODE=1 to both my user .profile and even the system environment file. Still haven't seen it having any noticeable effect on the problem with IBus active. So the only known solution for now when IBus is present is going to be the longer delays.

joshgoebel commented 1 year ago

At the end of a thread where it's tried to tell me several times the function is real:

Screen Shot 2023-02-24 at 10 17 54 PM
joshgoebel commented 1 year ago

Amazing how fast it understands what that copy and paste represents though. This stuff is so amazing and scary.

RedBearAK commented 1 year ago

It's helpful to remember that like it always says, it is only a "language model". It's only simulating understanding based on the patterns it has ingested from the web. That's why it runs off the rails and gives bad examples a lot. There's literally no intelligence involved in what it's doing. It's just trying to predict what the next word "should" be by extrapolating.

But it's pretty damn handy for giving intelligible "mostly right" explanations of documentation that would ordinarily take hours or days to understand. And the code examples it gives back are often at least 90% functional.

RedBearAK commented 1 year ago

Updated the PR with the changes to both files. Let me know what else needs to be completely rewritten. ๐Ÿ‘๐Ÿฝ ๐Ÿ˜„

RedBearAK commented 1 year ago

Added an updated Readme with a section to document the API, why/when it may be useful, and some sample values for different situations.

joshgoebel commented 1 year ago

This is looking really good, almost there!

joshgoebel commented 1 year ago

I think we're good here - you happy?

RedBearAK commented 1 year ago

I think we're good here - you happy?

I'd like to update the copying of the DEFAULTS dict references with deepcopy():

import copy
_THROTTLES = copy.deepcopy(THROTTLE_DELAY_DEFAULTS)
joshgoebel commented 1 year ago

You've been learning a lot, so congrats on that. :)

RedBearAK commented 1 year ago

You've been learning a lot, so congrats on that. :)

Few things here and there. ๐Ÿ˜†

If you're fine with not erasing the inconsistency by adding deepcopy(), I'm fine with its current state. ๐Ÿ‘๐Ÿฝ