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

Making multi-keystroke macros more reliable? #150

Open
RedBearAK opened this issue Jan 24, 2022 · 58 comments
Open

Making multi-keystroke macros more reliable? #150

RedBearAK opened this issue Jan 24, 2022 · 58 comments

Comments

@RedBearAK
Copy link

I've noticed that when trying to use macros they have a tendency to somehow crash or just stop outputting in the middle. Specifically this is with a multi-keystroke type of macro, like:

[K("x"), K("y"), K("z"), K("Space"), K("x"), K("y"), K("z"), K("Enter")], 

Ordinarily I would expect this to output:

xyz xyz
xyz xyz
xyz xyz
xyz xyz

With one line for each time I trigger the shortcut.

But what often happens, even if I wait a second between each trigger of the shortcut, is things like this:

xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xxyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xx
xyz xyz
xyz xyz
xyz xyz
xyz xyz
xxyz xyz
xyz xyz
xyz xyz
xyz xyz
xyxyz xyz
xyz xyz
xyz xyz
xyzxyz xyz
xyz xyz
xy
xyz xyz
xyz xyz
xyz xyz

I thought maybe the more keys in the macro, the more likely one of these hiccups will happen. But it actually often seems to happen right near the beginning of the macro sequence, if it's going to happen at all. Notice how there are a number of lines where the macro stopped after the first, second or third character, but really no lines that stop near the end.

Is there any way to tighten up the code to keep this from happening? Seems like the function responsible just develops a poor working memory at times. Or a buffer is getting overwhelmed with keystrokes coming in too fast? Maybe the keystrokes need a few milliseconds of delay before the next one?

I don't know, I'm just throwing out ideas here.

@mooz @Lenbok @rbreaves

@joshgoebel
Copy link

Maybe the keystrokes need a few milliseconds of delay before the next one?

It would be something if you could actually confirm that hypothesis... the code you're looking for is in transform.py#handle_commands... you want the for command in commands loop which is sending the array of "commands" to the output... it'd be easy to add a delay there.

Otherwise that sounds very strange behavior indeed.

@RedBearAK
Copy link
Author

@joshgoebel

I've tried putting many instances of sleep(1) throughout that loop in handle_commmands. Other than the fact that I have to do a second import time to keep from producing a crash when I start xkeysnail, the sleep lines seem to have no effect on how a macro gets sent to an application. The fact that there is a crash when I get the syntax wrong means I'm editing the right file, but nothing I do changes the speed of the macro.

/usr/local/lib/python3.10/dist-packages/xkeysnail/transform.py is the file I'm editing.

I use this somewhat lengthy macro in Firefox to test:

define_keymap(re.compile("Firefox", re.IGNORECASE),{
    K("C-comma"): [
        K("C-T"),K("a"),K("b"),K("o"),K("u"),K("t"),
        K("Shift-SEMICOLON"),K("p"),K("r"),K("e"),K("f"),
        K("e"),K("r"),K("e"),K("n"),K("c"),K("e"),K("s"),K("Enter")
    ],
})

When this works it seems to type at about the speed of someone typing at maybe 500 wpm. But it only works properly about 50% of the time.

@joshgoebel
Copy link

And what does the console say... does it log all the keys there despite them not seeming to get all the way to Firefox?

the sleep lines seem to have no effect on how a macro gets sent to an application.

Can you show me where exactly you're adding the delays?

@joshgoebel
Copy link

Other than the fact that I have to do a second import time to keep from producing a crash when I start xkeysnail

Do you have an isuse for that? That sounds strange - perhaps something is weird with your system... I'd try to figure out the simpel things first before delving into key delay issues. :)

@RedBearAK
Copy link
Author

@joshgoebel

The crash was probably down to some misuse of python syntax, like using sleep instead of time.sleep.

I've seen the same results on multiple different computers and multiple unrelated Linux distros over the course of the last couple of years. I don't think it has anything to do with my system.

Do you not have any issues with macros at all? Or have you not run the kind of test I ran that started this thread?

If you can do the same sort of test with perfect results 100% of the time, then maybe this is actually a problem with the specific config from Kinto.sh or the patched branch of xkeysnail that Kinto.sh uses to support "held keys" combos like Alt+Tab-Tab-Tab...

The view in the terminal when I remove --quiet is just showing the key combo I'm using, such as RC-Comma. I don't know how to see what xkeysnail is actually trying to send to the application.

As for where I tried to put the sleep(1) lines, I put one right after every line in handle_commands with a colon at the end of the line. All the if/elif lines inside the for loop. None of them seemed to have any effect on anything.

@joshgoebel
Copy link

joshgoebel commented May 29, 2022

Do you not have any issues with macros at all? Or have you not run the kind of test I ran that started this thread?

I'm new here. Haven't used the software much trying to decide if it's right for me and if I want to step up as part of a new small team to fork and continue development, respond and fix issues like these, etc. :-)

the patched branch of xkeysnail that Kinto.sh uses

Possibly but his changes to output have more to do with modifier states than plain keys... actually the plain key stuff is pretty boring which is why these glitches are surprising to me.

I don't know how to see what xkeysnail is actually trying to send to the application.

