top | item 27172011

Why Is It So Hard to Detect Keyup Event on Linux?

156 points| EntICOnc | 4 years ago |blog.robertelder.org | reply

128 comments

order
[+] BoppreH|4 years ago|reply
I'm the author of the `keyboard`[1] library mentioned in the article.

- It reads all events because it's meant to interface with your keyboard directly, for global hotkeys, macros, disabling or remaping keys, etc. Interacting with windows and per-application hotkeys are explicitly not a goal of the library at the moment.

- It reads /dev/input because the library was developed to work in as many environments as possible, including headless installations like raspberry pi's that may not have a graphical environment or even a monitor. There's an open change to _try_ to communicate with the X server first and fallback to /dev/input, but event suppression is not working reliably with this yet.

- It could read /dev/input by just being in the `input` group, but then `dumpkeys` doesn't work and you are stuck typing numeric scan codes instead of key names.

Due to a series of unfortunate personal circumstances I've been unable to give the proper maintenance the library requires, but the issues and thank yous have never stopped, and I'm slowly getting back to it again.

---

Little known trick: if you run the library as a standalone module (`python -m keyboard`), it prints a JSON object for each detected event. You can save them to a file and pipe them back (`python -m keyboard < events.txt`) to replay them like a macro.

Little known trick 2: I also created `mouse`[2], the companion library for my second favorite peripheral. It's the same thing, but for mouse events.

[1]: https://github.com/boppreh/keyboard

[2]: https://github.com/boppreh/mouse

[+] jorl17|4 years ago|reply
Hi! I've used your library in various projects and am very grateful for it!

I figured it'd be worth a shot to ask you this here, since I just caught you: have you considered adding some support for "rehooking"/"restarting" the library, so that callees can restart your library whenever they detect that their environment has changed (e.g. input devices have been (dis)connected, etc)?

I've contributed with some info to this issue https://github.com/boppreh/keyboard/issues/264 as to how we're dealing with this in some of our projects. Funnily enough, I didn't know about `python -m keyboard`, and had I known about it, it would definitely have saved me some time!

Once again, thank you very much for your hard work!

[+] vanous|4 years ago|reply
Thank you very much for the library. Recently I have been learning touch typing and detecting system wise characters typed per day plus speed and acuracy is on my todo list. I already selected the keyboard module for this after some initial research.
[+] pjc50|4 years ago|reply
> A tty doesn't know about the concept of key press or key release events. It only knows about 'data stream in' and 'data stream out'.

This is basically the long and the short of it; it's a very old API, and retrofitting key-up detection reliably is a serious problem.

[+] CoastalCoder|4 years ago|reply
Perhaps this is nitpicking, but I don't think the issue here is "data stream" vs. not-"data stream".

Perhaps a better distinction is "data stream of characters" vs. "data stream that can express key events (and perhaps other stuff as well)".

[+] drran|4 years ago|reply
To fix this low level problem, someone will need to modify xterm, or libvte, or any other terminal, to send escape sequences for key release events, when such mode is requested by an application.
[+] pengaru|4 years ago|reply
Userspace doesn't implement debouncing in linux, that's driver territory; linux isn't an rtos. It's unclear why this is even mentioned at all.

And you don't need root if your program's user is in the input group on most distros. You're opening a device node in /dev/input/XXX, the standard UNIX permissions model applies...

[+] klodolph|4 years ago|reply
Yes, I think the article's description of getch() as being inadequate due to a lack of debouncing is a bit weird. I would think that debouncing would be done even earlier, in the keyboard itself, since the debouncing you need is necessarily specific to the actual hardware. While USB HID keyboards are state-based (they transmit a packet listing which keys are pressed), the PS/2 protocol is event-based, and transmits "make" and "break" messages for key-press and key-release events, respectively.

(That said, I wouldn't be surprised if debouncing were done in the driver. I just think it's the wrong place for it.)

[+] zozbot234|4 years ago|reply
> And you don't need root if your program's user is in the input group on most distros.

It's complicated. Being in the input group is part of it, but to actually get the access you need to interface with the freedesktop-derived multiseat stack, which is as clunky as everything else that comes from the fd.o folks.

[+] ajross|4 years ago|reply
tl;dr: It's not about Linux, it's the terminal.

Linux exposes your keyboards as /dev/inputN devices and (with the exception of oddball devices or drivers without the hardware capability to detect them) every single one of them presents clean down/up pairs on every press, every time. If you want to read the device state, read it from the kernel.

The problem is that the author doesn't want to detect the device state. The author wants to write a program to run at the command line. And the command line is an environment descended from a long line of tty environments going back to the original teletypes of the 1960's. And those devices were never designed to expose a "keyboard" as a "device". They were a source of "characters" only.

Now, over time countless people writing countless terminal emulators have tried to address this shortcoming, in countless not-quite compatible ways. And that's been about as successful as you'd expect. You can do it, but...

But again, it's not about device support. Your command line program isn't and has never been connected to a "keyboard" device via any useful abstraction. The same feature that allows you to pipe input into it instead of typing at it makes this problem hard.

