diff options
Diffstat (limited to 'src/term.c')
-rw-r--r-- | src/term.c | 131 |
1 files changed, 127 insertions, 4 deletions
@@ -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 |
388 | local system = require('system') | 389 | local system = require('system') |
389 | system.listconsoleflags(io.stdout) -- List all the available flags and their current status | 390 | system.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) |
428 | for more information on the flags. | 429 | for 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 |
438 | local system = require('system') | 438 | local 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 |
501 | local system = require('system') | 502 | local 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 |
595 | local system = require('system') | 597 | local 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 | /* | ||
656 | reopen FDs for independent file descriptions. | ||
657 | */ | ||
658 | static 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 | /*** | ||
712 | Creates new file descriptions for `stdout` and `stderr`. | ||
713 | Even if the file descriptors are unique, they still might point to the same | ||
714 | file description, and hence share settings like `O_NONBLOCK`. This means that | ||
715 | if one of them is set to non-blocking, the other will be as well. This can | ||
716 | lead to unexpected behavior. | ||
717 | |||
718 | This function is used to detach `stdout` and `stderr` from the original | ||
719 | file descriptions, and create new file descriptions for them. This allows | ||
720 | independent control of flags (e.g., `O_NONBLOCK`) on `stdout` and `stderr`, | ||
721 | avoiding shared side effects. | ||
722 | |||
723 | Does 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 | */ | ||
729 | static 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 | /*** |
653 | Enables or disables non-blocking mode for a file (Posix). | 751 | Enables or disables non-blocking mode for a file (Posix). |
752 | Check `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 |
663 | local sys = require('system') | 764 | local sys = require('system') |
765 | sys.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 |
666 | local old_setting = sys.getnonblock(io.stdin) | 768 | local 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 | */ |
721 | static int lst_getnonblock(lua_State *L) | 825 | static 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 | */ |
787 | static int lst_readkey(lua_State *L) { | 892 | static 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 | */ |
976 | int lst_utf8cwidth(lua_State *L) { | 1093 | int 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 | */ |
1037 | int lst_utf8swidth(lua_State *L) { | 1155 | int 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) { | |||
1083 | Gets the current console code page (Windows). | 1201 | Gets 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 | */ |
1087 | static int lst_getconsolecp(lua_State *L) { | 1206 | static 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 | */ |
1104 | static int lst_setconsolecp(lua_State *L) { | 1224 | static 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) { | |||
1117 | Gets the current console output code page (Windows). | 1237 | Gets 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 | */ |
1121 | static int lst_getconsoleoutputcp(lua_State *L) { | 1242 | static 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 | */ |
1138 | static int lst_setconsoleoutputcp(lua_State *L) { | 1260 | static 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 }, |