Problem with Key events in TUI

So ... I recently discovered the Glicol music programming language (glicol.org), and thought it was pretty cool. But the existing implementation is browser-based, and I don't like doing that kind of thing in a browser, so I decided to develop a TUI environment for Glicol. I've done some GUI programming in the past, but this is my first serious attempt to work with the terminal.

My program uses ratatui and tui-textarea with crossterm as a backend. I've created a simple proof-of-concept version, and it sort of works, but I've encountered a problem with keyboard events. I don't think it's actually a bug, but it is behavior that I didn't expect and don't want.

I would like to use the keystroke combinations Ctrl+Enter to evaluate code in the main window, and Ctrl+. to stop playing sound. Of course, it's not absolutely necessary to use these particular keys, but I'd really like to, because (1) they are used in other music programming environments - e.g. in the very popular SuperCollider, and at least one other program I've seen, and (2) they are IMHO quite intuitive.

But it turns out those combinations don't work.

My event handling code looks like this:

    match crossterm::event::read()?.into() {
        Input {
            key: Key::Enter,
            ctrl: true,
            .. 
        } => {
            let code = textarea.lines().join("");
            let _ = tx.send(CtrlReq::Process(code));
        },
        Input {
            key: Key::Char('q' | 'Q'),
            ctrl: true,
            ..
        } => {
            let _ = tx.send(CtrlReq::Stop);
            break;
        },
        Input {
            key: Key::Char('.'),
            ctrl: true,
            ..
        } => {
            let _ = tx.send(CtrlReq::Pause);
        },
        input => {
            log(format!("Input: {:?}", input));
            let _ = textarea.input(input);
        }
    }

I've discovered that for certain keys, including Enter and ., crossterm's KeyEvent struct simply ignores the Ctrl modifier. And I guess that sort of makes sense, since apparently the crossterm keyboard mapping follows the old-school Unix terminal conventions. So, as I said, I guess it's not a bug, but it's not good for my purposes.

Is there any way I can make crossterm read the Ctrl key in combination with Enter and .? Or any other library I could use instead, to get the behavior I want?

I don't think this is possible with the terminal. unlike GUI system, the tty doesn't actually use "modifier" keys, instead certain key combinations are encoded as ASCII control characters or ANSI escape codes, for example, Ctrl-a is sent as ASCII SOH, start of heading, or 0x01, Alt-a is usually sent as ESC a, or 0x1b 0x61, etc.

crossterm just parse the encoded bytes and provides an "event"-like API, but fundamentally, they are completely different than the events in GUI system.

1 Like

Yes, but with kitty protocol one may support all these combos.

This severely limits number of supported terminals, of course.

4 Likes

A useful way to understand terminal input (without kitty extensions) is that it is a stream of typed text (plus a few control-character “commands”), not pressed keys. This makes sense of all of the limitations; everything you can't detect is because it is not the text.

1 Like

Mm, yeah ... it happens that Kitty is my usual terminal. But I don't want my program to be restricted in that way.

IDK, maybe I'll go with a GTK GUI instead of a TUI. My thinking was that since sound synthesis can be very resource-intensive, it would be good to have a very lightweight UI. But I think probably a GTK implementation - as long as it's not overly elaborate - could be lightweight enough for most situations. And I have a feeling this won't be the last time I have to work around the quirkiness of the terminal.

Ah, thank you! That does make sense.

Kitty protocol is supported by more than just Kitty: it's also supported by iTerm2, Ghostty, etc. With more on the way.

But, well… it's really funny that it took 50+ years for someone to, finally, push through something that all micros provided, essentially,on the day one…