Ah, you're right... i was thinking of poiting you d7901ec but that's only logging in the transform, what you want is some logging in the output.

using sleep instead of time.sleep.

Yeah it's time.sleep... and you'd need to import time of course...

I put one right after every line in handle_commands

That'd be hard to miss but you only really need one at the top of the main loop:

for command in command_list:
  # add a delay

But see my next message about adding this to output itself, I think that'd be better.

@joshgoebel
Copy link

I don't know how to see what xkeysnail is actually trying to send to the application.

You'd want to add logging to send_key_action and send_combo, just a print at the top to print the argument should be sufficient to give you some workable output... if you see the output on the console but the keystrokes aren't coming thru then something most be gummed up in uinput... perhaps adding a small delay there... since all output goes thru send_sync() perhaps try:

def send_sync():
    _uinput.syn()
    time.sleep(0.1)

You shouldn't need anything crazy like sleeping for a whole second.

@joshgoebel
Copy link

@rbreaves Have you heard of any similar bug reports?

@joshgoebel
Copy link

joshgoebel commented May 29, 2022

https://stackoverflow.com/questions/70324709/uinput-seems-to-break-while-sending-inputs-in-quick-succession

This seems super relevant... (though no answer), but it's the same problem you're describing... So perhaps even a 25 ms sleep in sync would help.

output.py

def send_sync():
    _uinput.syn()
    time.sleep(25/1000) # sleep 25ms

That would kind of suck for typing out long macros though if true... maybe play with a delay before or after sync and if it makes a difference...

@joshgoebel
Copy link

joshgoebel commented May 29, 2022

I use this somewhat lengthy macro in Firefox to test:

I just ran this 20-30 times in rapid succession doing:

  • Ctrl-, (trigger the macro, open preferences tab)
  • Ctrl-w close the tab
  • rinse/repeat

Every once in a blue moon Firefox would lag on the input a hair, but it worked every time, 100% consistently. I definitely think you're experience some type of edge case that not everyone is running into.

Note: This using 0.4.0/master... if you think the problem is the kinto version you'd need to test on the mainline version to confirm whether the issue persists or not. Should be easy to rule kinto in or out as the source of the trouble with that one test.

@RedBearAK
Copy link
Author

@joshgoebel

Of course I know just enough about how a for loop works that I put a sleep line right at the beginning of the loop. The use of one second was just because smaller values seemed to have no effect. I like to make things blindingly obvious when I'm troubleshooting.

def send_sync():
    _uinput.syn()
    time.sleep(25/1000) # sleep 25ms

Alright, so I found this in output.py, and wow, that's something to be really careful with. Finally had an effect, at least. But 25ms is quite a long delay, and it puts that delay between every key press or combo you type. EVERY key press. So 25ms delay before Alt+Tab switches to another app, etc.

BTW, had to do another import time in that file above the def send_sync to avoid an error when trying this.

I shortened it down to 5ms (5/1000) and that's short enough to not interfere much with regular typing and shortcuts, but it definitely still has a strong effect on how quickly the macro gets entered.

And, even that very short delay seems to make the macro a lot more reliable for me. I just did the Firefox prefs shortcut like 30 times in a row without a failure. There are the usual variations in how fast it seems to happen, even appearing to enter all the text almost instantaneously at times, while other times it's slow enough to look like someone "typing" really fast. But it worked 100% of the time for as long as I had the patience to test it.

If I do it so fast that another tab opens before the first is finished, the full text will now always be there in the URL bar but sometimes it apparently loses keyboard focus at the point where it would have sent the Enter key to finish the macro, so the tab is just sitting there with about:preferences in the URL bar, but a blank window and the generic "New Tab" Firefox icon instead of the Preferences gear icon. Obviously this is not a normal thing the user would do.

Now to tune the delay. I'm kind of surprised that such a short delay is already effective. The 25ms that the person was talking about in the link about uinput is almost an order of magnitude longer delay. That would indicate a rather severe problem with uinput receiving mouse clicks.

I still would like to understand why none of my attempts to insert a delay in the for loop amounted to anything. Don't understand that at all.

...

Still working with as little as 1ms as a value. Still seeing an extraordinary amount of variability in terms of how fast the macro actually seems to get sent to the app. Sometimes too fast to see anything, as if the full text was just pasted all at once. Other times, almost as slow as my fastest typing speed on a good day. Very visibly "typing" each individual letter slowly enough for me to watch it.

But, it's still succeeding at actually completing the entire macro without errors or missing characters, even after doing about 50 preferences tabs. Even when it acts much more slowly on occasion.

I think maybe the variability is simply down to the reliance on the system sleep command, which isn't real-time accurate in any way, from what I understand. And it looks like 1ms is actually the minimum possible value that would be meaningful.

It looks like my supposition that some kind of input buffer was failing must be correct, in a way. The question is what, and where.

I've even had a lowlatency kernel installed on Ubuntu for a while now, just for kicks. It appeared to have no effect on this issue.

@joshgoebel
Copy link

The use of one second was just because smaller values seemed to have no effect.

