[BRLTTY] Key combinations not (yet?) handled by the Linux virtual console

Nicolas Pitre nico at fluxnic.net
Wed Nov 26 22:01:30 UTC 2025


On Wed, 26 Nov 2025, Aura Kelloniemi wrote:

> Or actually, better would be to take a look at the Kitty keyboard protocol
> here: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
> 
> It seems that most modern terminal emulators prefer to implement the kitty
> protocol, because it is more advanced and more backwards-compatible than the
> Xterm's implementation of "CSI u". See the above link for all terminal
> emulators which support this protocol.
> 
> Kitty protocol has support for very advanced features, like sending
> applications information not only about key presses but also key releases.
> 
> I think it would be worthwhile to try to implement this in a TTY application
> which runs in the Linux console, reads raw keyboard input from the evdev
> devices and runs any terminal program (like tmux) in a pseudo-terminal,
> providing this program with the kitty keyboard protocol facilities.

You don't even need to access the evdev interface. In fact as a normal 
user you might not have permission to open it.

But the simpler KDSKBMODE ioctl just works on a Linux VT. See attached 
source code. This is like the showkey program but it was easier to 
reimplement than digging the showkey sources.

> Would anybody be interested in implementing this? I'm actually very
> interested, if there are others who feel the calling to join. The
> implementation language should be C, if we want to pave way for integrating
> this into Linux console in the future. If Linux integration is a low priority
> (or it is clear that this feature is unwanted by Linux developers), we can
> use some other systems language, like Zig, C++ or Rust.

I'm willing to add it to Linux if someone else does the research for the 
actual protocol to implement with justifications (seems you are into 
that already). Despite Linux console being a low priority, I had no 
issues refreshing Unicode support and adding bracketed paste support 
last time around.


Nicolas
-------------- next part --------------
#include <linux/kd.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <signal.h>
#include <stdlib.h>

int fd = -1;
long prev_mode;
struct termios orig_termios;

void cleanup(void) {
    if (fd >= 0) {
        // Restore keyboard mode
        ioctl(fd, KDSKBMODE, prev_mode);
        // Restore terminal settings
        tcsetattr(fd, TCSANOW, &orig_termios);
        close(fd);
        fd = -1;
    }
    printf("\nCleaned up and exiting.\n");
}

void signal_handler(int sig) {
    cleanup();
    exit(0);
}

int main(void) {
    fd = open("/dev/tty", O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    // Save original terminal settings
    if (tcgetattr(fd, &orig_termios) < 0) {
        perror("tcgetattr");
        return 1;
    }

    // Save keyboard mode
    if (ioctl(fd, KDGKBMODE, &prev_mode) < 0) {
        perror("KDGKBMODE");
        return 1;
    }

    // Set up signal handlers for cleanup AFTER saving states
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
    signal(SIGHUP, signal_handler);

    // Register cleanup on normal exit
    atexit(cleanup);

    // Set terminal to raw mode with timeout
    struct termios raw = orig_termios;
    raw.c_lflag &= ~(ICANON | ECHO | ISIG);
    raw.c_iflag &= ~(IXON | ICRNL);
    raw.c_cc[VMIN] = 0;      // Non-blocking read
    raw.c_cc[VTIME] = 50;    // 5 second timeout (in deciseconds)

    if (tcsetattr(fd, TCSANOW, &raw) < 0) {
        perror("tcsetattr");
        return 1;
    }

    // Set keyboard to K_MEDIUMRAW mode
    if (ioctl(fd, KDSKBMODE, K_MEDIUMRAW) < 0) {
        perror("KDSKBMODE");
        return 1;
    }

    printf("Listening for keyboard events. Will exit after 5 seconds of inactivity.\n\n");

    unsigned char keycode;
    int bytes_read;
    while ((bytes_read = read(fd, &keycode, 1)) >= 0) {
        if (bytes_read == 0) {
            // Timeout - no key pressed within timeout period
            printf("\nInactivity timeout reached.\n");
            break;
        }

        unsigned char key = keycode & 0x7F;
        int released = keycode & 0x80;

        if (released) {
            printf("Released: ");
        } else {
            printf("Pressed: ");
        }

        switch (key) {
            case KEY_ESC:        printf("ESC\n"); break;
            case KEY_ENTER:      printf("ENTER\n"); break;
            case KEY_UP:         printf("UP\n"); break;
            case KEY_DOWN:       printf("DOWN\n"); break;
            case KEY_LEFT:       printf("LEFT\n"); break;
            case KEY_RIGHT:      printf("RIGHT\n"); break;
            case KEY_LEFTSHIFT:  printf("LEFT SHIFT\n"); break;
            case KEY_RIGHTSHIFT: printf("RIGHT SHIFT\n"); break;
            case KEY_LEFTCTRL:   printf("LEFT CTRL\n"); break;
            case KEY_RIGHTCTRL:  printf("RIGHT CTRL\n"); break;
            case KEY_LEFTALT:    printf("LEFT ALT\n"); break;
            case KEY_RIGHTALT:   printf("RIGHT ALT\n"); break;
            case KEY_LEFTMETA:   printf("LEFT META\n"); break;
            case KEY_RIGHTMETA:  printf("RIGHT META\n"); break;
            default:             printf("keycode %d\n", key); break;
        }
    }

    return 0;
}


More information about the BRLTTY mailing list