aboutsummaryrefslogtreecommitdiff
path: root/src/term.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/term.c')
-rw-r--r--src/term.c131
1 files changed, 127 insertions, 4 deletions
diff --git a/src/term.c b/src/term.c
index 4cfce95..0325208 100644
--- a/src/term.c
+++ b/src/term.c
@@ -384,6 +384,7 @@ See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/con
384@treturn[1] boolean `true` on success 384@treturn[1] boolean `true` on success
385@treturn[2] nil 385@treturn[2] nil
386@treturn[2] string error message 386@treturn[2] string error message
387@within Terminal_Windows
387@usage 388@usage
388local system = require('system') 389local system = require('system')
389system.listconsoleflags(io.stdout) -- List all the available flags and their current status 390system.listconsoleflags(io.stdout) -- List all the available flags and their current status
@@ -427,13 +428,12 @@ input flags (for use with `io.stdin`) and `COF` are the output flags (for use wi
427_Note_: See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode) 428_Note_: See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode)
428for more information on the flags. 429for more information on the flags.
429 430
430
431
432@function getconsoleflags 431@function getconsoleflags
433@tparam file file file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` 432@tparam file file file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
434@treturn[1] bitflags the current console flags. 433@treturn[1] bitflags the current console flags.
435@treturn[2] nil 434@treturn[2] nil
436@treturn[2] string error message 435@treturn[2] string error message
436@within Terminal_Windows
437@usage 437@usage
438local system = require('system') 438local system = require('system')
439 439
@@ -497,6 +497,7 @@ The terminal attributes is a table with the following fields:
497@treturn[2] string error message 497@treturn[2] string error message
498@treturn[2] int errnum 498@treturn[2] int errnum
499@return error message if failed 499@return error message if failed
500@within Terminal_Posix
500@usage 501@usage
501local system = require('system') 502local system = require('system')
502 503
@@ -591,6 +592,7 @@ _Note_: only `iflag`, `oflag`, and `lflag` are supported at the moment. The othe
591@return[2] nil 592@return[2] nil
592@treturn[2] string error message 593@treturn[2] string error message
593@treturn[2] int errnum 594@treturn[2] int errnum
595@within Terminal_Posix
594@usage 596@usage
595local system = require('system') 597local system = require('system')
596 598
@@ -649,8 +651,105 @@ static int lst_tcsetattr(lua_State *L)
649 651
650 652
651 653
654#ifndef _WIN32
655/*
656reopen FDs for independent file descriptions.
657*/
658static void reopen_fd(lua_State *L, int fd, int flags) {
659 char path[64];
660 int newfd = -1;
661
662 if (fd != STDOUT_FILENO && fd != STDERR_FILENO) {
663 luaL_error(L, "Invalid file descriptor: %d. Only stdout (1) and stderr (2) are supported.", fd);
664 }
665
666 const char *fallback_path = (fd == STDOUT_FILENO) ? "/dev/stdout" :
667 (fd == STDERR_FILENO) ? "/dev/stderr" : NULL;
668
669 // If fd is a terminal, reopen its actual device (e.g. /dev/ttys003)
670 // Works on all POSIX platforms that have terminals (macOS, Linux, BSD, etc.)
671 if (isatty(fd)) {
672 const char *tty = ttyname(fd);
673 if (tty) {
674 newfd = open(tty, flags);
675 }
676 }
677
678 if (newfd < 0) {
679 // For non-tty: try /dev/fd/N — POSIX-compliant and standard on macOS, Linux, BSD.
680 // This gives a new file description even if the target is a file or pipe.
681 snprintf(path, sizeof(path), "/dev/fd/%d", fd);
682 newfd = open(path, flags);
683 }
684
685 if (newfd < 0) {
686 // Fallback: for platforms/environments where /dev/fd/N doesn't exist.
687 // /dev/stdout and /dev/stderr are standard on Linux, but may not create new descriptions.
688 if (fallback_path) {
689 newfd = open(fallback_path, flags);
690 }
691 }
692
693 if (newfd < 0) {
694 // All attempts failed — raise error with detailed info (call will not return)
695 luaL_error(L, "Failed to reopen fd %d: tried ttyname(), /dev/fd/%d, and fallback %s: %s",
696 fd, fd, fallback_path ? fallback_path : "(none)", strerror(errno));
697 }
698
699 // Replace the original fd with the new one
700 if (dup2(newfd, fd) < 0) {
701 close(newfd);
702 luaL_error(L, "dup2 failed for fd %d: %s", fd, strerror(errno));
703 }
704
705 close(newfd); // Close the new fd, as dup2 has replaced the original fd with it
706}
707#endif
708
709
710
711/***
712Creates new file descriptions for `stdout` and `stderr`.
713Even if the file descriptors are unique, they still might point to the same
714file description, and hence share settings like `O_NONBLOCK`. This means that
715if one of them is set to non-blocking, the other will be as well. This can
716lead to unexpected behavior.
717
718This function is used to detach `stdout` and `stderr` from the original
719file descriptions, and create new file descriptions for them. This allows
720independent control of flags (e.g., `O_NONBLOCK`) on `stdout` and `stderr`,
721avoiding shared side effects.
722
723Does not modify `stdin` (fd 0), and does nothing on Windows.
724@function detachfds
725@return boolean `true` on success, or throws an error on failure.
726@within Terminal_Posix
727@see setnonblock
728*/
729static int lst_detachfds(lua_State *L) {
730 static int already_detached = 0; // detaching is once per process(not per thread or Lua state)
731 if (already_detached) {
732 lua_pushnil(L);
733 lua_pushliteral(L, "stdout and stderr already detached");
734 return 1;
735 }
736 already_detached = 1;
737
738#ifndef _WIN32
739 // Reopen stdout and stderr with new file descriptions
740 reopen_fd(L, STDOUT_FILENO, O_WRONLY);
741 reopen_fd(L, STDERR_FILENO, O_WRONLY);
742#endif
743
744 lua_pushboolean(L, 1);
745 return 1;
746}
747
748
749
652/*** 750/***
653Enables or disables non-blocking mode for a file (Posix). 751Enables or disables non-blocking mode for a file (Posix).
752Check `detachfds` in case there are shared file descriptions.
654@function setnonblock 753@function setnonblock
655@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` 754@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr`
656@tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it. 755@tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it.
@@ -658,9 +757,12 @@ Enables or disables non-blocking mode for a file (Posix).
658@treturn[2] nil 757@treturn[2] nil
659@treturn[2] string error message 758@treturn[2] string error message
660@treturn[2] int errnum 759@treturn[2] int errnum
760@within Terminal_Posix
661@see getnonblock 761@see getnonblock
762@see detachfds
662@usage 763@usage
663local sys = require('system') 764local sys = require('system')
765sys.detachfds() -- detach stdout and stderr, so only stdin becomes non-blocking
664 766
665-- set io.stdin to non-blocking mode 767-- set io.stdin to non-blocking mode
666local old_setting = sys.getnonblock(io.stdin) 768local old_setting = sys.getnonblock(io.stdin)
@@ -717,6 +819,8 @@ Gets non-blocking mode status for a file (Posix).
717@treturn[2] nil 819@treturn[2] nil
718@treturn[2] string error message 820@treturn[2] string error message
719@treturn[2] int errnum 821@treturn[2] int errnum
822@within Terminal_Posix
823@see setnonblock
720*/ 824*/
721static int lst_getnonblock(lua_State *L) 825static int lst_getnonblock(lua_State *L)
722{ 826{
@@ -783,6 +887,7 @@ sequences will be buffered internally and returned one byte at a time.
783@treturn[3] nil on error 887@treturn[3] nil on error
784@treturn[3] string error message 888@treturn[3] string error message
785@treturn[3] int errnum (on posix) 889@treturn[3] int errnum (on posix)
890@within Terminal_Input
786*/ 891*/
787static int lst_readkey(lua_State *L) { 892static int lst_readkey(lua_State *L) {
788#ifdef _WIN32 893#ifdef _WIN32
@@ -803,7 +908,18 @@ static int lst_readkey(lua_State *L) {
803 } 908 }
804 909
805 wchar_t wc = _getwch(); 910 wchar_t wc = _getwch();
806 // printf("----\nread wchar_t: %x\n", wc); 911 // printf("----\nread wchar_t: %x\n", (unsigned int)wc);
912
913 if (wc == 0x00 || wc == 0xE0) {
914 // printf("Ignoring scan code: %x\n", (unsigned int)wc);
915 // On Windows, especially with key-repeat, we can get native scancodes even if we've set the console
916 // to ANSI mode. Something, something, terminal not keeping up... These are not valid wide characters,
917 // so we ignore them. Scan codes start with either 0x00 or 0xE0, and have a follow up byte, which we all ignore.
918 // These codes are unique, so we do not risk dropping valid data.
919 _getwch(); // Discard 2nd half of the scan code
920 return 0;
921 }
922
807 if (wc == WEOF) { 923 if (wc == WEOF) {
808 lua_pushnil(L); 924 lua_pushnil(L);
809 lua_pushliteral(L, "read error"); 925 lua_pushliteral(L, "read error");
@@ -824,7 +940,7 @@ static int lst_readkey(lua_State *L) {
824 } 940 }
825 941
826 wchar_t wc2 = _getwch(); 942 wchar_t wc2 = _getwch();
827 // printf("read wchar_t 2: %x\n", wc2); 943 // printf("read wchar_t 2: %x\n", (unsigned int)wc2);
828 if (wc2 == WEOF) { 944 if (wc2 == WEOF) {
829 lua_pushnil(L); 945 lua_pushnil(L);
830 lua_pushliteral(L, "read error"); 946 lua_pushliteral(L, "read error");
@@ -972,6 +1088,7 @@ Get the width of a utf8 character for terminal display.
972@treturn[1] int the display width in columns of the first character in the string (0 for an empty string) 1088@treturn[1] int the display width in columns of the first character in the string (0 for an empty string)
973@treturn[2] nil 1089@treturn[2] nil
974@treturn[2] string error message 1090@treturn[2] string error message
1091@within Terminal_UTF-8
975*/ 1092*/
976int lst_utf8cwidth(lua_State *L) { 1093int lst_utf8cwidth(lua_State *L) {
977 int width = 0; 1094 int width = 0;
@@ -1033,6 +1150,7 @@ Get the width of a utf8 string for terminal display.
1033@treturn[1] int the display width of the string in columns (0 for an empty string) 1150@treturn[1] int the display width of the string in columns (0 for an empty string)
1034@treturn[2] nil 1151@treturn[2] nil
1035@treturn[2] string error message 1152@treturn[2] string error message
1153@within Terminal_UTF-8
1036*/ 1154*/
1037int lst_utf8swidth(lua_State *L) { 1155int lst_utf8swidth(lua_State *L) {
1038 const char *utf8_str; 1156 const char *utf8_str;
@@ -1083,6 +1201,7 @@ int lst_utf8swidth(lua_State *L) {
1083Gets the current console code page (Windows). 1201Gets the current console code page (Windows).
1084@function getconsolecp 1202@function getconsolecp
1085@treturn[1] int the current code page (always 65001 on Posix systems) 1203@treturn[1] int the current code page (always 65001 on Posix systems)
1204@within Terminal_UTF-8
1086*/ 1205*/
1087static int lst_getconsolecp(lua_State *L) { 1206static int lst_getconsolecp(lua_State *L) {
1088 unsigned int cp = 65001; 1207 unsigned int cp = 65001;
@@ -1100,6 +1219,7 @@ Sets the current console code page (Windows).
1100@function setconsolecp 1219@function setconsolecp
1101@tparam int cp the code page to set, use `system.CODEPAGE_UTF8` (65001) for UTF-8 1220@tparam int cp the code page to set, use `system.CODEPAGE_UTF8` (65001) for UTF-8
1102@treturn[1] bool `true` on success (always `true` on Posix systems) 1221@treturn[1] bool `true` on success (always `true` on Posix systems)
1222@within Terminal_UTF-8
1103*/ 1223*/
1104static int lst_setconsolecp(lua_State *L) { 1224static int lst_setconsolecp(lua_State *L) {
1105 unsigned int cp = (unsigned int)luaL_checkinteger(L, 1); 1225 unsigned int cp = (unsigned int)luaL_checkinteger(L, 1);
@@ -1117,6 +1237,7 @@ static int lst_setconsolecp(lua_State *L) {
1117Gets the current console output code page (Windows). 1237Gets the current console output code page (Windows).
1118@function getconsoleoutputcp 1238@function getconsoleoutputcp
1119@treturn[1] int the current code page (always 65001 on Posix systems) 1239@treturn[1] int the current code page (always 65001 on Posix systems)
1240@within Terminal_UTF-8
1120*/ 1241*/
1121static int lst_getconsoleoutputcp(lua_State *L) { 1242static int lst_getconsoleoutputcp(lua_State *L) {
1122 unsigned int cp = 65001; 1243 unsigned int cp = 65001;
@@ -1134,6 +1255,7 @@ Sets the current console output code page (Windows).
1134@function setconsoleoutputcp 1255@function setconsoleoutputcp
1135@tparam int cp the code page to set, use `system.CODEPAGE_UTF8` (65001) for UTF-8 1256@tparam int cp the code page to set, use `system.CODEPAGE_UTF8` (65001) for UTF-8
1136@treturn[1] bool `true` on success (always `true` on Posix systems) 1257@treturn[1] bool `true` on success (always `true` on Posix systems)
1258@within Terminal_UTF-8
1137*/ 1259*/
1138static int lst_setconsoleoutputcp(lua_State *L) { 1260static int lst_setconsoleoutputcp(lua_State *L) {
1139 unsigned int cp = (unsigned int)luaL_checkinteger(L, 1); 1261 unsigned int cp = (unsigned int)luaL_checkinteger(L, 1);
@@ -1157,6 +1279,7 @@ static luaL_Reg func[] = {
1157 { "setconsoleflags", lst_setconsoleflags }, 1279 { "setconsoleflags", lst_setconsoleflags },
1158 { "tcgetattr", lst_tcgetattr }, 1280 { "tcgetattr", lst_tcgetattr },
1159 { "tcsetattr", lst_tcsetattr }, 1281 { "tcsetattr", lst_tcsetattr },
1282 { "detachfds", lst_detachfds },
1160 { "getnonblock", lst_getnonblock }, 1283 { "getnonblock", lst_getnonblock },
1161 { "setnonblock", lst_setnonblock }, 1284 { "setnonblock", lst_setnonblock },
1162 { "_readkey", lst_readkey }, 1285 { "_readkey", lst_readkey },