I tried this:
const std = @import("std");
pub fn main(init: std.process.Init) !void {
var buf: [1024]u8 = undefined;
var stdin_reader = std.Io.File.stdin().reader(init.io, &buf);
const stdin = &stdin_reader.interface;
const input = try stdin.takeByte();
std.log.debug("input: {any}", .{input});
}
…but it waits for me to press enter. I think it has been asked here already , but the answer there didn’t return the char immediately either on the older 0.13 version of Zig
Also I barely have any idea how to program in Zig and absolutely no idea how getch works in any other language. pls help
Terminals don’t send data until enter is pressed. This is called canonical mode. you can change this behavior by taking the terminal out of canonical mode, but that is usually done in the context of a TUI app.
If you pipe input from a file into your process stdin, I think you’ll see the behavior you are looking for.
Hello all, I was wondering what an approach might be for polling a buffer for a key-down event? Would I use std.io ?
I have a pause function…
Current tragic state:
read first byte of user input from \n terminated string (sigh)
q → exit program
otherwise → continue execution
Preferred awesome state:
poll key-down buffer for key-down event
ESC → exit program
other key down → continue execution
no input received → perform some other action (in my use case - animate ansi text fx), then ret…
This thread goes into discussions about how to poll for key down events and non-cannoncial modes, if that is what interests you.
As Calder said, this is not something you can do from within your program, regardless of the programming language. Only the shell itself can do that.
this is so oversimplified as to be obviously false, since in C for example, ncurses implements a getch that works like this. what would be more correct is to say that in order to get a single character, you have to negotiate with the shell to not buffer input. Userland programs absolutely can do that.
3 Likes
I kinda figured it out myself (and by looking at the code of getch-rs )
Apparently what I had to do was:
const std = @import("std");
const c = @cImport({
@cInclude("termios.h");
@cInclude("unistd.h");
});
pub fn main(init: std.process.Init) !void {
var term: c.struct_termios = undefined;
// read current settings and store them in `term`?
_ = c.tcgetattr(c.STDIN_FILENO, &term);
// get chars right away
term.c_lflag &= ~@as(c_uint, c.ICANON);
// don't print them
term.c_lflag &= ~@as(c_uint, c.ECHO);
// save settings?
_ = c.tcsetattr(c.STDIN_FILENO, c.TCSANOW, &term);
// intuitive part
var buf: [1024]u8 = undefined;
var stdin_reader = std.Io.File.stdin().reader(init.io, &buf);
const stdin = &stdin_reader.interface;
const input = try stdin.takeByte();
std.log.debug("input: {any}", .{input});
}
And add -lc to zig run command and it just works
I guess noob issue, hopefully this will help if someone else stumbles upon it
3 Likes
libc is not required.
std.posix provides the tc*attr API but with more zig idiomatic types e.g. packed structs instead of integer bit ops and zig errors instead of errno.
It may use a zig native implementation if libc is not linked, but does use libc if it is linked.
2 Likes
Oh so something like this?
const std = @import("std");
fn prepare_terminal_for_getch() !void {
// read current settings
var term = try std.posix.tcgetattr(0);
// get chars right away
term.lflag.ICANON = false;
// don't print them
term.lflag.ECHO = false;
// save settings?
try std.posix.tcsetattr(0, std.posix.TCSA.NOW, term);
}
pub fn main(init: std.process.Init) !void {
try prepare_terminal_for_getch();
var buf: [1024]u8 = undefined;
var stdin_reader = std.Io.File.stdin().reader(init.io, &buf);
const stdin = &stdin_reader.interface;
const input = try stdin.takeByte();
std.log.debug("input: {any}", .{input});
}
Thank you, this looks a lot simpler. Like I said, I have no idea how any of that terminal magic works, now I at least have a partial idea
I couldn’t find the STDIN_FILENO thing, but at least it works too, hopefully idiomatic enough
std.posix.STDIN_FILENO does exist
1 Like