[+] agumonkey|4 years ago|reply
What is the correct architecture or design to make TUI on a terminal then ? Honest question
[+] fulafel|4 years ago|reply
To add, you can read the kb events even in a command line program. Using GUI APIs works fine in terminal programs if the GUI is running. Works even remotely in a GUI that support network transparency, like X does. Eg like this:

https://stackoverflow.com/a/4037579

[+] amelius|4 years ago|reply
> Linux exposes your keyboards as /dev/inputN devices

Yeah, but those are only readable by root.

[+] silicon2401|4 years ago|reply
this comment was an excellent read. is there any kind of book that covers the historical development of computer tech in this way, that isn't just a full-blown technical reference?
[+] leoc|4 years ago|reply
Seeing this near the top of HN is a bit like seeing a "Why Does Italian Have All These Screwy Verb Endings?" post near the top of a web forum for professional students of linguistics ...
[+] munchbunny|4 years ago|reply
What's obvious to you might not be obvious to everyone. I've only heard reference to the issue previously but never looked at a detailed discussion, mostly out of not caring because I've never had to do something that depended on detecting keyup events in Linux in a non-browser context.
[+] ajkjk|4 years ago|reply
Really? There is almost no information in this article that I have ever heard before, and I imagine that I'm a pretty typical reader of this forum. You might have have a very mis-calibrated model of HN readers.
[+] teknopaul|4 years ago|reply
This is silly, you can't detect key up or keyb down in a tty, you detect what the terminal emulator gives you: chars and Esc sequences.

It's as sane as asking why you can detect joystick movements or the kettle boiling.

It bothers me how little people understand about the terminal and "the command line", despite using it everyday.

If people read man bash from top to bottom they would probably get a feel for what is going on. Learn how changing the title of your tty works and there is not much left to _not_ understand.

[+] imtringued|4 years ago|reply
Yeah, the author is not running into a Linux problem. He is running into an SSH problem.

>Something worth noting to avoid confusion is that if you run the example python keyboard example in the introductory paragraph of this article over an SSH connection, the code will still work and run, but when you type characters, nothing will happen.

>What gives?!? That's because it will be detecting 'local' keyboard events from the machine you just SSHed into! If it's a cloud server like EC2 or something, there probably aren't any keyboards attached to it!

"What gives?!?"? Is this supposed to be a joke? What else did you expect? Expecting your local keyboard to connect to the remote machine is as insane as expecting your keyboard to read the keys you want to press from your mind.

The problem he is running into is that SSH only forwards a character stream to the remote machine and that is entirely an SSH problem and the solution to forwarding the keyboard has been X forwarding. What he is asking is that SSH should be able to forward the keyboard without X.

He even mentioned a workaround that involves compensating the lack of this feature by forwarding the keyboard himself.

>Well, you have to build your own client/server application where a client/server listens locally on the machine with the keyboard, and then forwards these events to the remote machine where you're running the applications that needs to respond to these events.

My suggestion is that he should send an email to the openssh developers to add keyboard forwarding without X.

[+] account42|4 years ago|reply
> Are you interested in detecting local 'key up' events over an SSH connection without involving an X server? Oh boy, are you in for a disappointment! It turns out, that it's impossible. I don't mean the "I tried really hard and couldn't figure out how to do it, so it must therefore be impossible" kind of impossible, but the real kind of impossible where the keyup event doesn't get communicated at all during a non-X forwarded SSH session. Here is an experiment you can do to prove this to yourself [...]

This isn't really a good experiment as it assumes that everything that can be sent over SSH will always be sent. This not the case in the terminal world where by default you get a processed character stream and have to explicitly enable anything else. For example kitty's keyboard protocol [0] mentioned elsewhere in this thread needs to be activated by sending the escape sequence CSI > 1 u to the terminal on the other side of the SSH connection.

[0] https://sw.kovidgoyal.net/kitty/keyboard-protocol.html

[+] pcwalton|4 years ago|reply
> So, what do you do when you want to send 'key release' events over SSH when the target machine doesn't have an X server? Well, you have to build your own client/server application where a client/server listens locally on the machine with the keyboard, and then forwards these events to the remote machine where you're running the applications that needs to respond to these events. That sounds like a lot of work because you have to set up all the sockets and custom messaging protocols, but there's no way around it because the key release events simply aren't forwarded over your terminal-based SSH session when there is no X server forwarding configured.

This isn't that much work if you write it as a trivial Web app using WebSockets. Then you can leverage the browser's OS abstraction layer instead of having to write your own.

[+] marcodiego|4 years ago|reply
I don't think the article is fair. I'm not used to such lower level on linux, but it is certainly doable without root. SDL has been doing it for ages. Doing it from the terminal is another thing entirely since it was never designed for such kinds of events.
[+] thebeardisred|4 years ago|reply
I'm right there with you, but as someone who is used to the "lower levels" of Linux (to use your phrasing) this seems like the exploration of someone beginning to understand they were on the question side of an "X / Y problem" (https://xyproblem.info/).

