terminalstdiozig

How to capture a single keystroke without hitting Enter


How can I capture a single keystroke in my terminal without hitting Enter using Zig?

Here is what I am trying:

const std = @import("std");

const c = @cImport({
    @cInclude("termios.h");
    @cInclude("unistd.h");
    @cInclude("stdlib.h");
});

var orig_termios: c.termios = undefined;

pub fn enableRawMode() void {
    _ = c.tcgetattr(c.STDIN_FILENO, &orig_termios);
    _ = c.atexit(disableRawMode);

    var raw: c.termios = undefined;
    const flags = c.ICANON;
    raw.c_lflag &= ~flags;

    _ = c.tcsetattr(c.STDIN_FILENO, c.TCSANOW, &orig_termios);
}

pub fn disableRawMode() callconv(.C) void {
    _ = c.tcsetattr(c.STDIN_FILENO, c.TCSANOW, &orig_termios);
}

pub fn main() !void {
    enableRawMode();

    var char: u8 = undefined;
    const stdin = std.io.getStdIn().reader();

    char = try stdin.readByte();
    std.debug.print("char: {c}\n", .{char});
}

This doesn't compile because I cannot assign my flags. I get the error:

main.zig:16:20: error: type 'c_ulong' cannot represent integer value '-257'
    raw.c_lflag &= ~flags;
                   ^~~~~~

I have similar code working in C already, so I was trying to adapt it to Zig:

#include <termios.h>

void buffer_off(struct termios *term) {
  tcgetattr(STDIN_FILENO, term);
  term->c_lflag &= ~ICANON;
  tcsetattr(STDIN_FILENO, TCSANOW, term);
}

Solution

  • I learned the Zig way to disable/enable terminal buffering:

    const stdin = std.io.getStdIn();
    
    pub fn buffer_on(stdin: *const std.fs.File) !void {
        const term = try std.posix.tcgetattr(stdin.handle);
        try std.posix.tcsetattr(stdin.handle, .NOW, term);
    }
    
    pub fn buffer_off(stdin: *const std.fs.File) !void {
        var term = try std.posix.tcgetattr(stdin.handle);
        term.lflag.ICANON = false;
        try std.posix.tcsetattr(stdin.handle, .NOW, term);
    }
    

    Calling buffer_off() makes it so stdin.reader().readByte() no longer requires hitting Enter. Then at the end of main() I call buffer_on() to restore my terminal to normal.