diff options
author | Thijs <thijs@thijsschreijer.nl> | 2023-11-16 09:09:54 +0100 |
---|---|---|
committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2024-04-30 09:28:01 +0200 |
commit | bd994461ef7c2553da9a6945c685152bad50eb8f (patch) | |
tree | 28adc32712f00a200a34357e731a570bf1a359dc /src/term.c | |
parent | 47c24eed0191f8f72646be63dee94ac2b35eb062 (diff) | |
download | luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.tar.gz luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.tar.bz2 luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.zip |
feat(term): getting/setting terminal config flags
Diffstat (limited to 'src/term.c')
-rw-r--r-- | src/term.c | 822 |
1 files changed, 817 insertions, 5 deletions
@@ -1,37 +1,849 @@ | |||
1 | /// @submodule system | 1 | /// @submodule system |
2 | |||
3 | // Unix: see https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/ | ||
4 | // Windows: see https://learn.microsoft.com/en-us/windows/console/console-reference | ||
5 | |||
2 | #include <lua.h> | 6 | #include <lua.h> |
3 | #include <lauxlib.h> | 7 | #include <lauxlib.h> |
4 | #include <lualib.h> | 8 | #include <lualib.h> |
5 | #include "compat.h" | 9 | #include "compat.h" |
10 | #include "bitflags.h" | ||
6 | 11 | ||
7 | #ifndef _MSC_VER | 12 | #ifndef _MSC_VER |
8 | # include <unistd.h> | 13 | # include <unistd.h> |
9 | #endif | 14 | #endif |
10 | 15 | ||
16 | #ifdef _WIN32 | ||
17 | # include <windows.h> | ||
18 | #else | ||
19 | # include <termios.h> | ||
20 | # include <string.h> | ||
21 | # include <errno.h> | ||
22 | # include <fcntl.h> | ||
23 | # include <sys/ioctl.h> | ||
24 | # include <unistd.h> | ||
25 | #endif | ||
26 | |||
27 | #ifdef _WIN32 | ||
28 | // after an error is returned, GetLastError() result can be passed to this function to get a string | ||
29 | // representation of the error on the stack. | ||
30 | // result will be nil+error on the stack, always 2 results. | ||
31 | static void termFormatError(lua_State *L, DWORD errorCode, const char* prefix) { | ||
32 | //static void FormatErrorAndReturn(lua_State *L, DWORD errorCode, const char* prefix) { | ||
33 | LPSTR messageBuffer = NULL; | ||
34 | FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||
35 | NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); | ||
36 | |||
37 | lua_pushnil(L); | ||
38 | if (messageBuffer) { | ||
39 | if (prefix) { | ||
40 | lua_pushfstring(L, "%s: %s", prefix, messageBuffer); | ||
41 | } else { | ||
42 | lua_pushstring(L, messageBuffer); | ||
43 | } | ||
44 | LocalFree(messageBuffer); | ||
45 | } else { | ||
46 | lua_pushfstring(L, "%sError code %d", prefix ? prefix : "", errorCode); | ||
47 | } | ||
48 | } | ||
49 | #else | ||
50 | static int pusherror(lua_State *L, const char *info) | ||
51 | { | ||
52 | lua_pushnil(L); | ||
53 | if (info==NULL) | ||
54 | lua_pushstring(L, strerror(errno)); | ||
55 | else | ||
56 | lua_pushfstring(L, "%s: %s", info, strerror(errno)); | ||
57 | lua_pushinteger(L, errno); | ||
58 | return 3; | ||
59 | } | ||
60 | #endif | ||
11 | 61 | ||
12 | /*** | 62 | /*** |
13 | Checks if a file-handle is a TTY. | 63 | Checks if a file-handle is a TTY. |
14 | 64 | ||
15 | @function isatty | 65 | @function isatty |
16 | @tparam file file the file-handle to check | 66 | @tparam file file the file-handle to check, one of `io.stdin`, `io.stdout`, `io.stderr`. |
17 | @treturn boolean true if the file is a tty | 67 | @treturn boolean true if the file is a tty |
68 | @usage | ||
69 | local system = require('system') | ||
70 | if system.isatty(io.stdin) then | ||
71 | -- enable ANSI coloring etc on Windows, does nothing in Posix. | ||
72 | local flags = system.getconsoleflags(io.stdout) | ||
73 | system.setconsoleflags(io.stdout, flags + sys.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
74 | end | ||
18 | */ | 75 | */ |
19 | static int lua_isatty(lua_State* L) { | 76 | static int lst_isatty(lua_State* L) { |
20 | FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE); | 77 | FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE); |
21 | lua_pushboolean(L, isatty(fileno(*fh))); | 78 | lua_pushboolean(L, isatty(fileno(*fh))); |
22 | return 1; | 79 | return 1; |
23 | } | 80 | } |
24 | 81 | ||
25 | 82 | ||
83 | /*------------------------------------------------------------------------- | ||
84 | * Windows Get/SetConsoleMode functions | ||
85 | *-------------------------------------------------------------------------*/ | ||
26 | 86 | ||
27 | static luaL_Reg func[] = { | 87 | typedef struct ls_RegConst { |
28 | { "isatty", lua_isatty }, | 88 | const char *name; |
29 | { NULL, NULL } | 89 | DWORD value; |
90 | } ls_RegConst; | ||
91 | |||
92 | // Define a macro to check if a constant is defined and set it to 0 if not. | ||
93 | // This is needed because some flags are not defined on all platforms. So we | ||
94 | // still export the constants, but they will be all 0, and hence not do anything. | ||
95 | #ifdef _WIN32 | ||
96 | #define CHECK_WIN_FLAG_OR_ZERO(flag) flag | ||
97 | #define CHECK_NIX_FLAG_OR_ZERO(flag) 0 | ||
98 | #else | ||
99 | #define CHECK_WIN_FLAG_OR_ZERO(flag) 0 | ||
100 | #define CHECK_NIX_FLAG_OR_ZERO(flag) flag | ||
101 | #endif | ||
102 | |||
103 | // Export Windows constants to Lua | ||
104 | static const struct ls_RegConst win_console_in_flags[] = { | ||
105 | // Console Input Flags | ||
106 | {"CIF_ECHO_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_ECHO_INPUT)}, | ||
107 | {"CIF_INSERT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_INSERT_MODE)}, | ||
108 | {"CIF_LINE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LINE_INPUT)}, | ||
109 | {"CIF_MOUSE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_MOUSE_INPUT)}, | ||
110 | {"CIF_PROCESSED_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_INPUT)}, | ||
111 | {"CIF_QUICK_EDIT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_QUICK_EDIT_MODE)}, | ||
112 | {"CIF_WINDOW_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WINDOW_INPUT)}, | ||
113 | {"CIF_VIRTUAL_TERMINAL_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_INPUT)}, | ||
114 | {"CIF_EXTENDED_FLAGS", CHECK_WIN_FLAG_OR_ZERO(ENABLE_EXTENDED_FLAGS)}, | ||
115 | {"CIF_AUTO_POSITION", CHECK_WIN_FLAG_OR_ZERO(ENABLE_AUTO_POSITION)}, | ||
116 | {NULL, 0} | ||
117 | }; | ||
118 | |||
119 | static const struct ls_RegConst win_console_out_flags[] = { | ||
120 | // Console Output Flags | ||
121 | {"COF_PROCESSED_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_OUTPUT)}, | ||
122 | {"COF_WRAP_AT_EOL_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WRAP_AT_EOL_OUTPUT)}, | ||
123 | {"COF_VIRTUAL_TERMINAL_PROCESSING", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_PROCESSING)}, | ||
124 | {"COF_DISABLE_NEWLINE_AUTO_RETURN", CHECK_WIN_FLAG_OR_ZERO(DISABLE_NEWLINE_AUTO_RETURN)}, | ||
125 | {"COF_ENABLE_LVB_GRID_WORLDWIDE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LVB_GRID_WORLDWIDE)}, | ||
126 | {NULL, 0} | ||
127 | }; | ||
128 | |||
129 | |||
130 | // Export Unix constants to Lua | ||
131 | static const struct ls_RegConst nix_tcsetattr_actions[] = { | ||
132 | // The optional actions for tcsetattr | ||
133 | {"TCSANOW", CHECK_NIX_FLAG_OR_ZERO(TCSANOW)}, | ||
134 | {"TCSADRAIN", CHECK_NIX_FLAG_OR_ZERO(TCSADRAIN)}, | ||
135 | {"TCSAFLUSH", CHECK_NIX_FLAG_OR_ZERO(TCSAFLUSH)}, | ||
136 | {NULL, 0} | ||
137 | }; | ||
138 | |||
139 | static const struct ls_RegConst nix_console_i_flags[] = { | ||
140 | // Input flags (c_iflag) | ||
141 | {"I_IGNBRK", CHECK_NIX_FLAG_OR_ZERO(IGNBRK)}, | ||
142 | {"I_BRKINT", CHECK_NIX_FLAG_OR_ZERO(BRKINT)}, | ||
143 | {"I_IGNPAR", CHECK_NIX_FLAG_OR_ZERO(IGNPAR)}, | ||
144 | {"I_PARMRK", CHECK_NIX_FLAG_OR_ZERO(PARMRK)}, | ||
145 | {"I_INPCK", CHECK_NIX_FLAG_OR_ZERO(INPCK)}, | ||
146 | {"I_ISTRIP", CHECK_NIX_FLAG_OR_ZERO(ISTRIP)}, | ||
147 | {"I_INLCR", CHECK_NIX_FLAG_OR_ZERO(INLCR)}, | ||
148 | {"I_IGNCR", CHECK_NIX_FLAG_OR_ZERO(IGNCR)}, | ||
149 | {"I_ICRNL", CHECK_NIX_FLAG_OR_ZERO(ICRNL)}, | ||
150 | #ifndef __APPLE__ | ||
151 | {"I_IUCLC", CHECK_NIX_FLAG_OR_ZERO(IUCLC)}, // Might not be available on all systems | ||
152 | #else | ||
153 | {"I_IUCLC", 0}, | ||
154 | #endif | ||
155 | {"I_IXON", CHECK_NIX_FLAG_OR_ZERO(IXON)}, | ||
156 | {"I_IXANY", CHECK_NIX_FLAG_OR_ZERO(IXANY)}, | ||
157 | {"I_IXOFF", CHECK_NIX_FLAG_OR_ZERO(IXOFF)}, | ||
158 | {"I_IMAXBEL", CHECK_NIX_FLAG_OR_ZERO(IMAXBEL)}, | ||
159 | {NULL, 0} | ||
30 | }; | 160 | }; |
31 | 161 | ||
162 | static const struct ls_RegConst nix_console_o_flags[] = { | ||
163 | // Output flags (c_oflag) | ||
164 | {"O_OPOST", CHECK_NIX_FLAG_OR_ZERO(OPOST)}, | ||
165 | #ifndef __APPLE__ | ||
166 | {"O_OLCUC", CHECK_NIX_FLAG_OR_ZERO(OLCUC)}, // Might not be available on all systems | ||
167 | #else | ||
168 | {"O_OLCUC", 0}, | ||
169 | #endif | ||
170 | {"O_ONLCR", CHECK_NIX_FLAG_OR_ZERO(ONLCR)}, | ||
171 | {"O_OCRNL", CHECK_NIX_FLAG_OR_ZERO(OCRNL)}, | ||
172 | {"O_ONOCR", CHECK_NIX_FLAG_OR_ZERO(ONOCR)}, | ||
173 | {"O_ONLRET", CHECK_NIX_FLAG_OR_ZERO(ONLRET)}, | ||
174 | {"O_OFILL", CHECK_NIX_FLAG_OR_ZERO(OFILL)}, | ||
175 | {"O_OFDEL", CHECK_NIX_FLAG_OR_ZERO(OFDEL)}, | ||
176 | {"O_NLDLY", CHECK_NIX_FLAG_OR_ZERO(NLDLY)}, | ||
177 | {"O_CRDLY", CHECK_NIX_FLAG_OR_ZERO(CRDLY)}, | ||
178 | {"O_TABDLY", CHECK_NIX_FLAG_OR_ZERO(TABDLY)}, | ||
179 | {"O_BSDLY", CHECK_NIX_FLAG_OR_ZERO(BSDLY)}, | ||
180 | {"O_VTDLY", CHECK_NIX_FLAG_OR_ZERO(VTDLY)}, | ||
181 | {"O_FFDLY", CHECK_NIX_FLAG_OR_ZERO(FFDLY)}, | ||
182 | {NULL, 0} | ||
183 | }; | ||
184 | |||
185 | static const struct ls_RegConst nix_console_l_flags[] = { | ||
186 | // Local flags (c_lflag) | ||
187 | {"L_ISIG", CHECK_NIX_FLAG_OR_ZERO(ISIG)}, | ||
188 | {"L_ICANON", CHECK_NIX_FLAG_OR_ZERO(ICANON)}, | ||
189 | #ifndef __APPLE__ | ||
190 | {"L_XCASE", CHECK_NIX_FLAG_OR_ZERO(XCASE)}, // Might not be available on all systems | ||
191 | #else | ||
192 | {"L_XCASE", 0}, | ||
193 | #endif | ||
194 | {"L_ECHO", CHECK_NIX_FLAG_OR_ZERO(ECHO)}, | ||
195 | {"L_ECHOE", CHECK_NIX_FLAG_OR_ZERO(ECHOE)}, | ||
196 | {"L_ECHOK", CHECK_NIX_FLAG_OR_ZERO(ECHOK)}, | ||
197 | {"L_ECHONL", CHECK_NIX_FLAG_OR_ZERO(ECHONL)}, | ||
198 | {"L_NOFLSH", CHECK_NIX_FLAG_OR_ZERO(NOFLSH)}, | ||
199 | {"L_TOSTOP", CHECK_NIX_FLAG_OR_ZERO(TOSTOP)}, | ||
200 | {"L_ECHOCTL", CHECK_NIX_FLAG_OR_ZERO(ECHOCTL)}, // Might not be available on all systems | ||
201 | {"L_ECHOPRT", CHECK_NIX_FLAG_OR_ZERO(ECHOPRT)}, // Might not be available on all systems | ||
202 | {"L_ECHOKE", CHECK_NIX_FLAG_OR_ZERO(ECHOKE)}, // Might not be available on all systems | ||
203 | {"L_FLUSHO", CHECK_NIX_FLAG_OR_ZERO(FLUSHO)}, | ||
204 | {"L_PENDIN", CHECK_NIX_FLAG_OR_ZERO(PENDIN)}, | ||
205 | {"L_IEXTEN", CHECK_NIX_FLAG_OR_ZERO(IEXTEN)}, | ||
206 | {NULL, 0} | ||
207 | }; | ||
208 | |||
209 | static DWORD win_valid_in_flags = 0; | ||
210 | static DWORD win_valid_out_flags = 0; | ||
211 | static DWORD nix_valid_i_flags = 0; | ||
212 | static DWORD nix_valid_o_flags = 0; | ||
213 | static DWORD nix_valid_l_flags = 0; | ||
214 | static void initialize_valid_flags() | ||
215 | { | ||
216 | win_valid_in_flags = 0; | ||
217 | for (int i = 0; win_console_in_flags[i].name != NULL; i++) | ||
218 | { | ||
219 | win_valid_in_flags |= win_console_in_flags[i].value; | ||
220 | } | ||
221 | win_valid_out_flags = 0; | ||
222 | for (int i = 0; win_console_out_flags[i].name != NULL; i++) | ||
223 | { | ||
224 | win_valid_out_flags |= win_console_out_flags[i].value; | ||
225 | } | ||
226 | nix_valid_i_flags = 0; | ||
227 | for (int i = 0; nix_console_i_flags[i].name != NULL; i++) | ||
228 | { | ||
229 | nix_valid_i_flags |= nix_console_i_flags[i].value; | ||
230 | } | ||
231 | nix_valid_o_flags = 0; | ||
232 | for (int i = 0; nix_console_o_flags[i].name != NULL; i++) | ||
233 | { | ||
234 | nix_valid_o_flags |= nix_console_o_flags[i].value; | ||
235 | } | ||
236 | nix_valid_l_flags = 0; | ||
237 | for (int i = 0; nix_console_l_flags[i].name != NULL; i++) | ||
238 | { | ||
239 | nix_valid_l_flags |= nix_console_l_flags[i].value; | ||
240 | } | ||
241 | } | ||
242 | |||
243 | #ifdef _WIN32 | ||
244 | // first item on the stack should be io.stdin, io.stderr, or io.stdout, second item | ||
245 | // should be the flags to validate. | ||
246 | // If it returns NULL, then it leaves nil+err on the stack | ||
247 | static HANDLE get_console_handle(lua_State *L, int flags_optional) | ||
248 | { | ||
249 | if (lua_gettop(L) < 1) { | ||
250 | luaL_argerror(L, 1, "expected file handle"); | ||
251 | } | ||
252 | |||
253 | HANDLE handle; | ||
254 | DWORD valid; | ||
255 | FILE *file = *(FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE); | ||
256 | if (file == stdin && file != NULL) { | ||
257 | handle = GetStdHandle(STD_INPUT_HANDLE); | ||
258 | valid = win_valid_in_flags; | ||
259 | |||
260 | } else if (file == stdout && file != NULL) { | ||
261 | handle = GetStdHandle(STD_OUTPUT_HANDLE); | ||
262 | valid = win_valid_out_flags; | ||
263 | |||
264 | } else if (file == stderr && file != NULL) { | ||
265 | handle = GetStdHandle(STD_ERROR_HANDLE); | ||
266 | valid = win_valid_out_flags; | ||
267 | |||
268 | } else { | ||
269 | luaL_argerror(L, 1, "invalid file handle"); // does not return | ||
270 | } | ||
271 | |||
272 | if (handle == INVALID_HANDLE_VALUE) { | ||
273 | termFormatError(L, GetLastError(), "failed to retrieve std handle"); | ||
274 | lua_error(L); // does not return | ||
275 | } | ||
276 | |||
277 | if (handle == NULL) { | ||
278 | lua_pushnil(L); | ||
279 | lua_pushliteral(L, "failed to get console handle"); | ||
280 | return NULL; | ||
281 | } | ||
282 | |||
283 | if (flags_optional && lua_gettop(L) < 2) { | ||
284 | return handle; | ||
285 | } | ||
286 | |||
287 | if (lua_gettop(L) < 2) { | ||
288 | luaL_argerror(L, 2, "expected flags"); | ||
289 | } | ||
290 | |||
291 | LSBF_BITFLAG flags = lsbf_checkbitflags(L, 2); | ||
292 | if ((flags & ~valid) != 0) { | ||
293 | luaL_argerror(L, 2, "invalid flags"); | ||
294 | } | ||
295 | |||
296 | return handle; | ||
297 | } | ||
298 | #else | ||
299 | // first item on the stack should be io.stdin, io.stderr, or io.stdout. Throws a | ||
300 | // Lua error if the file is not one of these. | ||
301 | static int get_console_handle(lua_State *L) | ||
302 | { | ||
303 | FILE **file = (FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE); | ||
304 | if (file == NULL || *file == NULL) { | ||
305 | return luaL_argerror(L, 1, "expected file handle"); // call doesn't return | ||
306 | } | ||
307 | |||
308 | // Check if the file is stdin, stdout, or stderr | ||
309 | if (*file == stdin || *file == stdout || *file == stderr) { | ||
310 | // Push the file descriptor onto the Lua stack | ||
311 | return fileno(*file); | ||
312 | } | ||
313 | |||
314 | return luaL_argerror(L, 1, "invalid file handle"); // does not return | ||
315 | } | ||
316 | #endif | ||
317 | |||
318 | |||
319 | |||
320 | /*** | ||
321 | Sets the console flags (Windows). | ||
322 | The `CIF_` and `COF_` constants are available on the module table. Where `CIF` are the | ||
323 | input flags (for use with `io.stdin`) and `COF` are the output flags (for use with | ||
324 | `io.stdout`/`io.stderr`). | ||
325 | |||
326 | To see flag status and constant names check `listconsoleflags`. | ||
327 | |||
328 | Note: not all combinations of flags are allowed, as some are mutually exclusive or mutually required. | ||
329 | See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode) | ||
330 | @function setconsoleflags | ||
331 | @tparam file file the file-handle to set the flags on | ||
332 | @tparam bitflags bitflags the flags to set/unset | ||
333 | @treturn[1] boolean `true` on success | ||
334 | @treturn[2] nil | ||
335 | @treturn[2] string error message | ||
336 | @usage | ||
337 | local system = require('system') | ||
338 | system.listconsoleflags(io.stdout) -- List all the available flags and their current status | ||
339 | |||
340 | local flags = system.getconsoleflags(io.stdout) | ||
341 | assert(system.setconsoleflags(io.stdout, | ||
342 | flags + system.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
343 | |||
344 | system.listconsoleflags(io.stdout) -- List again to check the differences | ||
345 | */ | ||
346 | static int lst_setconsoleflags(lua_State *L) | ||
347 | { | ||
348 | #ifdef _WIN32 | ||
349 | HANDLE console_handle = get_console_handle(L, 0); | ||
350 | if (console_handle == NULL) { | ||
351 | return 2; // error message is already on the stack | ||
352 | } | ||
353 | LSBF_BITFLAG new_console_mode = lsbf_checkbitflags(L, 2); | ||
354 | |||
355 | DWORD prev_console_mode; | ||
356 | if (GetConsoleMode(console_handle, &prev_console_mode) == 0) | ||
357 | { | ||
358 | termFormatError(L, GetLastError(), "failed to get console mode"); | ||
359 | return 2; | ||
360 | } | ||
361 | |||
362 | int success = SetConsoleMode(console_handle, new_console_mode) != 0; | ||
363 | if (!success) | ||
364 | { | ||
365 | termFormatError(L, GetLastError(), "failed to set console mode"); | ||
366 | return 2; | ||
367 | } | ||
368 | |||
369 | #endif | ||
370 | lua_pushboolean(L, 1); | ||
371 | return 1; | ||
372 | } | ||
373 | |||
374 | |||
375 | |||
376 | /*** | ||
377 | Gets console flags (Windows). | ||
378 | @function getconsoleflags | ||
379 | @tparam file file the file-handle to get the flags from. | ||
380 | @treturn[1] bitflags the current console flags. | ||
381 | @treturn[2] nil | ||
382 | @treturn[2] string error message | ||
383 | @usage | ||
384 | local system = require('system') | ||
385 | |||
386 | local flags = system.getconsoleflags(io.stdout) | ||
387 | print("Current stdout flags:", tostring(flags)) | ||
388 | |||
389 | if flags:has(system.COF_VIRTUAL_TERMINAL_PROCESSING + system.COF_PROCESSED_OUTPUT) then | ||
390 | print("Both flags are set") | ||
391 | else | ||
392 | print("At least one flag is not set") | ||
393 | end | ||
394 | */ | ||
395 | static int lst_getconsoleflags(lua_State *L) | ||
396 | { | ||
397 | DWORD console_mode = 0; | ||
398 | |||
399 | #ifdef _WIN32 | ||
400 | HANDLE console_handle = get_console_handle(L, 1); | ||
401 | if (console_handle == NULL) { | ||
402 | return 2; // error message is already on the stack | ||
403 | } | ||
404 | |||
405 | if (GetConsoleMode(console_handle, &console_mode) == 0) | ||
406 | { | ||
407 | lua_pushnil(L); | ||
408 | lua_pushliteral(L, "failed to get console mode"); | ||
409 | return 2; | ||
410 | } | ||
411 | |||
412 | #endif | ||
413 | lsbf_pushbitflags(L, console_mode); | ||
414 | return 1; | ||
415 | } | ||
416 | |||
417 | |||
418 | |||
419 | /*------------------------------------------------------------------------- | ||
420 | * Unix tcgetattr/tcsetattr functions | ||
421 | *-------------------------------------------------------------------------*/ | ||
422 | // Code modified from the LuaPosix library by Gary V. Vaughan | ||
423 | // see https://github.com/luaposix/luaposix | ||
424 | |||
425 | /*** | ||
426 | Get termios state. | ||
427 | The terminal attributes is a table with the following fields: | ||
428 | |||
429 | - `iflag` input flags | ||
430 | - `oflag` output flags | ||
431 | - `cflag` control flags | ||
432 | - `lflag` local flags | ||
433 | - `ispeed` input speed | ||
434 | - `ospeed` output speed | ||
435 | - `cc` control characters | ||
436 | |||
437 | @function tcgetattr | ||
438 | @tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` | ||
439 | @treturn[1] termios terminal attributes, if successful. On Windows the bitflags are all 0, and the `cc` table is empty. | ||
440 | @treturn[2] nil | ||
441 | @treturn[2] string error message | ||
442 | @treturn[2] int errnum | ||
443 | @return error message if failed | ||
444 | @usage | ||
445 | local system = require('system') | ||
446 | |||
447 | local status = assert(tcgetattr(io.stdin)) | ||
448 | if status.iflag:has(system.I_IGNBRK) then | ||
449 | print("Ignoring break condition") | ||
450 | end | ||
451 | */ | ||
452 | static int lst_tcgetattr(lua_State *L) | ||
453 | { | ||
454 | #ifndef _WIN32 | ||
455 | int r, i; | ||
456 | struct termios t; | ||
457 | int fd = get_console_handle(L); | ||
458 | |||
459 | r = tcgetattr(fd, &t); | ||
460 | if (r == -1) return pusherror(L, NULL); | ||
461 | |||
462 | lua_newtable(L); | ||
463 | lsbf_pushbitflags(L, t.c_iflag); | ||
464 | lua_setfield(L, -2, "iflag"); | ||
465 | |||
466 | lsbf_pushbitflags(L, t.c_oflag); | ||
467 | lua_setfield(L, -2, "oflag"); | ||
468 | |||
469 | lsbf_pushbitflags(L, t.c_lflag); | ||
470 | lua_setfield(L, -2, "lflag"); | ||
471 | |||
472 | lsbf_pushbitflags(L, t.c_cflag); | ||
473 | lua_setfield(L, -2, "cflag"); | ||
474 | |||
475 | lua_pushinteger(L, cfgetispeed(&t)); | ||
476 | lua_setfield(L, -2, "ispeed"); | ||
477 | |||
478 | lua_pushinteger(L, cfgetospeed(&t)); | ||
479 | lua_setfield(L, -2, "ospeed"); | ||
480 | |||
481 | lua_newtable(L); | ||
482 | for (i=0; i<NCCS; i++) | ||
483 | { | ||
484 | lua_pushinteger(L, i); | ||
485 | lua_pushinteger(L, t.c_cc[i]); | ||
486 | lua_settable(L, -3); | ||
487 | } | ||
488 | lua_setfield(L, -2, "cc"); | ||
489 | |||
490 | #else | ||
491 | lua_newtable(L); | ||
492 | lsbf_pushbitflags(L, 0); | ||
493 | lua_setfield(L, -2, "iflag"); | ||
494 | lsbf_pushbitflags(L, 0); | ||
495 | lua_setfield(L, -2, "oflag"); | ||
496 | lsbf_pushbitflags(L, 0); | ||
497 | lua_setfield(L, -2, "lflag"); | ||
498 | lsbf_pushbitflags(L, 0); | ||
499 | lua_setfield(L, -2, "cflag"); | ||
500 | lua_pushinteger(L, 0); | ||
501 | lua_setfield(L, -2, "ispeed"); | ||
502 | lua_pushinteger(L, 0); | ||
503 | lua_setfield(L, -2, "ospeed"); | ||
504 | lua_newtable(L); | ||
505 | lua_setfield(L, -2, "cc"); | ||
506 | |||
507 | #endif | ||
508 | return 1; | ||
509 | } | ||
510 | |||
511 | |||
512 | |||
513 | /*** | ||
514 | Set termios state. | ||
515 | This function will set the flags as given. | ||
516 | |||
517 | The `I_`, `O_`, and `L_` constants are available on the module table. They are the respective | ||
518 | flags for the `iflags`, `oflags`, and `lflags` bitmasks. | ||
519 | |||
520 | To see flag status and constant names check `listtermflags`. For their meaning check | ||
521 | [the manpage](https://www.man7.org/linux/man-pages/man3/termios.3.html). | ||
522 | |||
523 | _Note_: not all combinations of flags are allowed, as some are mutually exclusive or mutually required. | ||
524 | See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode) | ||
525 | |||
526 | _Note_: only `iflag`, `oflag`, and `lflag` are supported at the moment. The other fields are ignored. | ||
527 | @function tcsetattr | ||
528 | @tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` | ||
529 | @int actions one of `TCSANOW`, `TCSADRAIN`, `TCSAFLUSH` | ||
530 | @tparam table termios a table with bitflag fields: | ||
531 | @tparam[opt] bitflags termios.iflag if given will set the input flags | ||
532 | @tparam[opt] bitflags termios.oflag if given will set the output flags | ||
533 | @tparam[opt] bitflags termios.lflag if given will set the local flags | ||
534 | @treturn[1] bool `true`, if successful. Always returns `true` on Windows. | ||
535 | @return[2] nil | ||
536 | @treturn[2] string error message | ||
537 | @treturn[2] int errnum | ||
538 | @usage | ||
539 | local system = require('system') | ||
540 | |||
541 | local status = assert(tcgetattr(io.stdin)) | ||
542 | if not status.lflag:has(system.L_ECHO) then | ||
543 | -- if echo is off, turn echoing newlines on | ||
544 | tcsetattr(io.stdin, system.TCSANOW, { lflag = status.lflag + system.L_ECHONL })) | ||
545 | end | ||
546 | */ | ||
547 | static int lst_tcsetattr(lua_State *L) | ||
548 | { | ||
549 | #ifndef _WIN32 | ||
550 | struct termios t; | ||
551 | int r, i; | ||
552 | int fd = get_console_handle(L); // first is the console handle | ||
553 | int act = luaL_checkinteger(L, 2); // second is the action to take | ||
554 | luaL_checktype(L, 3, LUA_TTABLE); // third is the termios table with fields | ||
555 | |||
556 | r = tcgetattr(fd, &t); | ||
557 | if (r == -1) return pusherror(L, NULL); | ||
558 | |||
559 | lua_getfield(L, 3, "iflag"); | ||
560 | if (!lua_isnil(L, -1)) { | ||
561 | t.c_iflag = lsbf_checkbitflags(L, -1); | ||
562 | } | ||
563 | lua_pop(L, 1); | ||
564 | |||
565 | lua_getfield(L, 3, "oflag"); | ||
566 | if (!lua_isnil(L, -1)) { | ||
567 | t.c_oflag = lsbf_checkbitflags(L, -1); | ||
568 | } | ||
569 | lua_pop(L, 1); | ||
570 | |||
571 | lua_getfield(L, 3, "lflag"); | ||
572 | if (!lua_isnil(L, -1)) { | ||
573 | t.c_lflag = lsbf_checkbitflags(L, -1); | ||
574 | } | ||
575 | lua_pop(L, 1); | ||
576 | |||
577 | // Skipping the others for now | ||
578 | |||
579 | // lua_getfield(L, 3, "cflag"); t.c_cflag = optint(L, -1, 0); lua_pop(L, 1); | ||
580 | // lua_getfield(L, 3, "ispeed"); cfsetispeed( &t, optint(L, -1, B0) ); lua_pop(L, 1); | ||
581 | // lua_getfield(L, 3, "ospeed"); cfsetospeed( &t, optint(L, -1, B0) ); lua_pop(L, 1); | ||
582 | |||
583 | // lua_getfield(L, 3, "cc"); | ||
584 | // for (i=0; i<NCCS; i++) | ||
585 | // { | ||
586 | // lua_pushinteger(L, i); | ||
587 | // lua_gettable(L, -2); | ||
588 | // t.c_cc[i] = optint(L, -1, 0); | ||
589 | // lua_pop(L, 1); | ||
590 | // } | ||
591 | |||
592 | r = tcsetattr(fd, act, &t); | ||
593 | if (r == -1) return pusherror(L, NULL); | ||
594 | #endif | ||
595 | |||
596 | lua_pushboolean(L, 1); | ||
597 | return 1; | ||
598 | } | ||
599 | |||
600 | |||
601 | |||
602 | /*** | ||
603 | Enables or disables non-blocking mode for a file (Posix). | ||
604 | @function setnonblock | ||
605 | @tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` | ||
606 | @tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it. | ||
607 | @treturn[1] bool `true`, if successful | ||
608 | @treturn[2] nil | ||
609 | @treturn[2] string error message | ||
610 | @treturn[2] int errnum | ||
611 | @see getnonblock | ||
612 | @usage | ||
613 | local sys = require('system') | ||
614 | |||
615 | -- set io.stdin to non-blocking mode | ||
616 | local old_setting = sys.getnonblock(io.stdin) | ||
617 | sys.setnonblock(io.stdin, true) | ||
618 | |||
619 | -- do stuff | ||
620 | |||
621 | -- restore old setting | ||
622 | sys.setnonblock(io.stdin, old_setting) | ||
623 | */ | ||
624 | static int lst_setnonblock(lua_State *L) | ||
625 | { | ||
626 | #ifndef _WIN32 | ||
627 | |||
628 | int fd = get_console_handle(L); | ||
629 | |||
630 | int flags = fcntl(fd, F_GETFL, 0); | ||
631 | if (flags == -1) { | ||
632 | return pusherror(L, "Error getting handle flags: "); | ||
633 | } | ||
634 | if (lua_toboolean(L, 2)) { | ||
635 | // truthy: set non-blocking | ||
636 | flags |= O_NONBLOCK; | ||
637 | } else { | ||
638 | // falsy: set disable non-blocking | ||
639 | flags &= ~O_NONBLOCK; | ||
640 | } | ||
641 | if (fcntl(fd, F_SETFL, flags) == -1) { | ||
642 | return pusherror(L, "Error changing O_NONBLOCK: "); | ||
643 | } | ||
644 | |||
645 | #endif | ||
646 | |||
647 | lua_pushboolean(L, 1); | ||
648 | return 1; | ||
649 | } | ||
650 | |||
651 | |||
652 | |||
653 | /*** | ||
654 | Gets non-blocking mode status for a file (Posix). | ||
655 | @function getnonblock | ||
656 | @tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` | ||
657 | @treturn[1] bool `true` if set to non-blocking, `false` if not. Always returns `false` on Windows. | ||
658 | @treturn[2] nil | ||
659 | @treturn[2] string error message | ||
660 | @treturn[2] int errnum | ||
661 | */ | ||
662 | static int lst_getnonblock(lua_State *L) | ||
663 | { | ||
664 | #ifndef _WIN32 | ||
665 | |||
666 | int fd = get_console_handle(L); | ||
667 | |||
668 | // Set O_NONBLOCK | ||
669 | int flags = fcntl(fd, F_GETFL, 0); | ||
670 | if (flags == -1) { | ||
671 | return pusherror(L, "Error getting handle flags: "); | ||
672 | } | ||
673 | if (flags & O_NONBLOCK) { | ||
674 | lua_pushboolean(L, 1); | ||
675 | } else { | ||
676 | lua_pushboolean(L, 0); | ||
677 | } | ||
678 | |||
679 | #else | ||
680 | lua_pushboolean(L, 0); | ||
681 | |||
682 | #endif | ||
683 | return 1; | ||
684 | } | ||
685 | |||
686 | |||
687 | |||
688 | /*------------------------------------------------------------------------- | ||
689 | * Reading keyboard input | ||
690 | *-------------------------------------------------------------------------*/ | ||
691 | |||
692 | /*** | ||
693 | Reads a key from the console non-blocking. | ||
694 | On Posix, `io.stdin` must be set to non-blocking mode using `setnonblock` | ||
695 | before calling this function. Otherwise it will block. | ||
696 | |||
697 | @function readkey | ||
698 | @treturn[1] integer the key code of the key that was pressed | ||
699 | @treturn[2] nil if no key was pressed | ||
700 | */ | ||
701 | static int lst_readkey(lua_State *L) { | ||
702 | #ifdef _WIN32 | ||
703 | if (_kbhit()) { | ||
704 | lua_pushinteger(L, _getch()); | ||
705 | return 1; | ||
706 | } | ||
707 | return 0; | ||
708 | |||
709 | #else | ||
710 | char ch; | ||
711 | if (read(STDIN_FILENO, &ch, 1) > 0) { | ||
712 | lua_pushinteger(L, ch); | ||
713 | return 1; | ||
714 | } | ||
715 | return 0; | ||
716 | |||
717 | #endif | ||
718 | } | ||
719 | |||
720 | /*** | ||
721 | Checks if a key has been pressed without reading it. | ||
722 | On Posix, `io.stdin` must be set to non-blocking mode using `setnonblock` | ||
723 | before calling this function. Otherwise it will block. | ||
724 | |||
725 | @function keypressed | ||
726 | @treturn boolean true if a key has been pressed, nil if not. | ||
727 | */ | ||
728 | static int lst_keypressed(lua_State *L) { | ||
729 | #ifdef _WIN32 | ||
730 | if (kbhit()) { | ||
731 | lua_pushboolean(L, 1); | ||
732 | return 1; | ||
733 | } | ||
734 | return 0; | ||
735 | |||
736 | #else | ||
737 | char ch; | ||
738 | if (read(STDIN_FILENO, &ch, 1) > 0) { | ||
739 | // key was read, push back to stdin | ||
740 | ungetc(ch, stdin); | ||
741 | lua_pushboolean(L, 1); | ||
742 | return 1; | ||
743 | } | ||
744 | return 0; | ||
745 | |||
746 | #endif | ||
747 | } | ||
748 | |||
749 | /*------------------------------------------------------------------------- | ||
750 | * Retrieve terminal size | ||
751 | *-------------------------------------------------------------------------*/ | ||
752 | |||
753 | |||
754 | /*** | ||
755 | Get the size of the terminal in columns and rows. | ||
756 | @function termsize | ||
757 | @treturn[1] int the number of columns | ||
758 | @treturn[1] int the number of rows | ||
759 | @treturn[2] nil | ||
760 | @treturn[2] string error message | ||
761 | */ | ||
762 | static int lst_termsize(lua_State *L) { | ||
763 | int columns, rows; | ||
764 | |||
765 | #ifdef _WIN32 | ||
766 | CONSOLE_SCREEN_BUFFER_INFO csbi; | ||
767 | if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { | ||
768 | termFormatError(L, GetLastError(), "Failed to get terminal size."); | ||
769 | return 2; | ||
770 | } | ||
771 | columns = csbi.srWindow.Right - csbi.srWindow.Left + 1; | ||
772 | rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; | ||
773 | |||
774 | #else | ||
775 | struct winsize ws; | ||
776 | if (ioctl(1, TIOCGWINSZ, &ws) == -1) { | ||
777 | return pusherror(L, "Failed to get terminal size."); | ||
778 | } | ||
779 | columns = ws.ws_col; | ||
780 | rows = ws.ws_row; | ||
781 | |||
782 | #endif | ||
783 | lua_pushinteger(L, columns); | ||
784 | lua_pushinteger(L, rows); | ||
785 | return 2; | ||
786 | } | ||
787 | |||
788 | |||
789 | |||
32 | /*------------------------------------------------------------------------- | 790 | /*------------------------------------------------------------------------- |
33 | * Initializes module | 791 | * Initializes module |
34 | *-------------------------------------------------------------------------*/ | 792 | *-------------------------------------------------------------------------*/ |
793 | |||
794 | static luaL_Reg func[] = { | ||
795 | { "isatty", lst_isatty }, | ||
796 | { "getconsoleflags", lst_getconsoleflags }, | ||
797 | { "setconsoleflags", lst_setconsoleflags }, | ||
798 | { "tcgetattr", lst_tcgetattr }, | ||
799 | { "tcsetattr", lst_tcsetattr }, | ||
800 | { "getnonblock", lst_setnonblock }, | ||
801 | { "setnonblock", lst_setnonblock }, | ||
802 | { "readkey", lst_readkey }, | ||
803 | { "keypressed", lst_keypressed }, | ||
804 | { "termsize", lst_termsize }, | ||
805 | { NULL, NULL } | ||
806 | }; | ||
807 | |||
808 | |||
809 | |||
35 | void term_open(lua_State *L) { | 810 | void term_open(lua_State *L) { |
811 | // set up constants and export the constants in module table | ||
812 | initialize_valid_flags(); | ||
813 | // Windows flags | ||
814 | for (int i = 0; win_console_in_flags[i].name != NULL; i++) | ||
815 | { | ||
816 | lsbf_pushbitflags(L, win_console_in_flags[i].value); | ||
817 | lua_setfield(L, -2, win_console_in_flags[i].name); | ||
818 | } | ||
819 | for (int i = 0; win_console_out_flags[i].name != NULL; i++) | ||
820 | { | ||
821 | lsbf_pushbitflags(L, win_console_out_flags[i].value); | ||
822 | lua_setfield(L, -2, win_console_out_flags[i].name); | ||
823 | } | ||
824 | // Unix flags | ||
825 | for (int i = 0; nix_console_i_flags[i].name != NULL; i++) | ||
826 | { | ||
827 | lsbf_pushbitflags(L, nix_console_i_flags[i].value); | ||
828 | lua_setfield(L, -2, nix_console_i_flags[i].name); | ||
829 | } | ||
830 | for (int i = 0; nix_console_o_flags[i].name != NULL; i++) | ||
831 | { | ||
832 | lsbf_pushbitflags(L, nix_console_o_flags[i].value); | ||
833 | lua_setfield(L, -2, nix_console_o_flags[i].name); | ||
834 | } | ||
835 | for (int i = 0; nix_console_l_flags[i].name != NULL; i++) | ||
836 | { | ||
837 | lsbf_pushbitflags(L, nix_console_l_flags[i].value); | ||
838 | lua_setfield(L, -2, nix_console_l_flags[i].name); | ||
839 | } | ||
840 | // Unix tcsetattr actions | ||
841 | for (int i = 0; nix_tcsetattr_actions[i].name != NULL; i++) | ||
842 | { | ||
843 | lua_pushinteger(L, nix_tcsetattr_actions[i].value); | ||
844 | lua_setfield(L, -2, nix_tcsetattr_actions[i].name); | ||
845 | } | ||
846 | |||
847 | // export functions | ||
36 | luaL_setfuncs(L, func, 0); | 848 | luaL_setfuncs(L, func, 0); |
37 | } | 849 | } |