Put differently, they're making the transition from Linux as a commodity used to build the thing they're dreaming of to Linux as a critical design component.

For example, he calls out in the beginning: > They are interested in performing some real-time based task that is controlled using keyboard presses. In my case, the goal was to remotely navigate a robot over an SSH connection using the 'w', 'a', 's', 'd' keys.

As the author discovers, that's fundamentally not how SSH works. This sort of behavior could be achieved using other mechanisms, but it's not even really an issue of the tty/pty. They're just trying to map a functional model from a different operating system to Linux.

As to the discussion of keyboard handling moves into Python code I would have again, gone a different path.

When the author took a turn into Python my first thought was this is trivial for evdev to handle (https://en.m.wikipedia.org/wiki/Evdev / https://python-evdev.readthedocs.io/en/latest/).

Next thing I know they touch on the kernel mechanisms managed by evdev.... And pivot across to X11 (which would seem to make sense until one realizes that the transition to Wayland from X11 is far further along than a layperson might imagine).

In the end a good write up which shows a lot of "raw power" on the part of the author. With some additional tutilage, exploration, or guidance they to really take their understanding to the next level.

For the folks who are in the weeds (like me) it is a good guided tour. Seeing into the "beginners mind" (and taking it to heart) can provide perspective as to how to make software more intuitive.

(Typed and butchered from my phone).

[+] riskable|4 years ago|reply
I could be wrong but I'm pretty sure SDL just reads the keydown/keyup events it gets from the X server (that's why you don't need to choose a keyboard device when you use it). So without X running SDL wouldn't be able to grab keyboard events.

It can grab joystick/gamepad events though since it reads those directly. In that case you have to enumerate then specify which gamepad/joystick device to use so it can open the correct /dev/ path.

[+] BoppreH|4 years ago|reply
It can certainly be done by interfacing with the X server, but it limits you to systems with a running X server (e.g. no headless raspberry PI's, no Wayland).

I have a planned change to use this a default, and fall back to /dev/input on non-X systems. It's not quite there yet, especially the capability of suppressing key events.

[+] GuB-42|4 years ago|reply
Small nitpick, if you use WASD for control, it may be better to use the keycodes and not the characters. People with non-QWERTY layouts probably want to use whatever keys they have on this location (ex: ZQSD for AZERTY layout).
[+] dec0dedab0de|4 years ago|reply
This might be a dumb question, but why would I want to detect a Keyup event in the first place?
[+] dragontamer|4 years ago|reply
Lets say you're porting a fighting game, such as Blazblue, to Linux.

Carl Clover (Blazblue character) attacks on keydown, while Carl's doll attacks on key-up. Expert players change the rhythms of their key-down / key-up to make combos possible.

In general, these kinds of fighting game characters are called 'Negative Edge' characters. There's a large number of them in many different fighting games. I know its present in Street Fighter, Marvel vs Capcom, Mortal Kombat, and Injustice. Even SSB:U (Shield-release / perfect shields) is a negative-edge event, showing just how common negative-edges have become in modern games.

I personally am only really good at Blazblue: and thus, I know that Carl Clover, and to a lesser extent Taokaka and Lichi are negative-edge characters. Its an entire "character design" philosophy, to make certain characters feel much different from others. But its all over the place.

----------

I HATE playing negative edge characters. If I realize a character has negative-edges, I run the heck away from them. Nonetheless, I accept that fighting games are fun because there's "always a character" that matches someone's personality.

If someone else has fun playing negative edge characters, I want to welcome them into the community, and therefore want to support negative-edge gameplay into a game. Its not about "me", its about "the community of players".

[+] theon144|4 years ago|reply
RTFA

>In my case, the goal was to remotely navigate a robot over an SSH connection using the 'w', 'a', 's', 'd' keys. Real-time tasks like this require extremely high responsiveness to key events for palatable performance.

Yeah, I don't think a SSH connection is the right tool either, but there's the answer (and basically the entire article follows from that).

[+] silon42|4 years ago|reply
To properly implement something like Ctrl+Tab for switching between tabs (assuming application has such a concept).
[+] akmittal|4 years ago|reply
All the applications getting keyup events seems like big security issue.

Is this same case with Wayland

[+] yw3410|4 years ago|reply
Is what the same as Wayland? Maybe you've misunderstood the article?

The process is running as root and reading directly from /proc; the article acknowledges that it's essentially a keylogger.

[+] memoblobi|4 years ago|reply
Because they are serial character devices?
[+] alkonaut|4 years ago|reply
Detecting keyup only in a window env seems like a perfectly good place to be. The problem there might be that there is no such thing as a "desktop linux" so writing a nice cross platform desktop app for any Linux regardless of window manager and desktop env I assume is a bit of a black art.
[+] josefx|4 years ago|reply
The X11 example only has a basic event loop with a check if the if the key up event was repeated. Everything else is setting up the window and tearing it down on close. That should work in any window manager.

Most of the examples are either global listeners or outside of a window environment.