Yeah I understand, just with 1 second you're starting to get into the area where the delay could cause it's own kind of problems is all. :-)

Still working with as little as 1ms as a value.

Unfortunately I don't know what to tell you - or where we'd go from here... I don't think adding a 1ms sleep here when no one else is reporting any issues is the correct solution, but I'm also not sure exactly how we'd go about gathering additional data.

Still seeing an extraordinary amount of variability in terms of how fast the macro actually seems to get sent to the app.

That seems strange to me since all that is happening on the Python side is a tight loop and a tiny amount of I/O... I saw some variability but I'd assume it was the Firefox UI, not xkeysnail.

It looks like my supposition that some kind of input buffer was failing must be correct,

You'd think... but we're talking a buffer with almost 0 capacity? Since it short circuits right away in so many of your tests it would seem the buffer is almost non-existant, which is weird.

@joshgoebel
Copy link

It looks like time.sleep() only has millisecond resolution. Though it could range between a few milliseconds to over 20 milliseconds (best case scenarios), based on what OS you are using, it does not look like you will get accurate microsecond sleep execution from it.

https://stackoverflow.com/questions/58553072/how-to-make-a-delay-at-100-200us

Could by Python itself that's the issue as evidentally this isn't what sleep was designed to do...

@joshgoebel
Copy link

https://github.com/torvalds/linux/blob/master/drivers/input/misc/uinput.c

Looks like uinput doesn't change that much and the buffer size is 16... 16 events I'm assuming...

@RedBearAK
Copy link
Author

@joshgoebel

Yeah I understand, just with 1 second you're starting to get into the area where the delay could cause it's own kind of problems is all. :-)

Hey, when you're not catching any fish the usual way, a round stick of material capable of rapidly oxidizing after ignition can come in handy. 🤘

Unfortunately I don't know what to tell you - or where we'd go from here... I don't think adding a 1ms sleep here when no one else is reporting any issues is the correct solution, but I'm also not sure exactly how we'd go about gathering additional data.

I find it very strange that nobody else has seemingly ever had this issue. I would attribute that to most users simply not using macros much, and an even smaller percentage attempting to use long macros with a higher probability of failing. Unless it really is somehow the Kinto branch that has this issue, but in that case I would expect other Kinto users to complain, especially about the Firefox preferences shortcut. But it's not even limited to Firefox, I had a similar issue with Chrome at times when I tried to use a similar macro to get to settings. And of course the testing in the text editor from the first post.

I don't see adding a 1ms delay to be harmful, but that's just my perspective. The question is why does such a ridiculously short delay, apparently the absolute minimum possible with time.sleep, seem to completely fix this. I literally haven't been able to get macros to fail since this change. I just did 100 lines of this text with a macro in the new GNOME Text Editor app, with no failures:

now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
...

There is a significant delay when I try to hit the shortcut multiple times, but when the entire buffered output (could be many lines, nothing shows up as long as I keep tapping the shortcut too quickly) finally shows up in the app, there are never any errors. No missing Enter at the end of line, no incomplete lines. Nothing. Works perfectly. (Well, see below.)

I just held Ctrl+Shift+Alt and "buzzed" the "t" as fast as I could, to do this macro. Nothing shows up for at least 5 seconds, then I get 25 new perfect lines of the same text. No problems other than the delayed output.

I can't even pretend to understand exactly what is failing without the delay. The buffered macro lines I'm inserting into the text editor are obviously WAY more than 16 characters, yet they never have errors. So the uinput buffer would not appear to be the problem... unless it really doesn't like receiving characters too fast, maybe?

I need the patched 0.3.0 xkeysnail to make things like Alt+Grave and Alt+Tab work correctly, so I'm not sure how to safely test the 0.4.0 branch, but I might do it sometime. I really would like to get this permanently solved.

Uh oh, looks like I was too optimistic. I finally got a couple of lines to show up in the text editor with missing characters. Haven't changed anything since previous testing.

now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of tntry.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of theountry.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.
now is the time for all good men to come to the aid of their country.

This is doing it slowly, letting each line appear before trying to do it again.

Interesting that these failures are near the end, where testing in the first post almost exclusively saw failures near the beginning of the macro.

Doing dozens more lines in the same way results in no other errors.

I don't know what to tell me either.

@joshgoebel
Copy link

I find it very strange that nobody else has seemingly ever had this issue.

Well so far that's the data... and for absolute empirical reported data we have only our individual experiences... mine is rock solid with no delay added... so that paired with no other reports make me think you're the outlier - though your points about not everyone using long macros is definitely worthy of note.

The buffered macro lines I'm inserting into the text editor are obviously WAY more than 16 characters, yet they never have errors. So the uinput buffer would not appear to be the problem...

You lost me here... I thought you were saying it was working so well only AFTER you had applied the delay... I'm not sure what data point you're trying to explain in this paragraph.

@RedBearAK
Copy link
Author

RedBearAK commented May 29, 2022

@joshgoebel

You lost me here... I thought you were saying it was working so well only AFTER you had applied the delay... I'm not sure what data point you're trying to explain in this paragraph.

