Path: blob/main/core/posix-node/src/termios.zig
1396 views
// Inspired by https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html12const c = @import("c.zig");3const node = @import("node.zig");4const std = @import("std");5const clib = @cImport({6@cInclude("termios.h");7@cInclude("stdlib.h");8@cInclude("unistd.h");9@cInclude("wchar.h");10@cInclude("locale.h");11@cInclude("fcntl.h");12});1314pub const constants = .{15.c_import = clib,16.names = [_][:0]const u8{17// c_iflag's:18"IGNBRK", "BRKINT", "IGNPAR",19"PARMRK", "INPCK", "ISTRIP",20"INLCR", "IGNCR", "ICRNL",21"IXON", "IXANY", "IXOFF",22"IMAXBEL", "IUTF8",23// c_oflag's:24"OPOST",25"ONLCR", "OCRNL", "ONOCR",26"ONLRET", "OFILL", "OFDEL",27// c_cflag's:28"CSIZE", "CS5", "CS6",29"CS7", "CS8", "CSTOPB",30"CREAD", "PARENB", "PARODD",31"HUPCL", "CLOCAL",32// c_lflag's33"ICANON",34"ISIG", "ECHO", "ECHOE",35"ECHOK", "ECHONL", "NOFLSH",36"TOSTOP", "IEXTEN",37// optional_actions38"TCOOFF",39"TCOON", "TCIOFF", "TCION",40"TCIFLUSH", "TCOFLUSH", "TCIOFLUSH",41"TCSANOW", "TCSADRAIN", "TCSAFLUSH",42},43};4445pub fn register(env: c.napi_env, exports: c.napi_value) !void {46try node.registerFunction(env, exports, "getChar", getChar);47try node.registerFunction(env, exports, "enableRawInput", enableRawInput);48try node.registerFunction(env, exports, "setEcho", setEcho);49try node.registerFunction(env, exports, "makeStdinBlocking", makeStdinBlocking);50try node.registerFunction(env, exports, "tcgetattr", tcgetattr);51try node.registerFunction(env, exports, "tcsetattr", tcsetattr);52try node.registerFunction(env, exports, "fcntlSetFlags", fcntlSetFlags);53try node.registerFunction(env, exports, "fcntlGetFlags", fcntlGetFlags);54}5556const Errors = error{ GetAttr, GetFlags, SetFlags, SetAttr, SetLocale };5758fn tcsetattr(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {59const argv = node.getArgv(env, info, 3) catch return null;60const fd = node.i32FromValue(env, argv[0], "fd") catch return null;61const optional_actions = node.i32FromValue(env, argv[1], "optional_actions") catch return null;62const obj = argv[2];63// Important: we first initialize tio with the current value, then set the supported64// values. This is important, since there are several non-supported fields, and setting65// them to 0 will break everything.66var tio: clib.termios = undefined;67if (clib.tcgetattr(fd, &tio) != 0) {68node.throwErrno(env, "tcgetattr - failed");69}70tio.c_iflag = node.u32_from_object(env, obj, "c_iflag") catch return null;71tio.c_oflag = node.u32_from_object(env, obj, "c_oflag") catch return null;72tio.c_cflag = node.u32_from_object(env, obj, "c_cflag") catch return null;73tio.c_lflag = node.u32_from_object(env, obj, "c_lflag") catch return null;74// std.debug.print("tcsetattr({d}, {d}, {})\n", .{ fd, optional_actions, tio });75if (clib.tcsetattr(fd, optional_actions, &tio) != 0) {76node.throwErrno(env, "tcsetattr - failed");77}78return null;79}8081fn tcgetattr(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {82const argv = node.getArgv(env, info, 1) catch return null;83const fd = node.i32FromValue(env, argv[0], "fd") catch return null;84var tio: clib.termios = undefined;85if (clib.tcgetattr(fd, &tio) != 0) {86node.throwErrno(env, "tcgetattr - failed");87}88// std.debug.print("tcgetattr({d})={}\n", .{ fd, tio });89// TODO: This truncate worries me a bit below!90var ret = node.createObject(env, "Termios") catch return null;91node.set_named_property_to_u32(env, ret, "c_iflag", @truncate(u32, tio.c_iflag), "setting c_iflag") catch return null;92node.set_named_property_to_u32(env, ret, "c_oflag", @truncate(u32, tio.c_oflag), "setting c_oflag") catch return null;93node.set_named_property_to_u32(env, ret, "c_cflag", @truncate(u32, tio.c_cflag), "setting c_cflag") catch return null;94node.set_named_property_to_u32(env, ret, "c_lflag", @truncate(u32, tio.c_lflag), "setting c_lflag") catch return null;95return ret;96}9798fn _makeStdinBlocking() Errors!void {99var flags = clib.fcntl(clib.STDIN_FILENO, clib.F_GETFL, @intCast(c_int, 0));100if (flags < 0) {101return Errors.GetFlags;102}103if (clib.fcntl(clib.STDIN_FILENO, clib.F_SETFL, flags & ~(clib.O_NONBLOCK)) < 0) {104return Errors.SetFlags;105}106}107108// fcntlSetFlags: (fd: number, flags: number) => number;109// fcntl(clib.STDIN_FILENO, clib.F_SETFL, flags & ~(clib.O_NONBLOCK))110fn fcntlSetFlags(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {111const argv = node.getArgv(env, info, 2) catch return null;112const fd = node.i32FromValue(env, argv[0], "fd") catch return null;113const flags = node.i32FromValue(env, argv[1], "flags") catch return null;114if (clib.fcntl(fd, clib.F_SETFL, flags) < 0) {115node.throwErrno(env, "fcntlSetFlags - failed");116return null;117}118return null;119}120121// fcntlGetFlags: (fd: number) => number;122// fcntl(clib.STDIN_FILENO, clib.F_GETFL, 0)123fn fcntlGetFlags(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {124const argv = node.getArgv(env, info, 1) catch return null;125const fd = node.i32FromValue(env, argv[0], "fd") catch return null;126const flags = clib.fcntl(fd, clib.F_GETFL, @intCast(c_int, 0));127if (flags < 0) {128node.throwErrno(env, "fcntlGetFlags - failed");129return null;130}131return node.create_i32(env, flags, "flags") catch return null;132}133134fn makeStdinBlocking(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {135_ = info;136_makeStdinBlocking() catch {137node.throwErrno(env, "makeStdinBlocking - failed");138};139return null;140}141142fn enableRawInput(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {143_ = info;144_enableRawInput() catch {145node.throwErrno(env, "enableRawInput - failed");146};147return null;148}149150// disables icanon for the terminal and enables the locale so151// we can read a single wide character using getChar below.152var enabled = false;153fn _enableRawInput() Errors!void {154if (enabled) return;155156try _makeStdinBlocking();157158var raw: clib.termios = undefined;159if (clib.tcgetattr(clib.STDIN_FILENO, &raw) != 0) {160return Errors.GetAttr;161}162raw.c_lflag &= ~(@intCast(@TypeOf(raw.c_lflag), clib.ICANON));163if (clib.tcsetattr(clib.STDIN_FILENO, clib.TCSAFLUSH, &raw) != 0) {164return Errors.SetAttr;165}166167// On Linux we need C.UTF-8 but it isn't available on MacOS. On MacOS the168// default "" is fine, but that doesn't work for me on Linux...169// This concerns me.170if (clib.setlocale(clib.LC_ALL, "C.UTF-8") == null) {171if (clib.setlocale(clib.LC_ALL, "") == null) {172return Errors.SetLocale;173}174}175176enabled = true;177}178179fn setEcho(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {180const argv = node.getArgv(env, info, 1) catch return null;181const enable = node.valueToBool(env, argv[0], "missing true or false argument") catch return null;182var raw: clib.termios = undefined;183if (clib.tcgetattr(clib.STDIN_FILENO, &raw) != 0) {184node.throwErrno(env, "setEcho - tcgetattr failed");185return null;186}187if (enable) {188raw.c_lflag |= @intCast(@TypeOf(raw.c_lflag), clib.ECHO);189} else {190raw.c_lflag &= ~(@intCast(@TypeOf(raw.c_lflag), clib.ECHO));191}192if (clib.tcsetattr(clib.STDIN_FILENO, clib.TCSAFLUSH, &raw) != 0) {193node.throwErrno(env, "setEcho - tcsetattr failed");194return null;195}196return null;197}198199// Use getChar to do a blocking read of a character. This supports wide characters200// (reading utf-8) in your locale. This changes properties of stdin to201// different values than what Node.js supports!202fn getChar(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {203_enableRawInput() catch {204node.throwErrno(env, "getChar - failed to enable raw mode");205return null;206};207_ = info;208var buf: [10]u8 = undefined;209const w: clib.wint_t = clib.getwchar();210if (w == clib.WEOF) {211node.throwErrno(env, "EOF");212return null;213}214215var w2: [2]clib.wchar_t = undefined;216w2[0] = @intCast(@TypeOf(w2[0]), w);217w2[1] = 0;218219const bytes = clib.wcstombs(&buf, &w2, buf.len);220if (bytes == -1) {221node.throwErrno(env, "failed to convert wide string to bytes");222return null;223}224var result: c.napi_value = undefined;225if (c.napi_create_string_utf8(env, @ptrCast([*c]const u8, &buf), bytes, &result) != c.napi_ok) {226node.throwErrno(env, "error creating string in getChar");227return null;228}229return result;230}231232233