How to read a multi-byte key press (or how to check if stdin is empty)?

Hi. I’m making a TUI for Linux. I’ve been having an issue where I can’t parse all key presses.

For example, most control characters like up arrow, down arrow, the F1-F12 keys all start with a 0x1b or ESC byte and then have an unknown number of bytes after. It seems some have a delimiter of ~, but some simply don’t.

Anyway, I haven’t found a Zig solution that works for correctly parsing key inputs. I first tried using Io.Reader.takeByte() to get the first byte from stdin. Then I checked if that byte is ESC, and if it is, I peek a byte. This works for then getting the rest of the bytes of a control character. The issue occurs if the user presses ESC, because peekByte() waits input if there’s nothing in stdin.

I have tried to use ioctl() with FIONREAD to check if stdin was empty but it didn’t seem to work, just told me there were 0 bytes in the buffer.

pub fn is_stdin_empty() bool {
    var bytes: u32 = undefined;
    const ret = linux.ioctl(linux.STDIN_FILENO, linux.T.FIONREAD, @intFromPtr(&bytes));

    if (ret == 0 and bytes == 0) {
        return true;
    } else {
        return false;
    }
}

So my question is, is there a Zig way to check if stdin is empty, and if not, whats the POSIX/Linux way to do this?

EDIT: (Note the double title because I’m unsure if this is an XY problem and if there is a Zig way to properly parse multibyte keys with the Io interface)

Thanks

Have you tried just using a timeout after reading an escape?

1 Like

How would this even be implemented? The issue is that .peekByte and .takeByte both wait for stdin, I couldn’t find any way to only takeByte if there is anything in the buffer. I think any timeout would require me to takeByte and then it waiting until something was actually entered

I wonder whether you can do something similar to this Zig port of the kilo editor.

/// Keep reading from stdin until we get a valid character, ignoring
/// .WouldBlock errors.
pub fn readChars(buf: []u8) !usize {
    while (true) {
        const n = posix.read(STDIN_FILENO, buf) catch |err| switch (err) {
            error.WouldBlock => continue,
            else => return err,
        };
        if (n >= 1) return n;
    }
}
1 Like

I do two io.concurrent() calls, one to my desired function and the other to an io.sleep(), wrap them in Io.Select and whichever one finishes first I act accordingly, need help with that?

I tried this and it works well, thanks.

1 Like

The solution I marked is much cleaner imo than a timeout or having to do concurrency

1 Like