You said uinput had a buffer of 16. Assumedly, 16 characters? When I trigger the macro shortcut rapidly before the first output appears, nothing appears until I pause for a while. Then the whole thing shows up. Could be hundreds of characters of output that was successfully assembled behind the scenes as I repeatedly tap the main macro key while holding the modifiers. And it pops into place in the app almost instantly.

So, somehow something is holding onto all those characters in memory and then finally inserting them into the app. A lot more than 16 characters worth. I'm just saying I don't really understand what relationship the small uinput buffer has to the overall issue.

And yes, this is only working with 99% reliability after inserting the delay. Before that it really was about 50/50 as a long term average. Sometimes it seemed to work pretty well, other times it was even worse than usual.

Data point: Happens in Ubuntu 22.04 (and previous iterations going back to 20.04 on the same machine). Also happens inside a VM running KDE Neon, where Kinto/xkeysnail is disabled on the Ubuntu host while using GNOME Boxes, and Kinto/xkeysnail is installed inside the VM.

@joshgoebel
Copy link

You said uinput had a buffer of 16. Assumedly, 16 characters?

No, events. A key press is typically 3 events, the scancode, the key event and then a SYN... our artificial output is usually just two events, but that's 4 events if you're counting key up and key down. So a single letter is often 4-6 input events.

Could be hundreds of characters of output that was successfully assembled

But if this was with the 1ms delay added then you aren't really stressing the buffer much at all since you're giving it time to drain...

Anyways if processes are waiting on keyboard input then putting anything into the buffer should wake those processes up... allowing them to read the keyboard before the buffer fills - that's how it's supposed to work.

@joshgoebel
Copy link

I need the patched 0.3.0 xkeysnail to make things like Alt+Grave and Alt+Tab work correctly,

You'd just have to "suffer" with the normal linux keystrokes when testing 0.4.0.... you can do all the same things, you just have to hit the REAL keys linux wants. But I don't really think that's your issue.

@RedBearAK
Copy link
Author

@joshgoebel

Data point: 2007 MacBook running recent install of Fedora 36 KDE spin. Core 2 Duo, 6GB mem.

Even without the delay added, inserts output in Firefox relatively slowly. More reliable than the other machine but I was able to get it to fail several times out of about a hundred tries. More frequently as the machine struggled to keep up with so many tab being opened. Most commonly it just wouldn’t hit Enter at the end of the macro, but partial insertions of text also occurred.

Another: Machine almost identical to the main system, but slightly slower CPU (Ryzen 3500u, 4c, 8t) and half the RAM (16GB). Running Fedora 36 (GNOME 42, like Ubuntu 22.04).

No delay added, has a macro error about 5% of around a hundred test tries.

Add the 1ms delay, can’t produce a failure in at least two hundred preferences tabs.

No, events. A key press is typically 3 events, the scancode, the key event and then a SYN... our artificial output is usually just two events, but that's 4 events if you're counting key up and key down. So a single letter is often 4-6 input events.

I see. So just a couple of characters really.

@RedBearAK
Copy link
Author

RedBearAK commented May 29, 2022

@joshgoebel

Did a sudo pip3 install —upgrade xkeysnail on the Fedora system to get the unpatched 0.4.0 installed.

After opening probably at least two hundred prefs tabs I haven’t had a single failure. Just the usual momentary delays at times when the machine can’t quite dedicate enough cycles to the xkeysnail process (as I open ridiculous numbers of new tabs each minute without bothering to close any). But no actual failure to complete the macro.

So something related to the Kinto patch?

@RedBearAK
Copy link
Author

@joshgoebel

Thanks for all the input, by the way. At least it led to finding a 99% reliable mitigation solution. A vast improvement for my daily driver system.

@joshgoebel
Copy link

something related to the Kinto patch?

You'd think that if you can jump back and forth between "have problems" and "zero problems" just by using kinto or mainline... but damn it's hard to say where... they honestly aren't THAT different...

Ideas:

  • Try cherry picking/patching with afb2c79 - maybe configuring UInput properly does some magic
  • Try adding logging to send_key_action and make note of any output differences (there will be some, but I mean MAJOR differences)

@RedBearAK
Copy link
Author

@joshgoebel

No, adding those lines from the commit and removing the delay line results in nothing good. Back to frequent partial failures of the Firefox macro.

As always, I don't really know how to implement the logging commands. I tried print() on every other line but of course that just produced blank lines.

Ah, things like print(key) and print(action) produce... a lot. But I'm not actually sure what kind of "output differences" you're referring to. Like, between when a macro works and when it doesn't? That's a lot of lines of output in the terminal, unless I can figure out how to keep everything from coming out on a new line.

@RedBearAK
Copy link
Author

RedBearAK commented May 30, 2022

@joshgoebel

Did some reformatting of the output. Still trying to figure out which parts are truly irrelevant and can be left out.

This feels kind of odd. I'm seeing two release events every time I press and release Right Ctrl, Left Ctrl, or Alt, but only one release event for Shift. Is that normal for mainline xkeysnail?

