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);
}
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.