aboutsummaryrefslogtreecommitdiff
path: root/src/term.c
diff options
context:
space:
mode:
authorThijs Schreijer <thijs@thijsschreijer.nl>2024-06-20 22:43:06 +0200
committerGitHub <noreply@github.com>2024-06-20 22:43:06 +0200
commitc1a64c1b75f97cef97965b3bd9a941564a6270bd (patch)
treeb9a92dff6462abd5859c3c76f19748fad5d6c025 /src/term.c
parent47c24eed0191f8f72646be63dee94ac2b35eb062 (diff)
parentb87e6d6d762ee823e81dd7a8984f330eb4018fd8 (diff)
downloadluasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.tar.gz
luasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.tar.bz2
luasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.zip
Merge pull request #21 from lunarmodules/terminal
Diffstat (limited to 'src/term.c')
-rw-r--r--src/term.c1133
1 files changed, 1127 insertions, 6 deletions
diff --git a/src/term.c b/src/term.c
index 2adb1e9..d8cc38e 100644
--- a/src/term.c
+++ b/src/term.c
@@ -1,37 +1,1158 @@
1/// @submodule system 1/// @module system
2
3/// Terminal.
4// Unix: see https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/
5//
6// Windows: see https://learn.microsoft.com/en-us/windows/console/console-reference
7// @section terminal
8
2#include <lua.h> 9#include <lua.h>
3#include <lauxlib.h> 10#include <lauxlib.h>
4#include <lualib.h> 11#include <lualib.h>
5#include "compat.h" 12#include "compat.h"
13#include "bitflags.h"
6 14
7#ifndef _MSC_VER 15#ifndef _MSC_VER
8# include <unistd.h> 16# include <unistd.h>
9#endif 17#endif
10 18
19#ifdef _WIN32
20# include <windows.h>
21# include <locale.h>
22#else
23# include <termios.h>
24# include <string.h>
25# include <errno.h>
26# include <fcntl.h>
27# include <sys/ioctl.h>
28# include <unistd.h>
29# include <wchar.h>
30# include <locale.h>
31#endif
32
33
34// Windows does not have a wcwidth function, so we use compatibilty code from
35// http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c by Markus Kuhn
36#include "wcwidth.h"
37
38
39#ifdef _WIN32
40// after an error is returned, GetLastError() result can be passed to this function to get a string
41// representation of the error on the stack.
42// result will be nil+error on the stack, always 2 results.
43static void termFormatError(lua_State *L, DWORD errorCode, const char* prefix) {
44//static void FormatErrorAndReturn(lua_State *L, DWORD errorCode, const char* prefix) {
45 LPSTR messageBuffer = NULL;
46 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
47 NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
48
49 lua_pushnil(L);
50 if (messageBuffer) {
51 if (prefix) {
52 lua_pushfstring(L, "%s: %s", prefix, messageBuffer);
53 } else {
54 lua_pushstring(L, messageBuffer);
55 }
56 LocalFree(messageBuffer);
57 } else {
58 lua_pushfstring(L, "%sError code %d", prefix ? prefix : "", errorCode);
59 }
60}
61#else
62static int pusherror(lua_State *L, const char *info)
63{
64 lua_pushnil(L);
65 if (info==NULL)
66 lua_pushstring(L, strerror(errno));
67 else
68 lua_pushfstring(L, "%s: %s", info, strerror(errno));
69 lua_pushinteger(L, errno);
70 return 3;
71}
72#endif
11 73
12/*** 74/***
13Checks if a file-handle is a TTY. 75Checks if a file-handle is a TTY.
14 76
15@function isatty 77@function isatty
16@tparam file file the file-handle to check 78@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 79@treturn boolean true if the file is a tty
80@usage
81local system = require('system')
82if system.isatty(io.stdin) then
83 -- enable ANSI coloring etc on Windows, does nothing in Posix.
84 local flags = system.getconsoleflags(io.stdout)
85 system.setconsoleflags(io.stdout, flags + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
86end
18*/ 87*/
19static int lua_isatty(lua_State* L) { 88static int lst_isatty(lua_State* L) {
20 FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE); 89 FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE);
21 lua_pushboolean(L, isatty(fileno(*fh))); 90 lua_pushboolean(L, isatty(fileno(*fh)));
22 return 1; 91 return 1;
23} 92}
24 93
25 94
95/*-------------------------------------------------------------------------
96 * Windows Get/SetConsoleMode functions
97 *-------------------------------------------------------------------------*/
26 98
27static luaL_Reg func[] = { 99typedef struct ls_RegConst {
28 { "isatty", lua_isatty }, 100 const char *name;
29 { NULL, NULL } 101 DWORD value;
102} ls_RegConst;
103
104// Define a macro to check if a constant is defined and set it to 0 if not.
105// This is needed because some flags are not defined on all platforms. So we
106// still export the constants, but they will be all 0, and hence not do anything.
107#ifdef _WIN32
108#define CHECK_WIN_FLAG_OR_ZERO(flag) flag
109#define CHECK_NIX_FLAG_OR_ZERO(flag) 0
110#else
111#define CHECK_WIN_FLAG_OR_ZERO(flag) 0
112#define CHECK_NIX_FLAG_OR_ZERO(flag) flag
113#endif
114
115// Export Windows constants to Lua
116static const struct ls_RegConst win_console_in_flags[] = {
117 // Console Input Flags
118 {"CIF_ECHO_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_ECHO_INPUT)},
119 {"CIF_INSERT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_INSERT_MODE)},
120 {"CIF_LINE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LINE_INPUT)},
121 {"CIF_MOUSE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_MOUSE_INPUT)},
122 {"CIF_PROCESSED_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_INPUT)},
123 {"CIF_QUICK_EDIT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_QUICK_EDIT_MODE)},
124 {"CIF_WINDOW_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WINDOW_INPUT)},
125 {"CIF_VIRTUAL_TERMINAL_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_INPUT)},
126 {"CIF_EXTENDED_FLAGS", CHECK_WIN_FLAG_OR_ZERO(ENABLE_EXTENDED_FLAGS)},
127 {"CIF_AUTO_POSITION", CHECK_WIN_FLAG_OR_ZERO(ENABLE_AUTO_POSITION)},
128 {NULL, 0}
129};
130
131static const struct ls_RegConst win_console_out_flags[] = {
132 // Console Output Flags
133 {"COF_PROCESSED_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_OUTPUT)},
134 {"COF_WRAP_AT_EOL_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WRAP_AT_EOL_OUTPUT)},
135 {"COF_VIRTUAL_TERMINAL_PROCESSING", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_PROCESSING)},
136 {"COF_DISABLE_NEWLINE_AUTO_RETURN", CHECK_WIN_FLAG_OR_ZERO(DISABLE_NEWLINE_AUTO_RETURN)},
137 {"COF_ENABLE_LVB_GRID_WORLDWIDE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LVB_GRID_WORLDWIDE)},
138 {NULL, 0}
139};
140
141
142// Export Unix constants to Lua
143static const struct ls_RegConst nix_tcsetattr_actions[] = {
144 // The optional actions for tcsetattr
145 {"TCSANOW", CHECK_NIX_FLAG_OR_ZERO(TCSANOW)},
146 {"TCSADRAIN", CHECK_NIX_FLAG_OR_ZERO(TCSADRAIN)},
147 {"TCSAFLUSH", CHECK_NIX_FLAG_OR_ZERO(TCSAFLUSH)},
148 {NULL, 0}
149};
150
151static const struct ls_RegConst nix_console_i_flags[] = {
152 // Input flags (c_iflag)
153 {"I_IGNBRK", CHECK_NIX_FLAG_OR_ZERO(IGNBRK)},
154 {"I_BRKINT", CHECK_NIX_FLAG_OR_ZERO(BRKINT)},
155 {"I_IGNPAR", CHECK_NIX_FLAG_OR_ZERO(IGNPAR)},
156 {"I_PARMRK", CHECK_NIX_FLAG_OR_ZERO(PARMRK)},
157 {"I_INPCK", CHECK_NIX_FLAG_OR_ZERO(INPCK)},
158 {"I_ISTRIP", CHECK_NIX_FLAG_OR_ZERO(ISTRIP)},
159 {"I_INLCR", CHECK_NIX_FLAG_OR_ZERO(INLCR)},
160 {"I_IGNCR", CHECK_NIX_FLAG_OR_ZERO(IGNCR)},
161 {"I_ICRNL", CHECK_NIX_FLAG_OR_ZERO(ICRNL)},
162#ifndef __APPLE__
163 {"I_IUCLC", CHECK_NIX_FLAG_OR_ZERO(IUCLC)}, // Might not be available on all systems
164#else
165 {"I_IUCLC", 0},
166#endif
167 {"I_IXON", CHECK_NIX_FLAG_OR_ZERO(IXON)},
168 {"I_IXANY", CHECK_NIX_FLAG_OR_ZERO(IXANY)},
169 {"I_IXOFF", CHECK_NIX_FLAG_OR_ZERO(IXOFF)},
170 {"I_IMAXBEL", CHECK_NIX_FLAG_OR_ZERO(IMAXBEL)},
171 {NULL, 0}
30}; 172};
31 173
174static const struct ls_RegConst nix_console_o_flags[] = {
175 // Output flags (c_oflag)
176 {"O_OPOST", CHECK_NIX_FLAG_OR_ZERO(OPOST)},
177#ifndef __APPLE__
178 {"O_OLCUC", CHECK_NIX_FLAG_OR_ZERO(OLCUC)}, // Might not be available on all systems
179#else
180 {"O_OLCUC", 0},
181#endif
182 {"O_ONLCR", CHECK_NIX_FLAG_OR_ZERO(ONLCR)},
183 {"O_OCRNL", CHECK_NIX_FLAG_OR_ZERO(OCRNL)},
184 {"O_ONOCR", CHECK_NIX_FLAG_OR_ZERO(ONOCR)},
185 {"O_ONLRET", CHECK_NIX_FLAG_OR_ZERO(ONLRET)},
186 {"O_OFILL", CHECK_NIX_FLAG_OR_ZERO(OFILL)},
187 {"O_OFDEL", CHECK_NIX_FLAG_OR_ZERO(OFDEL)},
188 {"O_NLDLY", CHECK_NIX_FLAG_OR_ZERO(NLDLY)},
189 {"O_CRDLY", CHECK_NIX_FLAG_OR_ZERO(CRDLY)},
190 {"O_TABDLY", CHECK_NIX_FLAG_OR_ZERO(TABDLY)},
191 {"O_BSDLY", CHECK_NIX_FLAG_OR_ZERO(BSDLY)},
192 {"O_VTDLY", CHECK_NIX_FLAG_OR_ZERO(VTDLY)},
193 {"O_FFDLY", CHECK_NIX_FLAG_OR_ZERO(FFDLY)},
194 {NULL, 0}
195};
196
197static const struct ls_RegConst nix_console_l_flags[] = {
198 // Local flags (c_lflag)
199 {"L_ISIG", CHECK_NIX_FLAG_OR_ZERO(ISIG)},
200 {"L_ICANON", CHECK_NIX_FLAG_OR_ZERO(ICANON)},
201#ifndef __APPLE__
202 {"L_XCASE", CHECK_NIX_FLAG_OR_ZERO(XCASE)}, // Might not be available on all systems
203#else
204 {"L_XCASE", 0},
205#endif
206 {"L_ECHO", CHECK_NIX_FLAG_OR_ZERO(ECHO)},
207 {"L_ECHOE", CHECK_NIX_FLAG_OR_ZERO(ECHOE)},
208 {"L_ECHOK", CHECK_NIX_FLAG_OR_ZERO(ECHOK)},
209 {"L_ECHONL", CHECK_NIX_FLAG_OR_ZERO(ECHONL)},
210 {"L_NOFLSH", CHECK_NIX_FLAG_OR_ZERO(NOFLSH)},
211 {"L_TOSTOP", CHECK_NIX_FLAG_OR_ZERO(TOSTOP)},
212 {"L_ECHOCTL", CHECK_NIX_FLAG_OR_ZERO(ECHOCTL)}, // Might not be available on all systems
213 {"L_ECHOPRT", CHECK_NIX_FLAG_OR_ZERO(ECHOPRT)}, // Might not be available on all systems
214 {"L_ECHOKE", CHECK_NIX_FLAG_OR_ZERO(ECHOKE)}, // Might not be available on all systems
215 {"L_FLUSHO", CHECK_NIX_FLAG_OR_ZERO(FLUSHO)},
216 {"L_PENDIN", CHECK_NIX_FLAG_OR_ZERO(PENDIN)},
217 {"L_IEXTEN", CHECK_NIX_FLAG_OR_ZERO(IEXTEN)},
218 {NULL, 0}
219};
220
221static DWORD win_valid_in_flags = 0;
222static DWORD win_valid_out_flags = 0;
223static DWORD nix_valid_i_flags = 0;
224static DWORD nix_valid_o_flags = 0;
225static DWORD nix_valid_l_flags = 0;
226static void initialize_valid_flags()
227{
228 win_valid_in_flags = 0;
229 for (int i = 0; win_console_in_flags[i].name != NULL; i++)
230 {
231 win_valid_in_flags |= win_console_in_flags[i].value;
232 }
233 win_valid_out_flags = 0;
234 for (int i = 0; win_console_out_flags[i].name != NULL; i++)
235 {
236 win_valid_out_flags |= win_console_out_flags[i].value;
237 }
238 nix_valid_i_flags = 0;
239 for (int i = 0; nix_console_i_flags[i].name != NULL; i++)
240 {
241 nix_valid_i_flags |= nix_console_i_flags[i].value;
242 }
243 nix_valid_o_flags = 0;
244 for (int i = 0; nix_console_o_flags[i].name != NULL; i++)
245 {
246 nix_valid_o_flags |= nix_console_o_flags[i].value;
247 }
248 nix_valid_l_flags = 0;
249 for (int i = 0; nix_console_l_flags[i].name != NULL; i++)
250 {
251 nix_valid_l_flags |= nix_console_l_flags[i].value;
252 }
253}
254
255#ifdef _WIN32
256// first item on the stack should be io.stdin, io.stderr, or io.stdout, second item
257// should be the flags to validate.
258// If it returns NULL, then it leaves nil+err on the stack
259static HANDLE get_console_handle(lua_State *L, int flags_optional)
260{
261 if (lua_gettop(L) < 1) {
262 luaL_argerror(L, 1, "expected file handle");
263 }
264
265 HANDLE handle;
266 DWORD valid;
267 FILE *file = *(FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE);
268 if (file == stdin && file != NULL) {
269 handle = GetStdHandle(STD_INPUT_HANDLE);
270 valid = win_valid_in_flags;
271
272 } else if (file == stdout && file != NULL) {
273 handle = GetStdHandle(STD_OUTPUT_HANDLE);
274 valid = win_valid_out_flags;
275
276 } else if (file == stderr && file != NULL) {
277 handle = GetStdHandle(STD_ERROR_HANDLE);
278 valid = win_valid_out_flags;
279
280 } else {
281 luaL_argerror(L, 1, "invalid file handle"); // does not return
282 }
283
284 if (handle == INVALID_HANDLE_VALUE) {
285 termFormatError(L, GetLastError(), "failed to retrieve std handle");
286 lua_error(L); // does not return
287 }
288
289 if (handle == NULL) {
290 lua_pushnil(L);
291 lua_pushliteral(L, "failed to get console handle");
292 return NULL;
293 }
294
295 if (flags_optional && lua_gettop(L) < 2) {
296 return handle;
297 }
298
299 if (lua_gettop(L) < 2) {
300 luaL_argerror(L, 2, "expected flags");
301 }
302
303 LSBF_BITFLAG flags = lsbf_checkbitflags(L, 2);
304 if ((flags & ~valid) != 0) {
305 luaL_argerror(L, 2, "invalid flags");
306 }
307
308 return handle;
309}
310#else
311// first item on the stack should be io.stdin, io.stderr, or io.stdout. Throws a
312// Lua error if the file is not one of these.
313static int get_console_handle(lua_State *L)
314{
315 FILE **file = (FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE);
316 if (file == NULL || *file == NULL) {
317 return luaL_argerror(L, 1, "expected file handle"); // call doesn't return
318 }
319
320 // Check if the file is stdin, stdout, or stderr
321 if (*file == stdin || *file == stdout || *file == stderr) {
322 // Push the file descriptor onto the Lua stack
323 return fileno(*file);
324 }
325
326 return luaL_argerror(L, 1, "invalid file handle"); // does not return
327}
328#endif
329
330
331
332/***
333Sets the console flags (Windows).
334The `CIF_` and `COF_` constants are available on the module table. Where `CIF` are the
335input flags (for use with `io.stdin`) and `COF` are the output flags (for use with
336`io.stdout`/`io.stderr`).
337
338To see flag status and constant names check `listconsoleflags`.
339
340Note: not all combinations of flags are allowed, as some are mutually exclusive or mutually required.
341See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode)
342@function setconsoleflags
343@tparam file file file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
344@tparam bitflags bitflags the flags to set/unset
345@treturn[1] boolean `true` on success
346@treturn[2] nil
347@treturn[2] string error message
348@usage
349local system = require('system')
350system.listconsoleflags(io.stdout) -- List all the available flags and their current status
351
352local flags = system.getconsoleflags(io.stdout)
353assert(system.setconsoleflags(io.stdout,
354 flags + system.COF_VIRTUAL_TERMINAL_PROCESSING)
355
356system.listconsoleflags(io.stdout) -- List again to check the differences
357*/
358static int lst_setconsoleflags(lua_State *L)
359{
360#ifdef _WIN32
361 HANDLE console_handle = get_console_handle(L, 0);
362 if (console_handle == NULL) {
363 return 2; // error message is already on the stack
364 }
365 LSBF_BITFLAG new_console_mode = lsbf_checkbitflags(L, 2);
366
367 if (!SetConsoleMode(console_handle, new_console_mode)) {
368 termFormatError(L, GetLastError(), "failed to set console mode");
369 return 2;
370 }
371
372#else
373 get_console_handle(L); // to validate args
374#endif
375
376 lua_pushboolean(L, 1);
377 return 1;
378}
379
380
381
382/***
383Gets console flags (Windows).
384The `CIF_` and `COF_` constants are available on the module table. Where `CIF` are the
385input flags (for use with `io.stdin`) and `COF` are the output flags (for use with
386`io.stdout`/`io.stderr`).
387
388_Note_: See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode)
389for more information on the flags.
390
391
392
393@function getconsoleflags
394@tparam file file file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
395@treturn[1] bitflags the current console flags.
396@treturn[2] nil
397@treturn[2] string error message
398@usage
399local system = require('system')
400
401local flags = system.getconsoleflags(io.stdout)
402print("Current stdout flags:", tostring(flags))
403
404if flags:has_all_of(system.COF_VIRTUAL_TERMINAL_PROCESSING + system.COF_PROCESSED_OUTPUT) then
405 print("Both flags are set")
406else
407 print("At least one flag is not set")
408end
409*/
410static int lst_getconsoleflags(lua_State *L)
411{
412 DWORD console_mode = 0;
413
414#ifdef _WIN32
415 HANDLE console_handle = get_console_handle(L, 1);
416 if (console_handle == NULL) {
417 return 2; // error message is already on the stack
418 }
419
420 if (GetConsoleMode(console_handle, &console_mode) == 0)
421 {
422 lua_pushnil(L);
423 lua_pushliteral(L, "failed to get console mode");
424 return 2;
425 }
426#else
427 get_console_handle(L); // to validate args
428
429#endif
430 lsbf_pushbitflags(L, console_mode);
431 return 1;
432}
433
434
435
436/*-------------------------------------------------------------------------
437 * Unix tcgetattr/tcsetattr functions
438 *-------------------------------------------------------------------------*/
439// Code modified from the LuaPosix library by Gary V. Vaughan
440// see https://github.com/luaposix/luaposix
441
442/***
443Get termios state (Posix).
444The terminal attributes is a table with the following fields:
445
446- `iflag` input flags
447- `oflag` output flags
448- `lflag` local flags
449- `cflag` control flags
450- `ispeed` input speed
451- `ospeed` output speed
452- `cc` control characters
453
454@function tcgetattr
455@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
456@treturn[1] termios terminal attributes, if successful. On Windows the bitflags are all 0, and the `cc` table is empty.
457@treturn[2] nil
458@treturn[2] string error message
459@treturn[2] int errnum
460@return error message if failed
461@usage
462local system = require('system')
463
464local status = assert(tcgetattr(io.stdin))
465if status.iflag:has_all_of(system.I_IGNBRK) then
466 print("Ignoring break condition")
467end
468*/
469static int lst_tcgetattr(lua_State *L)
470{
471#ifndef _WIN32
472 int r, i;
473 struct termios t;
474 int fd = get_console_handle(L);
475
476 r = tcgetattr(fd, &t);
477 if (r == -1) return pusherror(L, NULL);
478
479 lua_newtable(L);
480 lsbf_pushbitflags(L, t.c_iflag);
481 lua_setfield(L, -2, "iflag");
482
483 lsbf_pushbitflags(L, t.c_oflag);
484 lua_setfield(L, -2, "oflag");
485
486 lsbf_pushbitflags(L, t.c_lflag);
487 lua_setfield(L, -2, "lflag");
488
489 lsbf_pushbitflags(L, t.c_cflag);
490 lua_setfield(L, -2, "cflag");
491
492 lua_pushinteger(L, cfgetispeed(&t));
493 lua_setfield(L, -2, "ispeed");
494
495 lua_pushinteger(L, cfgetospeed(&t));
496 lua_setfield(L, -2, "ospeed");
497
498 lua_newtable(L);
499 for (i=0; i<NCCS; i++)
500 {
501 lua_pushinteger(L, i);
502 lua_pushinteger(L, t.c_cc[i]);
503 lua_settable(L, -3);
504 }
505 lua_setfield(L, -2, "cc");
506
507#else
508 lua_settop(L, 1); // remove all but file handle
509 get_console_handle(L, 1); //check args
510
511 lua_newtable(L);
512 lsbf_pushbitflags(L, 0);
513 lua_setfield(L, -2, "iflag");
514 lsbf_pushbitflags(L, 0);
515 lua_setfield(L, -2, "oflag");
516 lsbf_pushbitflags(L, 0);
517 lua_setfield(L, -2, "lflag");
518 lsbf_pushbitflags(L, 0);
519 lua_setfield(L, -2, "cflag");
520 lua_pushinteger(L, 0);
521 lua_setfield(L, -2, "ispeed");
522 lua_pushinteger(L, 0);
523 lua_setfield(L, -2, "ospeed");
524 lua_newtable(L);
525 lua_setfield(L, -2, "cc");
526
527#endif
528 return 1;
529}
530
531
532
533/***
534Set termios state (Posix).
535This function will set the flags as given.
536
537The `I_`, `O_`, and `L_` constants are available on the module table. They are the respective
538flags for the `iflags`, `oflags`, and `lflags` bitmasks.
539
540To see flag status and constant names check `listtermflags`. For their meaning check
541[the manpage](https://www.man7.org/linux/man-pages/man3/termios.3.html).
542
543_Note_: only `iflag`, `oflag`, and `lflag` are supported at the moment. The other fields are ignored.
544@function tcsetattr
545@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
546@int actions one of `TCSANOW`, `TCSADRAIN`, `TCSAFLUSH`
547@tparam table termios a table with bitflag fields:
548@tparam[opt] bitflags termios.iflag if given will set the input flags
549@tparam[opt] bitflags termios.oflag if given will set the output flags
550@tparam[opt] bitflags termios.lflag if given will set the local flags
551@treturn[1] bool `true`, if successful. Always returns `true` on Windows.
552@return[2] nil
553@treturn[2] string error message
554@treturn[2] int errnum
555@usage
556local system = require('system')
557
558local status = assert(tcgetattr(io.stdin))
559if not status.lflag:has_all_of(system.L_ECHO) then
560 -- if echo is off, turn echoing newlines on
561 tcsetattr(io.stdin, system.TCSANOW, { lflag = status.lflag + system.L_ECHONL }))
562end
563*/
564static int lst_tcsetattr(lua_State *L)
565{
566#ifndef _WIN32
567 struct termios t;
568 int r, i;
569 int fd = get_console_handle(L); // first is the console handle
570 int act = luaL_checkinteger(L, 2); // second is the action to take
571
572 r = tcgetattr(fd, &t);
573 if (r == -1) return pusherror(L, NULL);
574
575 t.c_iflag = lsbf_checkbitflagsfield(L, 3, "iflag", t.c_iflag);
576 t.c_oflag = lsbf_checkbitflagsfield(L, 3, "oflag", t.c_oflag);
577 t.c_lflag = lsbf_checkbitflagsfield(L, 3, "lflag", t.c_lflag);
578
579 // Skipping the others for now
580
581 // lua_getfield(L, 3, "cflag"); t.c_cflag = optint(L, -1, 0); lua_pop(L, 1);
582 // lua_getfield(L, 3, "ispeed"); cfsetispeed( &t, optint(L, -1, B0) ); lua_pop(L, 1);
583 // lua_getfield(L, 3, "ospeed"); cfsetospeed( &t, optint(L, -1, B0) ); lua_pop(L, 1);
584
585 // lua_getfield(L, 3, "cc");
586 // for (i=0; i<NCCS; i++)
587 // {
588 // lua_pushinteger(L, i);
589 // lua_gettable(L, -2);
590 // t.c_cc[i] = optint(L, -1, 0);
591 // lua_pop(L, 1);
592 // }
593
594 r = tcsetattr(fd, act, &t);
595 if (r == -1) return pusherror(L, NULL);
596
597#else
598 // Windows does not have a tcsetattr function, but we check arguments anyway
599 luaL_checkinteger(L, 2);
600 lsbf_checkbitflagsfield(L, 3, "iflag", 0);
601 lsbf_checkbitflagsfield(L, 3, "oflag", 0);
602 lsbf_checkbitflagsfield(L, 3, "lflag", 0);
603 lua_settop(L, 1); // remove all but file handle
604 get_console_handle(L, 1);
605#endif
606
607 lua_pushboolean(L, 1);
608 return 1;
609}
610
611
612
613/***
614Enables or disables non-blocking mode for a file (Posix).
615@function setnonblock
616@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
617@tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it.
618@treturn[1] bool `true`, if successful
619@treturn[2] nil
620@treturn[2] string error message
621@treturn[2] int errnum
622@see getnonblock
623@usage
624local sys = require('system')
625
626-- set io.stdin to non-blocking mode
627local old_setting = sys.getnonblock(io.stdin)
628sys.setnonblock(io.stdin, true)
629
630-- do stuff
631
632-- restore old setting
633sys.setnonblock(io.stdin, old_setting)
634*/
635static int lst_setnonblock(lua_State *L)
636{
637#ifndef _WIN32
638
639 int fd = get_console_handle(L);
640
641 int flags = fcntl(fd, F_GETFL, 0);
642 if (flags == -1) {
643 return pusherror(L, "Error getting handle flags: ");
644 }
645 if (lua_toboolean(L, 2)) {
646 // truthy: set non-blocking
647 flags |= O_NONBLOCK;
648 } else {
649 // falsy: set disable non-blocking
650 flags &= ~O_NONBLOCK;
651 }
652 if (fcntl(fd, F_SETFL, flags) == -1) {
653 return pusherror(L, "Error changing O_NONBLOCK: ");
654 }
655
656#else
657 if (lua_gettop(L) > 1) {
658 lua_settop(L, 1); // use one argument, because the second boolean will fail as get_console_flags expects bitflags
659 }
660 HANDLE console_handle = get_console_handle(L, 1);
661 if (console_handle == NULL) {
662 return 2; // error message is already on the stack
663 }
664
665#endif
666
667 lua_pushboolean(L, 1);
668 return 1;
669}
670
671
672
673/***
674Gets non-blocking mode status for a file (Posix).
675@function getnonblock
676@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
677@treturn[1] bool `true` if set to non-blocking, `false` if not. Always returns `false` on Windows.
678@treturn[2] nil
679@treturn[2] string error message
680@treturn[2] int errnum
681*/
682static int lst_getnonblock(lua_State *L)
683{
684#ifndef _WIN32
685
686 int fd = get_console_handle(L);
687
688 // Set O_NONBLOCK
689 int flags = fcntl(fd, F_GETFL, 0);
690 if (flags == -1) {
691 return pusherror(L, "Error getting handle flags: ");
692 }
693 if (flags & O_NONBLOCK) {
694 lua_pushboolean(L, 1);
695 } else {
696 lua_pushboolean(L, 0);
697 }
698
699#else
700 if (lua_gettop(L) > 1) {
701 lua_settop(L, 1); // use one argument, because the second boolean will fail as get_console_flags expects bitflags
702 }
703 HANDLE console_handle = get_console_handle(L, 1);
704 if (console_handle == NULL) {
705 return 2; // error message is already on the stack
706 }
707
708 lua_pushboolean(L, 0);
709
710#endif
711 return 1;
712}
713
714
715
716/*-------------------------------------------------------------------------
717 * Reading keyboard input
718 *-------------------------------------------------------------------------*/
719
720#ifdef _WIN32
721// Define a static buffer for UTF-8 characters
722static char utf8_buffer[4];
723static int utf8_buffer_len = 0;
724static int utf8_buffer_index = 0;
725#endif
726
727
728/***
729Reads a key from the console non-blocking. This function should not be called
730directly, but through the `system.readkey` or `system.readansi` functions. It
731will return the next byte from the input stream, or `nil` if no key was pressed.
732
733On Posix, `io.stdin` must be set to non-blocking mode using `setnonblock`
734and canonical mode must be turned off using `tcsetattr`,
735before calling this function. Otherwise it will block. No conversions are
736done on Posix, so the byte read is returned as-is.
737
738On Windows this reads a wide character and converts it to UTF-8. Multi-byte
739sequences will be buffered internally and returned one byte at a time.
740
741@function _readkey
742@treturn[1] integer the byte read from the input stream
743@treturn[2] nil if no key was pressed
744@treturn[3] nil on error
745@treturn[3] string error message
746@treturn[3] int errnum (on posix)
747*/
748static int lst_readkey(lua_State *L) {
749#ifdef _WIN32
750 if (utf8_buffer_len > 0) {
751 // Buffer not empty, return the next byte
752 lua_pushinteger(L, (unsigned char)utf8_buffer[utf8_buffer_index]);
753 utf8_buffer_index++;
754 utf8_buffer_len--;
755 // printf("returning from buffer: %d\n", luaL_checkinteger(L, -1));
756 if (utf8_buffer_len == 0) {
757 utf8_buffer_index = 0;
758 }
759 return 1;
760 }
761
762 if (!_kbhit()) {
763 return 0;
764 }
765
766 wchar_t wc = _getwch();
767 // printf("----\nread wchar_t: %x\n", wc);
768 if (wc == WEOF) {
769 lua_pushnil(L);
770 lua_pushliteral(L, "read error");
771 return 2;
772 }
773
774 if (sizeof(wchar_t) == 2) {
775 // printf("2-byte wchar_t\n");
776 // only 2 bytes wide, not 4
777 if (wc >= 0xD800 && wc <= 0xDBFF) {
778 // printf("2-byte wchar_t, received high, getting low...\n");
779
780 // we got a high surrogate, so we need to read the next one as the low surrogate
781 if (!_kbhit()) {
782 lua_pushnil(L);
783 lua_pushliteral(L, "incomplete surrogate pair");
784 return 2;
785 }
786
787 wchar_t wc2 = _getwch();
788 // printf("read wchar_t 2: %x\n", wc2);
789 if (wc2 == WEOF) {
790 lua_pushnil(L);
791 lua_pushliteral(L, "read error");
792 return 2;
793 }
794
795 if (wc2 < 0xDC00 || wc2 > 0xDFFF) {
796 lua_pushnil(L);
797 lua_pushliteral(L, "invalid surrogate pair");
798 return 2;
799 }
800 // printf("2-byte pair complete now\n");
801 wchar_t wch_pair[2] = { wc, wc2 };
802 utf8_buffer_len = WideCharToMultiByte(CP_UTF8, 0, wch_pair, 2, utf8_buffer, sizeof(utf8_buffer), NULL, NULL);
803
804 } else {
805 // printf("2-byte wchar_t, no surrogate pair\n");
806 // not a high surrogate, so we can handle just the 2 bytes directly
807 utf8_buffer_len = WideCharToMultiByte(CP_UTF8, 0, &wc, 1, utf8_buffer, sizeof(utf8_buffer), NULL, NULL);
808 }
809
810 } else {
811 // printf("4-byte wchar_t\n");
812 // 4 bytes wide, so handle as UTF-32 directly
813 utf8_buffer_len = WideCharToMultiByte(CP_UTF8, 0, &wc, 1, utf8_buffer, sizeof(utf8_buffer), NULL, NULL);
814 }
815 // printf("utf8_buffer_len: %d\n", utf8_buffer_len);
816 utf8_buffer_index = 0;
817 if (utf8_buffer_len <= 0) {
818 lua_pushnil(L);
819 lua_pushliteral(L, "UTF-8 conversion error");
820 return 2;
821 }
822
823 lua_pushinteger(L, (unsigned char)utf8_buffer[utf8_buffer_index]);
824 utf8_buffer_index++;
825 utf8_buffer_len--;
826 // printf("returning from buffer: %x\n", luaL_checkinteger(L, -1));
827 return 1;
828
829#else
830 // Posix implementation
831 char ch;
832 ssize_t bytes_read = read(STDIN_FILENO, &ch, 1);
833 if (bytes_read > 0) {
834 lua_pushinteger(L, (unsigned char)ch);
835 return 1;
836
837 } else if (bytes_read == 0) {
838 return 0; // End of file or stream closed
839
840 } else {
841 if (errno == EAGAIN || errno == EWOULDBLOCK) {
842 // Resource temporarily unavailable, no data available to read
843 return 0;
844 } else {
845 return pusherror(L, "read error");
846 }
847 }
848
849#endif
850}
851
852
853
854/*-------------------------------------------------------------------------
855 * Retrieve terminal size
856 *-------------------------------------------------------------------------*/
857
858
859/***
860Get the size of the terminal in rows and columns.
861@function termsize
862@treturn[1] int the number of rows
863@treturn[1] int the number of columns
864@treturn[2] nil
865@treturn[2] string error message
866*/
867static int lst_termsize(lua_State *L) {
868 int columns, rows;
869
870#ifdef _WIN32
871 CONSOLE_SCREEN_BUFFER_INFO csbi;
872 if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
873 termFormatError(L, GetLastError(), "Failed to get terminal size.");
874 return 2;
875 }
876 columns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
877 rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
878
879#else
880 struct winsize ws;
881 if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
882 return pusherror(L, "Failed to get terminal size.");
883 }
884 columns = ws.ws_col;
885 rows = ws.ws_row;
886
887#endif
888 lua_pushinteger(L, rows);
889 lua_pushinteger(L, columns);
890 return 2;
891}
892
893
894
895/*-------------------------------------------------------------------------
896 * utf8 conversion and support
897 *-------------------------------------------------------------------------*/
898
899// Function to convert a single UTF-8 character to a Unicode code point (uint32_t)
900// To prevent having to do codepage/locale changes, we use a custom implementation
901int utf8_to_wchar(const char *utf8, size_t len, mk_wchar_t *codepoint) {
902 if (len == 0) {
903 return -1; // No input provided
904 }
905
906 unsigned char c = (unsigned char)utf8[0];
907 if (c <= 0x7F) {
908 *codepoint = c;
909 return 1;
910 } else if ((c & 0xE0) == 0xC0) {
911 if (len < 2) return -1; // Not enough bytes
912 *codepoint = ((utf8[0] & 0x1F) << 6) | (utf8[1] & 0x3F);
913 return 2;
914 } else if ((c & 0xF0) == 0xE0) {
915 if (len < 3) return -1; // Not enough bytes
916 *codepoint = ((utf8[0] & 0x0F) << 12) | ((utf8[1] & 0x3F) << 6) | (utf8[2] & 0x3F);
917 return 3;
918 } else if ((c & 0xF8) == 0xF0) {
919 if (len < 4) return -1; // Not enough bytes
920 *codepoint = ((utf8[0] & 0x07) << 18) | ((utf8[1] & 0x3F) << 12) | ((utf8[2] & 0x3F) << 6) | (utf8[3] & 0x3F);
921 return 4;
922 } else {
923 // Invalid UTF-8 character
924 return -1;
925 }
926}
927
928
929/***
930Get the width of a utf8 character for terminal display.
931@function utf8cwidth
932@tparam string utf8_char the utf8 character to check, only the width of the first character will be returned
933@treturn[1] int the display width in columns of the first character in the string (0 for an empty string)
934@treturn[2] nil
935@treturn[2] string error message
936*/
937int lst_utf8cwidth(lua_State *L) {
938 const char *utf8_char;
939 size_t utf8_len;
940 utf8_char = luaL_checklstring(L, 1, &utf8_len);
941 int width = 0;
942
943 mk_wchar_t wc;
944
945 if (utf8_len == 0) {
946 lua_pushinteger(L, 0);
947 return 1;
948 }
949
950 // Convert the UTF-8 string to a wide character
951 int bytes_processed = utf8_to_wchar(utf8_char, utf8_len, &wc);
952 if (bytes_processed == -1) {
953 lua_pushnil(L);
954 lua_pushstring(L, "Invalid UTF-8 character");
955 return 2;
956 }
957
958 // Get the width of the wide character
959 width = mk_wcwidth(wc);
960 if (width == -1) {
961 lua_pushnil(L);
962 lua_pushstring(L, "Character width determination failed");
963 return 2;
964 }
965
966 lua_pushinteger(L, width);
967 return 1;
968}
969
970
971
972
973/***
974Get the width of a utf8 string for terminal display.
975@function utf8swidth
976@tparam string utf8_string the utf8 string to check
977@treturn[1] int the display width of the string in columns (0 for an empty string)
978@treturn[2] nil
979@treturn[2] string error message
980*/
981int lst_utf8swidth(lua_State *L) {
982 const char *utf8_str;
983 size_t utf8_len;
984 utf8_str = luaL_checklstring(L, 1, &utf8_len);
985 int total_width = 0;
986
987 if (utf8_len == 0) {
988 lua_pushinteger(L, 0);
989 return 1;
990 }
991
992 int bytes_processed = 0;
993 size_t i = 0;
994 mk_wchar_t wc;
995
996 while (i < utf8_len) {
997 bytes_processed = utf8_to_wchar(utf8_str + i, utf8_len - i, &wc);
998 if (bytes_processed == -1) {
999 lua_pushnil(L);
1000 lua_pushstring(L, "Invalid UTF-8 character");
1001 return 2;
1002 }
1003
1004 int width = mk_wcwidth(wc);
1005 if (width == -1) {
1006 lua_pushnil(L);
1007 lua_pushstring(L, "Character width determination failed");
1008 return 2;
1009 }
1010
1011 total_width += width;
1012 i += bytes_processed;
1013 }
1014
1015 lua_pushinteger(L, total_width);
1016 return 1;
1017}
1018
1019
1020
1021/*-------------------------------------------------------------------------
1022 * Windows codepage functions
1023 *-------------------------------------------------------------------------*/
1024
1025
1026/***
1027Gets the current console code page (Windows).
1028@function getconsolecp
1029@treturn[1] int the current code page (always 65001 on Posix systems)
1030*/
1031static int lst_getconsolecp(lua_State *L) {
1032 unsigned int cp = 65001;
1033#ifdef _WIN32
1034 cp = GetConsoleCP();
1035#endif
1036 lua_pushinteger(L, cp);
1037 return 1;
1038}
1039
1040
1041
1042/***
1043Sets the current console code page (Windows).
1044@function setconsolecp
1045@tparam int cp the code page to set, use `system.CODEPAGE_UTF8` (65001) for UTF-8
1046@treturn[1] bool `true` on success (always `true` on Posix systems)
1047*/
1048static int lst_setconsolecp(lua_State *L) {
1049 unsigned int cp = (unsigned int)luaL_checkinteger(L, 1);
1050 int success = TRUE;
1051#ifdef _WIN32
1052 SetConsoleCP(cp);
1053#endif
1054 lua_pushboolean(L, success);
1055 return 1;
1056}
1057
1058
1059
1060/***
1061Gets the current console output code page (Windows).
1062@function getconsoleoutputcp
1063@treturn[1] int the current code page (always 65001 on Posix systems)
1064*/
1065static int lst_getconsoleoutputcp(lua_State *L) {
1066 unsigned int cp = 65001;
1067#ifdef _WIN32
1068 cp = GetConsoleOutputCP();
1069#endif
1070 lua_pushinteger(L, cp);
1071 return 1;
1072}
1073
1074
1075
1076/***
1077Sets the current console output code page (Windows).
1078@function setconsoleoutputcp
1079@tparam int cp the code page to set, use `system.CODEPAGE_UTF8` (65001) for UTF-8
1080@treturn[1] bool `true` on success (always `true` on Posix systems)
1081*/
1082static int lst_setconsoleoutputcp(lua_State *L) {
1083 unsigned int cp = (unsigned int)luaL_checkinteger(L, 1);
1084 int success = TRUE;
1085#ifdef _WIN32
1086 SetConsoleOutputCP(cp);
1087#endif
1088 lua_pushboolean(L, success);
1089 return 1;
1090}
1091
1092
1093
32/*------------------------------------------------------------------------- 1094/*-------------------------------------------------------------------------
33 * Initializes module 1095 * Initializes module
34 *-------------------------------------------------------------------------*/ 1096 *-------------------------------------------------------------------------*/
1097
1098static luaL_Reg func[] = {
1099 { "isatty", lst_isatty },
1100 { "getconsoleflags", lst_getconsoleflags },
1101 { "setconsoleflags", lst_setconsoleflags },
1102 { "tcgetattr", lst_tcgetattr },
1103 { "tcsetattr", lst_tcsetattr },
1104 { "getnonblock", lst_getnonblock },
1105 { "setnonblock", lst_setnonblock },
1106 { "_readkey", lst_readkey },
1107 { "termsize", lst_termsize },
1108 { "utf8cwidth", lst_utf8cwidth },
1109 { "utf8swidth", lst_utf8swidth },
1110 { "getconsolecp", lst_getconsolecp },
1111 { "setconsolecp", lst_setconsolecp },
1112 { "getconsoleoutputcp", lst_getconsoleoutputcp },
1113 { "setconsoleoutputcp", lst_setconsoleoutputcp },
1114 { NULL, NULL }
1115};
1116
1117
1118
35void term_open(lua_State *L) { 1119void term_open(lua_State *L) {
1120 // set up constants and export the constants in module table
1121 initialize_valid_flags();
1122 // Windows flags
1123 for (int i = 0; win_console_in_flags[i].name != NULL; i++)
1124 {
1125 lsbf_pushbitflags(L, win_console_in_flags[i].value);
1126 lua_setfield(L, -2, win_console_in_flags[i].name);
1127 }
1128 for (int i = 0; win_console_out_flags[i].name != NULL; i++)
1129 {
1130 lsbf_pushbitflags(L, win_console_out_flags[i].value);
1131 lua_setfield(L, -2, win_console_out_flags[i].name);
1132 }
1133 // Unix flags
1134 for (int i = 0; nix_console_i_flags[i].name != NULL; i++)
1135 {
1136 lsbf_pushbitflags(L, nix_console_i_flags[i].value);
1137 lua_setfield(L, -2, nix_console_i_flags[i].name);
1138 }
1139 for (int i = 0; nix_console_o_flags[i].name != NULL; i++)
1140 {
1141 lsbf_pushbitflags(L, nix_console_o_flags[i].value);
1142 lua_setfield(L, -2, nix_console_o_flags[i].name);
1143 }
1144 for (int i = 0; nix_console_l_flags[i].name != NULL; i++)
1145 {
1146 lsbf_pushbitflags(L, nix_console_l_flags[i].value);
1147 lua_setfield(L, -2, nix_console_l_flags[i].name);
1148 }
1149 // Unix tcsetattr actions
1150 for (int i = 0; nix_tcsetattr_actions[i].name != NULL; i++)
1151 {
1152 lua_pushinteger(L, nix_tcsetattr_actions[i].value);
1153 lua_setfield(L, -2, nix_tcsetattr_actions[i].name);
1154 }
1155
1156 // export functions
36 luaL_setfuncs(L, func, 0); 1157 luaL_setfuncs(L, func, 0);
37} 1158}