Watching keyboard devices plug in
=================================================================================================
send_key_action:key Key.LEFT_SHIFT                  send_key_action:action Action.PRESS
update_modifier_key_pressed:key Key.LEFT_SHIFT      update_modifier_key_pressed:action Action.PRESS
update_pressed_keys:key Key.LEFT_SHIFT              update_pressed_keys:action Action.PRESS

=================================================================================================
send_key_action:key Key.LEFT_SHIFT                  send_key_action:action Action.RELEASE
update_modifier_key_pressed:key Key.LEFT_SHIFT      update_modifier_key_pressed:action Action.RELEASE
update_pressed_keys:key Key.LEFT_SHIFT              update_pressed_keys:action Action.RELEASE

=================================================================================================
send_key_action:key Key.LEFT_CTRL                  send_key_action:action Action.PRESS
update_modifier_key_pressed:key Key.LEFT_CTRL      update_modifier_key_pressed:action Action.PRESS
update_pressed_keys:key Key.LEFT_CTRL              update_pressed_keys:action Action.PRESS

=================================================================================================
send_key_action:key Key.LEFT_CTRL                  send_key_action:action Action.RELEASE
update_modifier_key_pressed:key Key.LEFT_CTRL      update_modifier_key_pressed:action Action.RELEASE
update_pressed_keys:key Key.LEFT_CTRL              update_pressed_keys:action Action.RELEASE

=================================================================================================
send_key_action:key Key.LEFT_CTRL                  send_key_action:action Action.RELEASE
update_modifier_key_pressed:key Key.LEFT_CTRL      update_modifier_key_pressed:action Action.RELEASE
update_pressed_keys:key Key.LEFT_CTRL              update_pressed_keys:action Action.RELEASE

=================================================================================================
send_key_action:key Key.LEFT_ALT                  send_key_action:action Action.PRESS
update_modifier_key_pressed:key Key.LEFT_ALT      update_modifier_key_pressed:action Action.PRESS
update_pressed_keys:key Key.LEFT_ALT              update_pressed_keys:action Action.PRESS

=================================================================================================
send_key_action:key Key.LEFT_ALT                  send_key_action:action Action.RELEASE
update_modifier_key_pressed:key Key.LEFT_ALT      update_modifier_key_pressed:action Action.RELEASE
update_pressed_keys:key Key.LEFT_ALT              update_pressed_keys:action Action.RELEASE

=================================================================================================
send_key_action:key Key.LEFT_ALT                  send_key_action:action Action.RELEASE
update_modifier_key_pressed:key Key.LEFT_ALT      update_modifier_key_pressed:action Action.RELEASE
update_pressed_keys:key Key.LEFT_ALT              update_pressed_keys:action Action.RELEASE

=================================================================================================
send_key_action:key Key.RIGHT_CTRL                  send_key_action:action Action.PRESS
update_modifier_key_pressed:key Key.RIGHT_CTRL      update_modifier_key_pressed:action Action.PRESS
update_pressed_keys:key Key.RIGHT_CTRL              update_pressed_keys:action Action.PRESS

=================================================================================================
send_key_action:key Key.RIGHT_CTRL                  send_key_action:action Action.RELEASE
update_modifier_key_pressed:key Key.RIGHT_CTRL      update_modifier_key_pressed:action Action.RELEASE
update_pressed_keys:key Key.RIGHT_CTRL              update_pressed_keys:action Action.RELEASE

=================================================================================================
send_key_action:key Key.RIGHT_CTRL                  send_key_action:action Action.RELEASE
update_modifier_key_pressed:key Key.RIGHT_CTRL      update_modifier_key_pressed:action Action.RELEASE
update_pressed_keys:key Key.RIGHT_CTRL              update_pressed_keys:action Action.RELEASE

@RedBearAK
Copy link
Author

@joshgoebel

Wonder if this has anything to do with anything.

I see a ton of press/release events for RIGHT_CTRL during the whole macro, even if I make a point of hitting Ctrl+Comma and releasing the Ctrl key as quickly as possible.

Watching keyboard devices plug in
Key.RIGHT_CTRL             Action.PRESS       #####      
WM_CLASS 'firefox' | active keymaps = [Anonymous keymap, General Web Browsers, Anonymous keymap, General GUI, Wordwise - not vscode]
RC-COMMA
Key.T             Action.PRESS       #####      
Key.T             Action.RELEASE       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.A             Action.PRESS       #####      
Key.A             Action.RELEASE       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.B             Action.PRESS       #####      
Key.B             Action.RELEASE       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.O             Action.PRESS       #####      
Key.O             Action.RELEASE       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.U             Action.PRESS       #####      
Key.U             Action.RELEASE       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.T             Action.PRESS       #####      
Key.T             Action.RELEASE       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.SEMICOLON             Action.PRESS       #####      
Key.SEMICOLON             Action.RELEASE       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.P             Action.PRESS       #####      
Key.P             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.R             Action.PRESS       #####      
Key.R             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.E             Action.PRESS       #####      
Key.E             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.F             Action.PRESS       #####      
Key.F             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.E             Action.PRESS       #####      
Key.E             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.R             Action.PRESS       #####      
Key.R             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.E             Action.PRESS       #####      
Key.E             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.N             Action.PRESS       #####      
Key.N             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.C             Action.PRESS       #####      
Key.C             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.E             Action.PRESS       #####      
Key.E             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.S             Action.PRESS       #####      
Key.S             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.ENTER             Action.PRESS       #####      
Key.ENTER             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.RIGHT_CTRL             Action.RELEASE       #####      
Key.LEFT_SHIFT             Action.RELEASE       #####      
Key.RIGHT_CTRL             Action.PRESS       #####      

