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