Without the delay there was quickly a failure in Firefox, but I can't see any corresponding failure in this log output. It seems to always show the full macro in the terminal even if it didn't actually work in the application.

@joshgoebel
Copy link

It seems to always show the full macro in the terminal even if it didn't actually work in the application.

Expected.

I see a ton of press/release events for RIGHT_CTRL during the whole macro,

Yep, you've found it. And look at shift also... this would seem to be a bug/flaw in the design of how Kinto's handling keystate and how it wants to persist the modifiers until the full combo ends - shift getting "stuck" half on/off is definitely a bug. The buffer just can't handle all that [unnecessary] data being thrown at it so very fast. If you added more modifiers (right shift, alt) I'd imagine you'd see things get progressively worse and worse.

@rbreaves

This might point out an overall flaw in the overall engine as well... that holding and reasserting the original "real" input state should perhaps be handled differently across large sequences of combos/keys:

  • Real keys should be lifted at the beginning of combo(s).
  • Combo(s) (many possible artificial key-presses should run)
  • Real keys should finally be reasserted, but only at the very end.

@joshgoebel
Copy link

Even then I wonder if we might find that in SOME legit edge cases one could kill the buffer in NORMAL usage and that we might need to build in a throttle so that if we detect we're pushing too much data we handle flow control ourselves.

@RedBearAK Your welcome - and thanks for putting in the time to do the testing, it was great to just toss you ideas and then you come back with test results. I've learned a lot here about the underlying engine and input processing.

@joshgoebel
Copy link

@RedBearAK You might have better luck with a throttle.... so have a global counter, and you count the writes to uinput... when you get close to the buffer size, you add a LONGER delay (now we can afford more time because we're doing it less often)... then reset the counter. This is a stop gap solution though - really the engine needs to just not send all the raise/press keystrokes.

@joshgoebel
Copy link

joshgoebel commented Jun 7, 2022

I was having issues that reminded me of this... the Cmd-, for preferences was inconsistently working in Chrome... but it seems the problem is that Chrome is too slow... The ALT-E happens, then IF the s enter happens a fraction of a second before the menu opens you can submit a search or have some other weird behavior and then menu just hangs open.

Often it's fast enough, but not always. I wonder if we might always need a small delay between long sequences of keystrokes...

This is kind of a problem with feeding input to apps when we have no idea if they are truly ready for that input or not.

@RedBearAK
Copy link
Author

@joshgoebel

This is kind of a problem with feeding input to apps when we have no idea if they are truly ready for that input or not.

That is definitely an issue, not knowing if you're going to be typing in the right place.

preferences was inconsistently working in Chrome

I had that very same trouble with Chrome, and only on one machine if I remember right. Other machines would seem to work perfectly with that same macro. I'll bet it was the same laptop that gave me so much trouble with macros in general.

I remember that I changed the Chrome prefs macro to be more like the Firefox prefs macro. In other words I made it open a new tab and type "chrome://settings" instead of trying to use the quicker keyboard shortcut. It seemed to work a lot better for me. Now I realize that was probably just because it took a fraction of a second longer to operate.

Most likely with the artificial sleep delay I have on that machine now, even the original Chrome prefs macro would work reliably.

Between cutting out some of the redundant key press events and adding the 1ms delay in the output loop, this issue could probably be solved in 99.99% of cases.

@joshgoebel
Copy link

Most likely with the artificial sleep delay I have on that machine now, even the original Chrome prefs macro would work reliably.

I forget how much delay you're using but I first tried a generic 1ms delay after ever command and that didn't fix it, that's when I just made a PR to add the 10ms delay in the config itself.

@joshgoebel
Copy link

Although your delay is per key, so that's entirely different I suppose.

@RedBearAK
Copy link
Author

@joshgoebel

Although your delay is per key, so that's entirely different I suppose.

That's the only one that ever had an actual observable effect for me.

And I just had a thought that it could be even more effective if the sleep came first, instead of last. That way there's a tiny delay even before the very first event.

def send_sync():
    time.sleep(1/1000) # sleep 1ms
    _uinput.syn()

@joshgoebel
Copy link

You wouldn't need a delay before the first event... that's the safest one, empty buffer.

@RedBearAK
Copy link
Author

empty buffer

I was thinking more that it would give the receiving window another fraction of a second to be ready to receive input, but of course for the very first event the window wouldn't have received the shortcut yet to even trigger a new tab or something, so yeah. Silly idea.

@joshgoebel
Copy link

When it breaks I can see the events with evtest but not with xinput

I just noticed this (or for some reason it just clicked)... if the events make it to evdev then the problem isn't the uinput, it's higher up... can you test your broken setup again but (while also breaking it in the UI) have evtest running pointed at the virtal output device? py-evdev-uinput

If evtest shows the events but they don't make it thru this it's a higher-level issue like libinput or something.

@RedBearAK
Copy link
Author

@joshgoebel

If evtest shows the events but they don't make it thru this it's a higher-level issue like libinput or something.

OK, I removed the sleep from output.py and did the test with evtest pointing at that device. I managed to get two FF preferences macro failures pretty quickly and each time I saw the full "about[Shift][Semicolon]preferences[Enter]" events reflected in the evtest output. No apparent interruption of the event sequence. Along with all the excessive RIGHTCTRL and LEFTSHIFT events. In one of the failures only "ab" made it into the URL bar. On the other it performed a web search for "abou" or something to that effect.

This is with the Kinto branch, as always.

@joshgoebel
Copy link

joshgoebel commented Jun 9, 2022

saw the full "about[Shift][Semicolon]preferences[Enter]" events reflected in the evtest output.

Could you run xev now and confirm they are missing from there (as the poster on Stack Overflow indicates it is for them)? If so perhaps then we raise this with the libinput people and see if they have any thoughts/suggests? They've been helpful in the past.

https://gitlab.freedesktop.org/libinput/libinput/

@RedBearAK
Copy link
Author

RedBearAK commented Jun 9, 2022

@joshgoebel

I'm not immediately seeing exactly how to interpret this output. It seems much less chatty about individual key press events. I'll have to spend some time looking at it, but maybe you have better eyes. This is the full output of a run with sleep removed and a macro failure near the end that ended up doing a web search for "aces". LOL. That's an unusual one.

Actually this seems to be more talking about window/focus events, which might have been relevant to the poster having issues with clicks. Not so much missing keystrokes.

xev-output.txt

Yeah, I did xev -event keyboard and after messing around in FF for a while there's still only one keyboard event shown in the terminal. Don't think this is quite the right tool for catching what's going on with libinput, if that is where the problem lies.

@joshgoebel
Copy link

Oh sorry xev probably needs the focus when you run the macro, so you might have to tweak your config to allow that... it only shows events that go to it.

@RedBearAK
Copy link
Author

@joshgoebel

Oh sorry xev probably needs the focus when you run the macro, so you might have to tweak your config to allow that... it only shows events that go to it.

Oh, so if I put a macro shortcut in the terminal block and run it while xev is active, all the keystrokes should show? Makes sense. I'll give that a try when I get a chance.

@joshgoebel
Copy link

Oh, so if I put a macro shortcut in the terminal block and run it while xev is active,

No, I think you need to do it on top of the xev window... so xprop it and make a macro just for it... then trigger the macro there... and we're wanting to see if xev drops the ball... in which case I think we can go to the libinput people.

@RedBearAK
Copy link
Author

No, I think you need to do it on top of the xev window... so xprop it and make a macro just for it... then trigger the macro there... and we're wanting to see if xev drops the ball... in which case I think we can go to the libinput people.

Oh, the little window itself. Sure. Makes even more sense.

@RedBearAK
Copy link
Author

RedBearAK commented Jun 9, 2022

@joshgoebel

Well, this is extremely contrived in order to get something human readable from the extensive output, but:

Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisi.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
TSamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisao.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.

The xev window doesn't have a WM_CLASS attribute, so I had to go with a macro in the "general" block which will work even with windows without a WM_CLASS.

Saw the same kind of pattern of incomplete lines whether I held the modifier keys the whole time, or did each shortcut individually.

    K("RC-Shift-Alt-t"):    [K("Shift-t"),K("h"),K("i"),K("s"),K("Space"),K("i"),K("s"),K("Space"),K("a"),K("Space"),K("m"),K("a"),K("c"),K("r"),K("o"),K("dot"),K("Space")],

The spaces were sedded out during the trimming process. Each character was originally on a separate line as part of the general output.

xev -event keyboard | grep -i xmblookup | grep -v "gives 0 bytes" | sed ':a;N;$!ba;s/\n//g' | sed -e 's/XmbLookupString gives 1 bytes://g' | sed -e 's/     //g' | sed -e 's/(..)//g' | sed -e 's/"//g' | sed -e 's/ //g' | sed 's/T/\'$'\nT/g'

But... before doing all the filtering for readability I was unable to distinguish any instance of incomplete macros when everything was vertical. The output in the terminal window was strangely inconsistent, but whenever I would quit the xev window it would all end up looking complete in the terminal. I don't know what that means.

Of course, early on in the filtering it's grepping only for the "XmbLookup" lines, which seemed most relevant. There are other "XLookup" lines that are not in this output.

Without the filtering the output from even a single instance of the macro is quite extensive and difficult to sort through visually.

@RedBearAK
Copy link
Author

@joshgoebel

There's the capital "S" in the output above, and then this capital "O":

Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Tmacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
ThisO.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisa.
Thisisamacro.
Thisisamacro.
Thismacro.
Thisisamacro.
Thisisamacro.

I find that very peculiar. Neither of those letters should be capitalized, and the "O" is separated from the capital "T" by multiple letters in between, yet somehow it got capitalized also.

@RedBearAK
Copy link
Author

@joshgoebel

Just to be thorough (25 macros in each run) I put the sleep line back, and of course this is the predictable result:

Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.
Thisisamacro.

@joshgoebel
Copy link

Ok, so if I'm reading this right xev also has the input issue... lets file an issue with libinput and see what they say then... I'd reference this issue as well as the stack overflow report...

@RedBearAK
Copy link
Author

@joshgoebel

I'm not even sure how to describe the issue, or how it relates to libinput. Any suggestions? I'm sure you'd have much better luck using terminology they might understand.

@RedBearAK
Copy link
Author

@joshgoebel

And didn't we already figure out that this problem is basically nonexistent outside of the Kinto branch of xkeysnail? Maybe the Kinto branch is just using libinput wrong or breaking internally somehow.

@joshgoebel
Copy link

And didn't we already figure out that this problem is basically nonexistent outside of the Kinto branch of xkeysnail?

Did we? Did you? That's not something I can confirm. I can't reproduce it, but that doesn't mean it's not an issue... but you've tried my code and 0.4.0... does the issue magically go away? If so then I'd consider it fixed. If you're still having the issue on 0.4.0 or my codebase then I'd say there is something deeper going on.

@joshgoebel
Copy link

joshgoebel commented Jun 9, 2022

I mean my version (and kinto mainline) sends a lot less bogus events, so that [seems to] HELP, but did it FIX it? I dunno... you tell me. :)

@RedBearAK
Copy link
Author

@joshgoebel

I don't think there's strong evidence that it's never a problem on the other branches, necessarily, just that it doesn't seem to happen there in an easily replicateable way. I just want to make sure there's real evidence here that it's somehow a libinput problem that libinput could, and should, fix.

@joshgoebel
Copy link

I just want to make sure there's real evidence here that it's somehow a libinput problem that libinput could, and should, fix.

Well, just because it doesn't happen doesn't mean we're actually fixing the problem... it sounds like something is out of whack - things should not just "get lost" (or corrupted) between the Linux layer and the X layer... so your last tests likely proves there is some problem somewhere - for reals. And libinput would be the first place to check - they might have ideas to go further.

IE, it could be kinto is a "best case" for putting the right kind of pressure on the system to reveal the problem - not that it's itself causing it. But hard to say.

If you're not really experiencing it anymore I'm happy to let this drop on the floor for now... enough things to focus on.

@ian-h-chamberlain
Copy link

Sorry to revive an old issue but, from reading the thread I'm not sure exactly what the workaround is, and I started experiencing this myself. @RedBearAK where did you actually put the sleep you mentioned, and how long is it? I'd love to apply the same kind of workaround if possible.

@RedBearAK
Copy link
Author

@ian-h-chamberlain

So, the fundamental problem here is that something (kernel input?) seems to get confused on the timing of the modifier key press-release cycle, which is theoretically being "pressed" before the non-modifier key it is supposed to affect, and "released" after that key is released.

This was an order of magnitude worse inside some GNOME Boxes virtual machines I was testing. To the point where even a lot of common shortcut combos were failing to do what was expected. On bare metal the issue is usually fixed with just a millisecond or so of "space" around the "normal" key press. In the VMs it took adding a window of about 50ms before and 70ms after the "normal" key press-release to provide a 99.9% cure for the issue.

I haven't used xkeysnail for several months, replacing it with Josh Goebel's fork keyszer, which has a number of new features and bug fixes, and some significantly different behavior in some ways. I worked out an API function that would insert a configurable delay inside the keymapper at the place that seemed the most effective after doing a LOT of testing. So the easiest way to deal with this problem is to switch to keyszer and use this in your config file to inject some delays appropriate to your situation.

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

This is just an example from my bare metal install on Fedora, on a mid-range Ryzen laptop.

Here's the pull request that added the new API function. The delays are in the send_combo() function, in output.py.

https://github.com/joshgoebel/keyszer/pull/134/files

The new API is in mainline keyszer, so if you download the latest state from the green button on GitHub or do the below commands to get it, this feature should be there.

cd ~/Downloads
git clone https://github.com/joshgoebel/keyszer
cd keyszer
pip install --upgrade .

@ian-h-chamberlain
Copy link

Thanks for the detailed steps -- I had been considering switching to keyszer for a while anyway, so maybe this is another good reason to do it. I guess I was sort of hoping rbreaves/kinto#718 might happen before I decided to switch, but I have enough custom stuff + issues at this point maybe I should just bite the bullet and swap over.

@RedBearAK
Copy link
Author

@ian-h-chamberlain

There's a custom config in the comments on this pull request, along with some instructions for setting up keyszer that a few people have found useful. The two files in the "Files" tab of the page can be downloaded "raw" and used as a Kinto config, following the instructions in the comment.

rbreaves/kinto#750 (comment)

Or, a much newer version of the config that I'm currently using is posted further down in the thread, which does not require messing with the keyboard type modmaps. With the last version I posted in the thread there may be a problem with an extra extension on one of the "include" files, like ".py.py". Have to fix that. Otherwise, it should be a good starting point for a Kinto-like config for keyszer. But with some new features. Like the automated keyboard types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants