aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md20
-rw-r--r--doc_topics/03-terminal.md2
-rw-r--r--docs/classes/bitflags.html2
-rw-r--r--docs/examples/compat.lua.html2
-rw-r--r--docs/examples/flag_debugging.lua.html2
-rw-r--r--docs/examples/password_input.lua.html2
-rw-r--r--docs/examples/read.lua.html2
-rw-r--r--docs/examples/readline.lua.html2
-rw-r--r--docs/examples/spinner.lua.html2
-rw-r--r--docs/examples/spiral_snake.lua.html2
-rw-r--r--docs/examples/terminalsize.lua.html2
-rw-r--r--docs/index.html2
-rw-r--r--docs/modules/system.html59
-rw-r--r--docs/topics/01-introduction.md.html2
-rw-r--r--docs/topics/02-development.md.html2
-rw-r--r--docs/topics/03-terminal.md.html4
-rw-r--r--docs/topics/CHANGELOG.md.html28
-rw-r--r--docs/topics/LICENSE.md.html2
-rw-r--r--rockspecs/luasystem-0.6.0-1.rockspec85
-rw-r--r--rockspecs/luasystem-0.6.1-1.rockspec85
-rw-r--r--rockspecs/luasystem-0.6.2-1.rockspec85
-rw-r--r--spec/04-term_spec.lua67
-rw-r--r--src/core.c2
-rw-r--r--src/term.c100
-rw-r--r--system/init.lua70
25 files changed, 580 insertions, 53 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c9e15e..d4107d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,22 @@ The scope of what is covered by the version number excludes:
27 27
28## Version history 28## Version history
29 29
30### version 0.6.2, released 15-Apr-2025
31
32 - Fix: autotermrestore didn't work because its metatable was overwritten.
33
34### version 0.6.1, released 13-Apr-2025
35
36- Docs: document readansi internal buffer for incomplete sequences.
37- Fix: ensure to properly parse `<alt>+key` key presses
38
39### version 0.6.0, released 10-Apr-2025
40
41- Fix: when sleep returns an error, pass that on in `readkey`.
42- Feat: added `detachfds` which will create separate file descriptions for `stdout`
43 and `stderr` to ensure that related settings (eg. non-blocking flag) will not be shared
44 amongst those streams and `stdin`.
45
30### version 0.5.1, released 12-Mar-2025 46### version 0.5.1, released 12-Mar-2025
31 47
32- Fix: on older unixes with glibc < 2.25, fall back to `/dev/urandom` 48- Fix: on older unixes with glibc < 2.25, fall back to `/dev/urandom`
@@ -38,12 +54,12 @@ The scope of what is covered by the version number excludes:
38- Feat: allow passing in a sleep function to `readkey` and `readansi` 54- Feat: allow passing in a sleep function to `readkey` and `readansi`
39- Fix: NetBSD fix compilation, undeclared directives 55- Fix: NetBSD fix compilation, undeclared directives
40- Refactor: random bytes; remove deprecated API usage on Windows, move to 56- Refactor: random bytes; remove deprecated API usage on Windows, move to
41 binary api instead of /dev/urandom file on linux and bsd 57 binary api instead of `/dev/urandom` file on linux and bsd
42 58
43### version 0.4.5, released 18-Dec-2024 59### version 0.4.5, released 18-Dec-2024
44 60
45- Fix: suppress a warning when building with clang 61- Fix: suppress a warning when building with clang
46- Fix: do not rely on luaconf.h to include limits.h, fixes builds with latest LuaJIT (#38). 62- Fix: do not rely on `luaconf.h` to include `limits.h`, fixes builds with latest LuaJIT (#38).
47 63
48### version 0.4.4, released 03-Sep-2024 64### version 0.4.4, released 03-Sep-2024
49 65
diff --git a/doc_topics/03-terminal.md b/doc_topics/03-terminal.md
index 5bdf543..a5341d6 100644
--- a/doc_topics/03-terminal.md
+++ b/doc_topics/03-terminal.md
@@ -16,6 +16,7 @@ Since there are a myriad of settings available;
16- `system.setconsoleflags` (Windows) 16- `system.setconsoleflags` (Windows)
17- `system.setconsolecp` (Windows) 17- `system.setconsolecp` (Windows)
18- `system.setconsoleoutputcp` (Windows) 18- `system.setconsoleoutputcp` (Windows)
19- `system.detachfds` (Posix)
19- `system.setnonblock` (Posix) 20- `system.setnonblock` (Posix)
20- `system.tcsetattr` (Posix) 21- `system.tcsetattr` (Posix)
21 22
@@ -105,6 +106,7 @@ To use non-blocking input here's how to set it up:
105 sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT) 106 sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT)
106 107
107 -- setup Posix by disabling echo, canonical mode, and making non-blocking 108 -- setup Posix by disabling echo, canonical mode, and making non-blocking
109 sys.detachfds() -- ensure stdin/out/err have their own file descriptions
108 local of_attr = sys.tcgetattr(io.stdin) 110 local of_attr = sys.tcgetattr(io.stdin)
109 sys.tcsetattr(io.stdin, sys.TCSANOW, { 111 sys.tcsetattr(io.stdin, sys.TCSANOW, {
110 lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, 112 lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO,
diff --git a/docs/classes/bitflags.html b/docs/classes/bitflags.html
index 647cc62..3e99d0e 100644
--- a/docs/classes/bitflags.html
+++ b/docs/classes/bitflags.html
@@ -298,7 +298,7 @@ return <code>false</code> if the flags are checked.
298</div> <!-- id="main" --> 298</div> <!-- id="main" -->
299<div id="about"> 299<div id="about">
300<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 300<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
301<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 301<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
302</div> <!-- id="about" --> 302</div> <!-- id="about" -->
303</div> <!-- id="container" --> 303</div> <!-- id="container" -->
304</body> 304</body>
diff --git a/docs/examples/compat.lua.html b/docs/examples/compat.lua.html
index e421044..b57a912 100644
--- a/docs/examples/compat.lua.html
+++ b/docs/examples/compat.lua.html
@@ -112,7 +112,7 @@
112</div> <!-- id="main" --> 112</div> <!-- id="main" -->
113<div id="about"> 113<div id="about">
114<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 114<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
115<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 115<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
116</div> <!-- id="about" --> 116</div> <!-- id="about" -->
117</div> <!-- id="container" --> 117</div> <!-- id="container" -->
118</body> 118</body>
diff --git a/docs/examples/flag_debugging.lua.html b/docs/examples/flag_debugging.lua.html
index e8a75e3..616ce69 100644
--- a/docs/examples/flag_debugging.lua.html
+++ b/docs/examples/flag_debugging.lua.html
@@ -80,7 +80,7 @@
80</div> <!-- id="main" --> 80</div> <!-- id="main" -->
81<div id="about"> 81<div id="about">
82<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 82<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
83<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 83<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
84</div> <!-- id="about" --> 84</div> <!-- id="about" -->
85</div> <!-- id="container" --> 85</div> <!-- id="container" -->
86</body> 86</body>
diff --git a/docs/examples/password_input.lua.html b/docs/examples/password_input.lua.html
index a5eac4d..faa0473 100644
--- a/docs/examples/password_input.lua.html
+++ b/docs/examples/password_input.lua.html
@@ -132,7 +132,7 @@ useful for reading secrets from the user.
132</div> <!-- id="main" --> 132</div> <!-- id="main" -->
133<div id="about"> 133<div id="about">
134<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 134<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
135<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 135<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
136</div> <!-- id="about" --> 136</div> <!-- id="about" -->
137</div> <!-- id="container" --> 137</div> <!-- id="container" -->
138</body> 138</body>
diff --git a/docs/examples/read.lua.html b/docs/examples/read.lua.html
index 3b53a8b..1a9648c 100644
--- a/docs/examples/read.lua.html
+++ b/docs/examples/read.lua.html
@@ -143,7 +143,7 @@ sys.<span class="function-name">setnonblock</span>(<span class="global">io</span
143</div> <!-- id="main" --> 143</div> <!-- id="main" -->
144<div id="about"> 144<div id="about">
145<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 145<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
146<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 146<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
147</div> <!-- id="about" --> 147</div> <!-- id="about" -->
148</div> <!-- id="container" --> 148</div> <!-- id="container" -->
149</body> 149</body>
diff --git a/docs/examples/readline.lua.html b/docs/examples/readline.lua.html
index dd63721..fad5df7 100644
--- a/docs/examples/readline.lua.html
+++ b/docs/examples/readline.lua.html
@@ -549,7 +549,7 @@ sys.<span class="function-name">setconsoleflags</span>(<span class="global">io</
549</div> <!-- id="main" --> 549</div> <!-- id="main" -->
550<div id="about"> 550<div id="about">
551<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 551<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
552<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 552<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
553</div> <!-- id="about" --> 553</div> <!-- id="about" -->
554</div> <!-- id="container" --> 554</div> <!-- id="container" -->
555</body> 555</body>
diff --git a/docs/examples/spinner.lua.html b/docs/examples/spinner.lua.html
index 99a1c91..c776106 100644
--- a/docs/examples/spinner.lua.html
+++ b/docs/examples/spinner.lua.html
@@ -137,7 +137,7 @@ sys.<span class="function-name">setnonblock</span>(<span class="global">io</span
137</div> <!-- id="main" --> 137</div> <!-- id="main" -->
138<div id="about"> 138<div id="about">
139<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 139<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
140<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 140<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
141</div> <!-- id="about" --> 141</div> <!-- id="about" -->
142</div> <!-- id="container" --> 142</div> <!-- id="container" -->
143</body> 143</body>
diff --git a/docs/examples/spiral_snake.lua.html b/docs/examples/spiral_snake.lua.html
index 7be16e8..a102103 100644
--- a/docs/examples/spiral_snake.lua.html
+++ b/docs/examples/spiral_snake.lua.html
@@ -145,7 +145,7 @@ codes for moving the cursor around.
145</div> <!-- id="main" --> 145</div> <!-- id="main" -->
146<div id="about"> 146<div id="about">
147<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 147<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
148<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 148<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
149</div> <!-- id="about" --> 149</div> <!-- id="about" -->
150</div> <!-- id="container" --> 150</div> <!-- id="container" -->
151</body> 151</body>
diff --git a/docs/examples/terminalsize.lua.html b/docs/examples/terminalsize.lua.html
index 6a6a93e..9aca861 100644
--- a/docs/examples/terminalsize.lua.html
+++ b/docs/examples/terminalsize.lua.html
@@ -110,7 +110,7 @@ sys.<span class="function-name">tcsetattr</span>(<span class="global">io</span>.
110</div> <!-- id="main" --> 110</div> <!-- id="main" -->
111<div id="about"> 111<div id="about">
112<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 112<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
113<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 113<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
114</div> <!-- id="about" --> 114</div> <!-- id="about" -->
115</div> <!-- id="container" --> 115</div> <!-- id="container" -->
116</body> 116</body>
diff --git a/docs/index.html b/docs/index.html
index 5d7b02f..6461689 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -142,7 +142,7 @@
142</div> <!-- id="main" --> 142</div> <!-- id="main" -->
143<div id="about"> 143<div id="about">
144<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 144<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
145<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 145<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
146</div> <!-- id="about" --> 146</div> <!-- id="about" -->
147</div> <!-- id="container" --> 147</div> <!-- id="container" -->
148</body> 148</body>
diff --git a/docs/modules/system.html b/docs/modules/system.html
index fa41458..0934b35 100644
--- a/docs/modules/system.html
+++ b/docs/modules/system.html
@@ -124,6 +124,10 @@
124 <td class="summary">Backs up terminal settings and restores them on application exit.</td> 124 <td class="summary">Backs up terminal settings and restores them on application exit.</td>
125 </tr> 125 </tr>
126 <tr> 126 <tr>
127 <td class="name" nowrap><a href="#detachfds">detachfds ()</a></td>
128 <td class="summary">Creates new file descriptions for <code>stdout</code> and <code>stderr</code>.</td>
129 </tr>
130 <tr>
127 <td class="name" nowrap><a href="#getconsolecp">getconsolecp ()</a></td> 131 <td class="name" nowrap><a href="#getconsolecp">getconsolecp ()</a></td>
128 <td class="summary">Gets the current console code page (Windows).</td> 132 <td class="summary">Gets the current console code page (Windows).</td>
129 </tr> 133 </tr>
@@ -498,6 +502,40 @@ sequences will be buffered internally and returned one byte at a time.
498 502
499</dd> 503</dd>
500 <dt> 504 <dt>
505 <a name = "detachfds"></a>
506 <strong>detachfds ()</strong>
507 </dt>
508 <dd>
509 Creates new file descriptions for <code>stdout</code> and <code>stderr</code>.
510Even if the file descriptors are unique, they still might point to the same
511file description, and hence share settings like <code>O_NONBLOCK</code>. This means that
512if one of them is set to non-blocking, the other will be as well. This can
513lead to unexpected behavior.</p>
514
515<p>This function is used to detach <code>stdout</code> and <code>stderr</code> from the original
516file descriptions, and create new file descriptions for them. This allows
517independent control of flags (e.g., <code>O_NONBLOCK</code>) on <code>stdout</code> and <code>stderr</code>,
518avoiding shared side effects.</p>
519
520<p>Does not modify <code>stdin</code> (fd 0), and does nothing on Windows.
521
522
523
524 <h3>Returns:</h3>
525 <ol>
526
527 boolean <code>true</code> on success, or throws an error on failure.
528 </ol>
529
530
531 <h3>See also:</h3>
532 <ul>
533 <a href="../modules/system.html#setnonblock">setnonblock</a>
534 </ul>
535
536
537</dd>
538 <dt>
501 <a name = "getconsolecp"></a> 539 <a name = "getconsolecp"></a>
502 <strong>getconsolecp ()</strong> 540 <strong>getconsolecp ()</strong>
503 </dt> 541 </dt>
@@ -632,6 +670,10 @@ for more information on the flags.
632 </ol> 670 </ol>
633 671
634 672
673 <h3>See also:</h3>
674 <ul>
675 <a href="../modules/system.html#setnonblock">setnonblock</a>
676 </ul>
635 677
636 678
637</dd> 679</dd>
@@ -772,7 +814,9 @@ system.<span class="function-name">listconsoleflags</span>(<span class="global">
772 error message; <code>&quot;timeout&quot;</code> if the timeout was reached.</li> 814 error message; <code>&quot;timeout&quot;</code> if the timeout was reached.</li>
773 <li> 815 <li>
774 <span class="types"><a class="type" href="https://www.lua.org/manual/5.4/manual.html#6.4">string</a></span> 816 <span class="types"><a class="type" href="https://www.lua.org/manual/5.4/manual.html#6.4">string</a></span>
775 partial result in case of an error while reading a sequence, the sequence so far.</li> 817 partial result in case of an error while reading a sequence, the sequence so far.
818 The function retains its own internal buffer, so on the next call the incomplete buffer is used to
819 complete the sequence.</li>
776 </ol> 820 </ol>
777 821
778 822
@@ -801,7 +845,7 @@ system.<span class="function-name">listconsoleflags</span>(<span class="global">
801 </li> 845 </li>
802 <li><span class="parameter">fsleep</span> 846 <li><span class="parameter">fsleep</span>
803 <span class="types"><span class="type">function</span></span> 847 <span class="types"><span class="type">function</span></span>
804 the function to call for sleeping. 848 the function to call for sleeping; <code>ok, err = fsleep(secs)</code>
805 (<em>default</em> system.sleep) 849 (<em>default</em> system.sleep)
806 </li> 850 </li>
807 </ul> 851 </ul>
@@ -819,7 +863,7 @@ system.<span class="function-name">listconsoleflags</span>(<span class="global">
819 if no key was read</li> 863 if no key was read</li>
820 <li> 864 <li>
821 <span class="types"><a class="type" href="https://www.lua.org/manual/5.4/manual.html#6.4">string</a></span> 865 <span class="types"><a class="type" href="https://www.lua.org/manual/5.4/manual.html#6.4">string</a></span>
822 error message; <code>&quot;timeout&quot;</code> if the timeout was reached.</li> 866 error message when the timeout was reached (<code>&quot;timeout&quot;</code>), or if <a href="../modules/system.html#sleep">sleep</a> failed.</li>
823 </ol> 867 </ol>
824 868
825 869
@@ -947,6 +991,7 @@ system.<span class="function-name">listconsoleflags</span>(<span class="global">
947 </dt> 991 </dt>
948 <dd> 992 <dd>
949 Enables or disables non-blocking mode for a file (Posix). 993 Enables or disables non-blocking mode for a file (Posix).
994Check <a href="../modules/system.html#detachfds">detachfds</a> in case there are shared file descriptions.
950 995
951 996
952 <h3>Parameters:</h3> 997 <h3>Parameters:</h3>
@@ -985,13 +1030,15 @@ system.<span class="function-name">listconsoleflags</span>(<span class="global">
985 1030
986 <h3>See also:</h3> 1031 <h3>See also:</h3>
987 <ul> 1032 <ul>
988 <a href="../modules/system.html#getnonblock">getnonblock</a> 1033 <li><a href="../modules/system.html#getnonblock">getnonblock</a></li>
1034 <li><a href="../modules/system.html#detachfds">detachfds</a></li>
989 </ul> 1035 </ul>
990 1036
991 <h3>Usage:</h3> 1037 <h3>Usage:</h3>
992 <ul> 1038 <ul>
993 <pre class="example"><span class="keyword">local</span> sys = <span class="global">require</span>(<span class="string">'system'</span>) 1039 <pre class="example"><span class="keyword">local</span> sys = <span class="global">require</span>(<span class="string">'system'</span>)
994 1040sys.<span class="function-name">detachfds</span>() <span class="comment">-- detach stdout and stderr, so only stdin becomes non-blocking
1041</span>
995<span class="comment">-- set io.stdin to non-blocking mode 1042<span class="comment">-- set io.stdin to non-blocking mode
996</span><span class="keyword">local</span> old_setting = sys.<span class="function-name">getnonblock</span>(<span class="global">io</span>.stdin) 1043</span><span class="keyword">local</span> old_setting = sys.<span class="function-name">getnonblock</span>(<span class="global">io</span>.stdin)
997sys.<span class="function-name">setnonblock</span>(<span class="global">io</span>.stdin, <span class="keyword">true</span>) 1044sys.<span class="function-name">setnonblock</span>(<span class="global">io</span>.stdin, <span class="keyword">true</span>)
@@ -1422,7 +1469,7 @@ This function will sleep, without doing a busy-loop and wasting CPU cycles.
1422</div> <!-- id="main" --> 1469</div> <!-- id="main" -->
1423<div id="about"> 1470<div id="about">
1424<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 1471<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
1425<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 1472<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
1426</div> <!-- id="about" --> 1473</div> <!-- id="about" -->
1427</div> <!-- id="container" --> 1474</div> <!-- id="container" -->
1428</body> 1475</body>
diff --git a/docs/topics/01-introduction.md.html b/docs/topics/01-introduction.md.html
index f0f627d..5c62ad3 100644
--- a/docs/topics/01-introduction.md.html
+++ b/docs/topics/01-introduction.md.html
@@ -84,7 +84,7 @@ independence.</p>
84</div> <!-- id="main" --> 84</div> <!-- id="main" -->
85<div id="about"> 85<div id="about">
86<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 86<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
87<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 87<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
88</div> <!-- id="about" --> 88</div> <!-- id="about" -->
89</div> <!-- id="container" --> 89</div> <!-- id="container" -->
90</body> 90</body>
diff --git a/docs/topics/02-development.md.html b/docs/topics/02-development.md.html
index 315fa37..197fde7 100644
--- a/docs/topics/02-development.md.html
+++ b/docs/topics/02-development.md.html
@@ -84,7 +84,7 @@ pass locally, and do not rely on CI only.</p>
84</div> <!-- id="main" --> 84</div> <!-- id="main" -->
85<div id="about"> 85<div id="about">
86<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 86<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
87<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 87<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
88</div> <!-- id="about" --> 88</div> <!-- id="about" -->
89</div> <!-- id="container" --> 89</div> <!-- id="container" -->
90</body> 90</body>
diff --git a/docs/topics/03-terminal.md.html b/docs/topics/03-terminal.md.html
index 7ce28af..84465af 100644
--- a/docs/topics/03-terminal.md.html
+++ b/docs/topics/03-terminal.md.html
@@ -93,6 +93,7 @@ up both platforms to make it work.</p>
93 <li><a href="../modules/system.html#setconsoleflags">system.setconsoleflags</a> (Windows)</li> 93 <li><a href="../modules/system.html#setconsoleflags">system.setconsoleflags</a> (Windows)</li>
94 <li><a href="../modules/system.html#setconsolecp">system.setconsolecp</a> (Windows)</li> 94 <li><a href="../modules/system.html#setconsolecp">system.setconsolecp</a> (Windows)</li>
95 <li><a href="../modules/system.html#setconsoleoutputcp">system.setconsoleoutputcp</a> (Windows)</li> 95 <li><a href="../modules/system.html#setconsoleoutputcp">system.setconsoleoutputcp</a> (Windows)</li>
96 <li><a href="../modules/system.html#detachfds">system.detachfds</a> (Posix)</li>
96 <li><a href="../modules/system.html#setnonblock">system.setnonblock</a> (Posix)</li> 97 <li><a href="../modules/system.html#setnonblock">system.setnonblock</a> (Posix)</li>
97 <li><a href="../modules/system.html#tcsetattr">system.tcsetattr</a> (Posix)</li> 98 <li><a href="../modules/system.html#tcsetattr">system.tcsetattr</a> (Posix)</li>
98</ul> 99</ul>
@@ -195,6 +196,7 @@ also not being echoed to the terminal (independent of the echo settings used wit
195</span>sys.<span class="function-name">setconsoleflags</span>(<span class="global">io</span>.stdin, sys.<span class="function-name">getconsoleflags</span>(<span class="global">io</span>.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT) 196</span>sys.<span class="function-name">setconsoleflags</span>(<span class="global">io</span>.stdin, sys.<span class="function-name">getconsoleflags</span>(<span class="global">io</span>.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT)
196 197
197<span class="comment">-- setup Posix by disabling echo, canonical mode, and making non-blocking 198<span class="comment">-- setup Posix by disabling echo, canonical mode, and making non-blocking
199</span>sys.<span class="function-name">detachfds</span>() <span class="comment">-- ensure stdin/out/err have their own file descriptions
198</span><span class="keyword">local</span> of_attr = sys.<span class="function-name">tcgetattr</span>(<span class="global">io</span>.stdin) 200</span><span class="keyword">local</span> of_attr = sys.<span class="function-name">tcgetattr</span>(<span class="global">io</span>.stdin)
199sys.<span class="function-name">tcsetattr</span>(<span class="global">io</span>.stdin, sys.TCSANOW, { 201sys.<span class="function-name">tcsetattr</span>(<span class="global">io</span>.stdin, sys.TCSANOW, {
200 lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, 202 lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO,
@@ -218,7 +220,7 @@ For an example see <a href="../examples/password_input.lua.html"><code>examples/
218</div> <!-- id="main" --> 220</div> <!-- id="main" -->
219<div id="about"> 221<div id="about">
220<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 222<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
221<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 223<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
222</div> <!-- id="about" --> 224</div> <!-- id="about" -->
223</div> <!-- id="container" --> 225</div> <!-- id="container" -->
224</body> 226</body>
diff --git a/docs/topics/CHANGELOG.md.html b/docs/topics/CHANGELOG.md.html
index fa2d706..8a26156 100644
--- a/docs/topics/CHANGELOG.md.html
+++ b/docs/topics/CHANGELOG.md.html
@@ -106,6 +106,28 @@
106<p><a name="Version_history"></a></p> 106<p><a name="Version_history"></a></p>
107<h2>Version history</h2> 107<h2>Version history</h2>
108 108
109<h3>version 0.6.2, released 15-Apr-2025</h3>
110
111<ul>
112 <li>Fix: autotermrestore didn't work because its metatable was overwritten.</li>
113</ul>
114
115<h3>version 0.6.1, released 13-Apr-2025</h3>
116
117<ul>
118 <li>Docs: document readansi internal buffer for incomplete sequences.</li>
119 <li>Fix: ensure to properly parse <code>&lt;alt&gt;+key</code> key presses</li>
120</ul>
121
122<h3>version 0.6.0, released 10-Apr-2025</h3>
123
124<ul>
125 <li>Fix: when sleep returns an error, pass that on in <code>readkey</code>.</li>
126 <li>Feat: added <code>detachfds</code> which will create separate file descriptions for <code>stdout</code>
127 and <code>stderr</code> to ensure that related settings (eg. non-blocking flag) will not be shared
128 amongst those streams and <code>stdin</code>.</li>
129</ul>
130
109<h3>version 0.5.1, released 12-Mar-2025</h3> 131<h3>version 0.5.1, released 12-Mar-2025</h3>
110 132
111<ul> 133<ul>
@@ -120,14 +142,14 @@
120 <li>Feat: allow passing in a sleep function to <code>readkey</code> and <code>readansi</code></li> 142 <li>Feat: allow passing in a sleep function to <code>readkey</code> and <code>readansi</code></li>
121 <li>Fix: NetBSD fix compilation, undeclared directives</li> 143 <li>Fix: NetBSD fix compilation, undeclared directives</li>
122 <li>Refactor: random bytes; remove deprecated API usage on Windows, move to 144 <li>Refactor: random bytes; remove deprecated API usage on Windows, move to
123 binary api instead of /dev/urandom file on linux and bsd</li> 145 binary api instead of <code>/dev/urandom</code> file on linux and bsd</li>
124</ul> 146</ul>
125 147
126<h3>version 0.4.5, released 18-Dec-2024</h3> 148<h3>version 0.4.5, released 18-Dec-2024</h3>
127 149
128<ul> 150<ul>
129 <li>Fix: suppress a warning when building with clang</li> 151 <li>Fix: suppress a warning when building with clang</li>
130 <li>Fix: do not rely on luaconf.h to include limits.h, fixes builds with latest LuaJIT (#38).</li> 152 <li>Fix: do not rely on <code>luaconf.h</code> to include <code>limits.h</code>, fixes builds with latest LuaJIT (#38).</li>
131</ul> 153</ul>
132 154
133<h3>version 0.4.4, released 03-Sep-2024</h3> 155<h3>version 0.4.4, released 03-Sep-2024</h3>
@@ -199,7 +221,7 @@
199</div> <!-- id="main" --> 221</div> <!-- id="main" -->
200<div id="about"> 222<div id="about">
201<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 223<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
202<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 224<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
203</div> <!-- id="about" --> 225</div> <!-- id="about" -->
204</div> <!-- id="container" --> 226</div> <!-- id="container" -->
205</body> 227</body>
diff --git a/docs/topics/LICENSE.md.html b/docs/topics/LICENSE.md.html
index 612cdb9..198c238 100644
--- a/docs/topics/LICENSE.md.html
+++ b/docs/topics/LICENSE.md.html
@@ -94,7 +94,7 @@ SOFTWARE.</p>
94</div> <!-- id="main" --> 94</div> <!-- id="main" -->
95<div id="about"> 95<div id="about">
96<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> 96<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
97<i style="float:right;">Last updated 2025-03-12 15:17:50 </i> 97<i style="float:right;">Last updated 2025-04-15 13:14:45 </i>
98</div> <!-- id="about" --> 98</div> <!-- id="about" -->
99</div> <!-- id="container" --> 99</div> <!-- id="container" -->
100</body> 100</body>
diff --git a/rockspecs/luasystem-0.6.0-1.rockspec b/rockspecs/luasystem-0.6.0-1.rockspec
new file mode 100644
index 0000000..21c2dc0
--- /dev/null
+++ b/rockspecs/luasystem-0.6.0-1.rockspec
@@ -0,0 +1,85 @@
1local package_name = "luasystem"
2local package_version = "0.6.0"
3local rockspec_revision = "1"
4local github_account_name = "lunarmodules"
5local github_repo_name = "luasystem"
6
7
8package = package_name
9version = package_version.."-"..rockspec_revision
10
11source = {
12 url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
13 branch = (package_version == "scm") and "master" or nil,
14 tag = (package_version ~= "scm") and "v"..package_version or nil,
15}
16
17description = {
18 summary = 'Platform independent system calls for Lua.',
19 detailed = [[
20 Adds a Lua API for making platform independent system calls.
21 ]],
22 license = 'MIT <http://opensource.org/licenses/MIT>',
23 homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
24}
25
26dependencies = {
27 'lua >= 5.1',
28}
29
30local function make_platform(plat)
31 local defines = {
32 linux = { },
33 unix = { },
34 macosx = { },
35 win32 = { "WINVER=0x0600", "_WIN32_WINNT=0x0600" },
36 mingw32 = { "WINVER=0x0600", "_WIN32_WINNT=0x0600" },
37 }
38 local libraries = {
39 linux = { "rt" },
40 unix = { },
41 macosx = { },
42 win32 = { "advapi32", "winmm", "bcrypt" },
43 mingw32 = { },
44 }
45 local libdirs = {
46 linux = nil,
47 unix = nil,
48 macosx = nil,
49 win32 = nil,
50 mingw32 = { },
51 }
52 return {
53 modules = {
54 ['system.core'] = {
55 sources = {
56 'src/core.c',
57 'src/compat.c',
58 'src/time.c',
59 'src/environment.c',
60 'src/random.c',
61 'src/term.c',
62 'src/bitflags.c',
63 'src/wcwidth.c',
64 },
65 defines = defines[plat],
66 libraries = libraries[plat],
67 libdirs = libdirs[plat],
68 },
69 },
70 }
71end
72
73build = {
74 type = 'builtin',
75 platforms = {
76 linux = make_platform('linux'),
77 unix = make_platform('unix'),
78 macosx = make_platform('macosx'),
79 win32 = make_platform('win32'),
80 mingw32 = make_platform('mingw32'),
81 },
82 modules = {
83 ['system.init'] = 'system/init.lua',
84 },
85}
diff --git a/rockspecs/luasystem-0.6.1-1.rockspec b/rockspecs/luasystem-0.6.1-1.rockspec
new file mode 100644
index 0000000..51bbddd
--- /dev/null
+++ b/rockspecs/luasystem-0.6.1-1.rockspec
@@ -0,0 +1,85 @@
1local package_name = "luasystem"
2local package_version = "0.6.1"
3local rockspec_revision = "1"
4local github_account_name = "lunarmodules"
5local github_repo_name = "luasystem"
6
7
8package = package_name
9version = package_version.."-"..rockspec_revision
10
11source = {
12 url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
13 branch = (package_version == "scm") and "master" or nil,
14 tag = (package_version ~= "scm") and "v"..package_version or nil,
15}
16
17description = {
18 summary = 'Platform independent system calls for Lua.',
19 detailed = [[
20 Adds a Lua API for making platform independent system calls.
21 ]],
22 license = 'MIT <http://opensource.org/licenses/MIT>',
23 homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
24}
25
26dependencies = {
27 'lua >= 5.1',
28}
29
30local function make_platform(plat)
31 local defines = {
32 linux = { },
33 unix = { },
34 macosx = { },
35 win32 = { "WINVER=0x0600", "_WIN32_WINNT=0x0600" },
36 mingw32 = { "WINVER=0x0600", "_WIN32_WINNT=0x0600" },
37 }
38 local libraries = {
39 linux = { "rt" },
40 unix = { },
41 macosx = { },
42 win32 = { "advapi32", "winmm", "bcrypt" },
43 mingw32 = { },
44 }
45 local libdirs = {
46 linux = nil,
47 unix = nil,
48 macosx = nil,
49 win32 = nil,
50 mingw32 = { },
51 }
52 return {
53 modules = {
54 ['system.core'] = {
55 sources = {
56 'src/core.c',
57 'src/compat.c',
58 'src/time.c',
59 'src/environment.c',
60 'src/random.c',
61 'src/term.c',
62 'src/bitflags.c',
63 'src/wcwidth.c',
64 },
65 defines = defines[plat],
66 libraries = libraries[plat],
67 libdirs = libdirs[plat],
68 },
69 },
70 }
71end
72
73build = {
74 type = 'builtin',
75 platforms = {
76 linux = make_platform('linux'),
77 unix = make_platform('unix'),
78 macosx = make_platform('macosx'),
79 win32 = make_platform('win32'),
80 mingw32 = make_platform('mingw32'),
81 },
82 modules = {
83 ['system.init'] = 'system/init.lua',
84 },
85}
diff --git a/rockspecs/luasystem-0.6.2-1.rockspec b/rockspecs/luasystem-0.6.2-1.rockspec
new file mode 100644
index 0000000..7a8549f
--- /dev/null
+++ b/rockspecs/luasystem-0.6.2-1.rockspec
@@ -0,0 +1,85 @@
1local package_name = "luasystem"
2local package_version = "0.6.2"
3local rockspec_revision = "1"
4local github_account_name = "lunarmodules"
5local github_repo_name = "luasystem"
6
7
8package = package_name
9version = package_version.."-"..rockspec_revision
10
11source = {
12 url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
13 branch = (package_version == "scm") and "master" or nil,
14 tag = (package_version ~= "scm") and "v"..package_version or nil,
15}
16
17description = {
18 summary = 'Platform independent system calls for Lua.',
19 detailed = [[
20 Adds a Lua API for making platform independent system calls.
21 ]],
22 license = 'MIT <http://opensource.org/licenses/MIT>',
23 homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
24}
25
26dependencies = {
27 'lua >= 5.1',
28}
29
30local function make_platform(plat)
31 local defines = {
32 linux = { },
33 unix = { },
34 macosx = { },
35 win32 = { "WINVER=0x0600", "_WIN32_WINNT=0x0600" },
36 mingw32 = { "WINVER=0x0600", "_WIN32_WINNT=0x0600" },
37 }
38 local libraries = {
39 linux = { "rt" },
40 unix = { },
41 macosx = { },
42 win32 = { "advapi32", "winmm", "bcrypt" },
43 mingw32 = { },
44 }
45 local libdirs = {
46 linux = nil,
47 unix = nil,
48 macosx = nil,
49 win32 = nil,
50 mingw32 = { },
51 }
52 return {
53 modules = {
54 ['system.core'] = {
55 sources = {
56 'src/core.c',
57 'src/compat.c',
58 'src/time.c',
59 'src/environment.c',
60 'src/random.c',
61 'src/term.c',
62 'src/bitflags.c',
63 'src/wcwidth.c',
64 },
65 defines = defines[plat],
66 libraries = libraries[plat],
67 libdirs = libdirs[plat],
68 },
69 },
70 }
71end
72
73build = {
74 type = 'builtin',
75 platforms = {
76 linux = make_platform('linux'),
77 unix = make_platform('unix'),
78 macosx = make_platform('macosx'),
79 win32 = make_platform('win32'),
80 mingw32 = make_platform('mingw32'),
81 },
82 modules = {
83 ['system.init'] = 'system/init.lua',
84 },
85}
diff --git a/spec/04-term_spec.lua b/spec/04-term_spec.lua
index 907f903..0ce0033 100644
--- a/spec/04-term_spec.lua
+++ b/spec/04-term_spec.lua
@@ -855,6 +855,32 @@ describe("Terminal:", function()
855 assert.is.near(1, timing, 0.5) -- this also works for MacOS in CI 855 assert.is.near(1, timing, 0.5) -- this also works for MacOS in CI
856 end) 856 end)
857 857
858
859 it("calls flseep to execute the sleep", function()
860 setbuffer("")
861
862 local sleep_called = false
863 local mysleep = function()
864 sleep_called = true
865 end
866
867 system.readkey(0.01, mysleep)
868 assert.is_true(sleep_called)
869 end)
870
871
872 it("returns errors by fsleep", function()
873 setbuffer("")
874
875 local mysleep = function()
876 return nil, "boom!"
877 end
878
879 local ok, err = system.readkey(1, mysleep)
880 assert.is.falsy(ok)
881 assert.equals("boom!", err)
882 end)
883
858 end) 884 end)
859 885
860 886
@@ -902,10 +928,31 @@ describe("Terminal:", function()
902 end) 928 end)
903 929
904 930
931 it("reads ANSI escape sequences, just an '<esc>O' (<alt><O> key press)", function()
932 setbuffer("\27O")
933 assert.are.same({"\27O", "ansi"}, {system.readansi(0)})
934 end)
935
936
937 it("reads <alt>+key key presses; <esc>+<key>", function()
938 setbuffer("\27a\27b\27c\27d")
939 assert.are.same({"\27a", "ansi"}, {system.readansi(0)})
940 assert.are.same({"\27b", "ansi"}, {system.readansi(0)})
941 assert.are.same({"\27c", "ansi"}, {system.readansi(0)})
942 assert.are.same({"\27d", "ansi"}, {system.readansi(0)})
943 end)
944
945
946 it("reads <ctrl><alt>[ key press; <esc>+<esc>", function()
947 setbuffer("\27\27\27\27")
948 assert.are.same({"\27\27", "ansi"}, {system.readansi(0)})
949 assert.are.same({"\27\27", "ansi"}, {system.readansi(0)})
950 end)
951
952
905 it("returns a single <esc> character if no sequence is found", function() 953 it("returns a single <esc> character if no sequence is found", function()
906 setbuffer("\27\27[A") 954 setbuffer("\27")
907 assert.are.same({"\27", "char"}, {system.readansi(0)}) 955 assert.are.same({"\27", "char"}, {system.readansi(0)})
908 assert.are.same({"\27[A", "ansi"}, {system.readansi(0)})
909 end) 956 end)
910 957
911 958
@@ -919,6 +966,22 @@ describe("Terminal:", function()
919 assert.is.near(1, timing, 0.5) -- this also works for MacOS in CI 966 assert.is.near(1, timing, 0.5) -- this also works for MacOS in CI
920 end) 967 end)
921 968
969
970 it("incomplete ANSI sequences will be completed on next call", function()
971 setbuffer("\27[")
972 assert.are.same({nil, "timeout", "\27["}, {system.readansi(0)})
973 setbuffer("A")
974 assert.are.same({"\27[A", "ansi"}, {system.readansi(0)})
975 end)
976
977
978 it("incomplete UTF8 sequences will be completed on next call", function()
979 setbuffer(string.char(240, 159))
980 assert.are.same({nil, "timeout", string.char(240, 159)}, {system.readansi(0)})
981 setbuffer(string.char(154, 128))
982 assert.are.same({"🚀", "char"}, {system.readansi(0)})
983 end)
984
922 end) 985 end)
923 986
924 end) 987 end)
diff --git a/src/core.c b/src/core.c
index b642074..7776de4 100644
--- a/src/core.c
+++ b/src/core.c
@@ -4,7 +4,7 @@
4#include <lua.h> 4#include <lua.h>
5#include <lauxlib.h> 5#include <lauxlib.h>
6 6
7#define LUASYSTEM_VERSION "LuaSystem 0.5.1" 7#define LUASYSTEM_VERSION "LuaSystem 0.6.2"
8 8
9#ifdef _WIN32 9#ifdef _WIN32
10#define LUAEXPORT __declspec(dllexport) 10#define LUAEXPORT __declspec(dllexport)
diff --git a/src/term.c b/src/term.c
index 4cfce95..792832d 100644
--- a/src/term.c
+++ b/src/term.c
@@ -649,8 +649,104 @@ static int lst_tcsetattr(lua_State *L)
649 649
650 650
651 651
652#ifndef _WIN32
653/*
654reopen FDs for independent file descriptions.
655*/
656static void reopen_fd(lua_State *L, int fd, int flags) {
657 char path[64];
658 int newfd = -1;
659
660 if (fd != STDOUT_FILENO && fd != STDERR_FILENO) {
661 luaL_error(L, "Invalid file descriptor: %d. Only stdout (1) and stderr (2) are supported.", fd);
662 }
663
664 const char *fallback_path = (fd == STDOUT_FILENO) ? "/dev/stdout" :
665 (fd == STDERR_FILENO) ? "/dev/stderr" : NULL;
666
667 // If fd is a terminal, reopen its actual device (e.g. /dev/ttys003)
668 // Works on all POSIX platforms that have terminals (macOS, Linux, BSD, etc.)
669 if (isatty(fd)) {
670 const char *tty = ttyname(fd);
671 if (tty) {
672 newfd = open(tty, flags);
673 }
674 }
675
676 if (newfd < 0) {
677 // For non-tty: try /dev/fd/N — POSIX-compliant and standard on macOS, Linux, BSD.
678 // This gives a new file description even if the target is a file or pipe.
679 snprintf(path, sizeof(path), "/dev/fd/%d", fd);
680 newfd = open(path, flags);
681 }
682
683 if (newfd < 0) {
684 // Fallback: for platforms/environments where /dev/fd/N doesn't exist.
685 // /dev/stdout and /dev/stderr are standard on Linux, but may not create new descriptions.
686 if (fallback_path) {
687 newfd = open(fallback_path, flags);
688 }
689 }
690
691 if (newfd < 0) {
692 // All attempts failed — raise error with detailed info (call will not return)
693 luaL_error(L, "Failed to reopen fd %d: tried ttyname(), /dev/fd/%d, and fallback %s: %s",
694 fd, fd, fallback_path ? fallback_path : "(none)", strerror(errno));
695 }
696
697 // Replace the original fd with the new one
698 if (dup2(newfd, fd) < 0) {
699 close(newfd);
700 luaL_error(L, "dup2 failed for fd %d: %s", fd, strerror(errno));
701 }
702
703 close(newfd); // Close the new fd, as dup2 has replaced the original fd with it
704}
705#endif
706
707
708
709/***
710Creates new file descriptions for `stdout` and `stderr`.
711Even if the file descriptors are unique, they still might point to the same
712file description, and hence share settings like `O_NONBLOCK`. This means that
713if one of them is set to non-blocking, the other will be as well. This can
714lead to unexpected behavior.
715
716This function is used to detach `stdout` and `stderr` from the original
717file descriptions, and create new file descriptions for them. This allows
718independent control of flags (e.g., `O_NONBLOCK`) on `stdout` and `stderr`,
719avoiding shared side effects.
720
721Does not modify `stdin` (fd 0), and does nothing on Windows.
722@function detachfds
723@return boolean `true` on success, or throws an error on failure.
724@see setnonblock
725*/
726static int lst_detachfds(lua_State *L) {
727 static int already_detached = 0; // detaching is once per process(not per thread or Lua state)
728 if (already_detached) {
729 lua_pushnil(L);
730 lua_pushliteral(L, "stdout and stderr already detached");
731 return 1;
732 }
733 already_detached = 1;
734
735#ifndef _WIN32
736 // Reopen stdout and stderr with new file descriptions
737 reopen_fd(L, STDOUT_FILENO, O_WRONLY);
738 reopen_fd(L, STDERR_FILENO, O_WRONLY);
739#endif
740
741 lua_pushboolean(L, 1);
742 return 1;
743}
744
745
746
652/*** 747/***
653Enables or disables non-blocking mode for a file (Posix). 748Enables or disables non-blocking mode for a file (Posix).
749Check `detachfds` in case there are shared file descriptions.
654@function setnonblock 750@function setnonblock
655@tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` 751@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. 752@tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it.
@@ -659,8 +755,10 @@ Enables or disables non-blocking mode for a file (Posix).
659@treturn[2] string error message 755@treturn[2] string error message
660@treturn[2] int errnum 756@treturn[2] int errnum
661@see getnonblock 757@see getnonblock
758@see detachfds
662@usage 759@usage
663local sys = require('system') 760local sys = require('system')
761sys.detachfds() -- detach stdout and stderr, so only stdin becomes non-blocking
664 762
665-- set io.stdin to non-blocking mode 763-- set io.stdin to non-blocking mode
666local old_setting = sys.getnonblock(io.stdin) 764local old_setting = sys.getnonblock(io.stdin)
@@ -717,6 +815,7 @@ Gets non-blocking mode status for a file (Posix).
717@treturn[2] nil 815@treturn[2] nil
718@treturn[2] string error message 816@treturn[2] string error message
719@treturn[2] int errnum 817@treturn[2] int errnum
818@see setnonblock
720*/ 819*/
721static int lst_getnonblock(lua_State *L) 820static int lst_getnonblock(lua_State *L)
722{ 821{
@@ -1157,6 +1256,7 @@ static luaL_Reg func[] = {
1157 { "setconsoleflags", lst_setconsoleflags }, 1256 { "setconsoleflags", lst_setconsoleflags },
1158 { "tcgetattr", lst_tcgetattr }, 1257 { "tcgetattr", lst_tcgetattr },
1159 { "tcsetattr", lst_tcsetattr }, 1258 { "tcsetattr", lst_tcsetattr },
1259 { "detachfds", lst_detachfds },
1160 { "getnonblock", lst_getnonblock }, 1260 { "getnonblock", lst_getnonblock },
1161 { "setnonblock", lst_setnonblock }, 1261 { "setnonblock", lst_setnonblock },
1162 { "_readkey", lst_readkey }, 1262 { "_readkey", lst_readkey },
diff --git a/system/init.lua b/system/init.lua
index a81978e..a9f57e9 100644
--- a/system/init.lua
+++ b/system/init.lua
@@ -13,14 +13,16 @@ local system = require 'system.core'
13system.CODEPAGE_UTF8 = 65001 13system.CODEPAGE_UTF8 = 65001
14 14
15do 15do
16 local backup_mt = {} 16 local backup_indicator = {}
17 17
18 --- Returns a backup of terminal settings for stdin/out/err. 18 --- Returns a backup of terminal settings for stdin/out/err.
19 -- Handles terminal/console flags, Windows codepage, and non-block flags on the streams. 19 -- Handles terminal/console flags, Windows codepage, and non-block flags on the streams.
20 -- Backs up terminal/console flags only if a stream is a tty. 20 -- Backs up terminal/console flags only if a stream is a tty.
21 -- @return table with backup of terminal settings 21 -- @return table with backup of terminal settings
22 function system.termbackup() 22 function system.termbackup()
23 local backup = setmetatable({}, backup_mt) 23 local backup = {
24 __type = backup_indicator, -- cannot set a metatable, since autotermrestore uses it for GC
25 }
24 26
25 if system.isatty(io.stdin) then 27 if system.isatty(io.stdin) then
26 backup.console_in = system.getconsoleflags(io.stdin) 28 backup.console_in = system.getconsoleflags(io.stdin)
@@ -51,7 +53,7 @@ do
51 -- @tparam table backup the backup of terminal settings, see `termbackup`. 53 -- @tparam table backup the backup of terminal settings, see `termbackup`.
52 -- @treturn boolean true 54 -- @treturn boolean true
53 function system.termrestore(backup) 55 function system.termrestore(backup)
54 if getmetatable(backup) ~= backup_mt then 56 if type(backup) ~= "table" or backup.__type ~= backup_indicator then
55 error("arg #1 to termrestore, expected backup table, got " .. type(backup), 2) 57 error("arg #1 to termrestore, expected backup table, got " .. type(backup), 2)
56 end 58 end
57 59
@@ -236,10 +238,10 @@ do
236 -- Using `system.readansi` is preferred over this function. Since this function can leave stray/invalid 238 -- Using `system.readansi` is preferred over this function. Since this function can leave stray/invalid
237 -- byte-sequences in the input buffer, while `system.readansi` reads full ANSI and UTF8 sequences. 239 -- byte-sequences in the input buffer, while `system.readansi` reads full ANSI and UTF8 sequences.
238 -- @tparam number timeout the timeout in seconds. 240 -- @tparam number timeout the timeout in seconds.
239 -- @tparam[opt=system.sleep] function fsleep the function to call for sleeping. 241 -- @tparam[opt=system.sleep] function fsleep the function to call for sleeping; `ok, err = fsleep(secs)`
240 -- @treturn[1] byte the byte value that was read. 242 -- @treturn[1] byte the byte value that was read.
241 -- @treturn[2] nil if no key was read 243 -- @treturn[2] nil if no key was read
242 -- @treturn[2] string error message; `"timeout"` if the timeout was reached. 244 -- @treturn[2] string error message when the timeout was reached (`"timeout"`), or if `sleep` failed.
243 function system.readkey(timeout, fsleep) 245 function system.readkey(timeout, fsleep)
244 if type(timeout) ~= "number" then 246 if type(timeout) ~= "number" then
245 error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2) 247 error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2)
@@ -248,7 +250,10 @@ do
248 local interval = 0.0125 250 local interval = 0.0125
249 local key = system._readkey() 251 local key = system._readkey()
250 while key == nil and timeout > 0 do 252 while key == nil and timeout > 0 do
251 (fsleep or system.sleep)(math.min(interval, timeout)) 253 local ok, err = (fsleep or system.sleep)(math.min(interval, timeout))
254 if not ok then
255 return nil, err
256 end
252 timeout = timeout - interval 257 timeout = timeout - interval
253 interval = math.min(0.2, interval * 2) 258 interval = math.min(0.2, interval * 2)
254 key = system._readkey() 259 key = system._readkey()
@@ -264,7 +269,6 @@ end
264 269
265 270
266do 271do
267 local left_over_key
268 local sequence -- table to store the sequence in progress 272 local sequence -- table to store the sequence in progress
269 local utf8_length -- length of utf8 sequence currently being processed 273 local utf8_length -- length of utf8 sequence currently being processed
270 local unpack = unpack or table.unpack 274 local unpack = unpack or table.unpack
@@ -282,6 +286,8 @@ do
282 -- @treturn[2] nil in case of an error 286 -- @treturn[2] nil in case of an error
283 -- @treturn[2] string error message; `"timeout"` if the timeout was reached. 287 -- @treturn[2] string error message; `"timeout"` if the timeout was reached.
284 -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far. 288 -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far.
289 -- The function retains its own internal buffer, so on the next call the incomplete buffer is used to
290 -- complete the sequence.
285 function system.readansi(timeout, fsleep) 291 function system.readansi(timeout, fsleep)
286 if type(timeout) ~= "number" then 292 if type(timeout) ~= "number" then
287 error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2) 293 error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2)
@@ -292,33 +298,47 @@ do
292 298
293 if not sequence then 299 if not sequence then
294 -- no sequence in progress, read a key 300 -- no sequence in progress, read a key
295 301 local err
296 if left_over_key then 302 key, err = system.readkey(timeout, fsleep)
297 -- we still have a cached key from the last call 303 if key == nil then -- timeout or error
298 key = left_over_key 304 return nil, err
299 left_over_key = nil
300 else
301 -- read a new key
302 local err
303 key, err = system.readkey(timeout, fsleep)
304 if key == nil then -- timeout or error
305 return nil, err
306 end
307 end 305 end
308 306
309 if key == 27 then 307 if key == 27 then
310 -- looks like an ansi escape sequence, immediately read next char 308 -- looks like an ansi escape sequence, immediately read next char
311 -- as an heuristic against manually typing escape sequences 309 -- as an heuristic against manually typing escape sequences
312 local key2 = system.readkey(0, fsleep) 310 local key2 = system.readkey(0, fsleep)
313 if key2 ~= 91 and key2 ~= 79 then -- we expect either "[" or "O" for an ANSI sequence 311 if key2 == nil then
314 -- not the expected [ or O character, so we return the key as is 312 -- no key available, return the escape key, on its own
315 -- and store the extra key read for the next call 313 sequence = nil
316 left_over_key = key2
317 return string.char(key), "char" 314 return string.char(key), "char"
315
316 elseif key2 == 91 then
317 -- "[" means it is for sure an ANSI sequence
318 sequence = { key, key2 }
319
320 elseif key2 == 79 then
321 -- "O" means it is either an ANSI sequence or just an <alt>+O key stroke
322 -- check if there is yet another byte available
323 local key3 = system.readkey(0, fsleep)
324 if key3 == nil then
325 -- no key available, return the <alt>O key stroke, report as ANSI
326 sequence = nil
327 return string.char(key, key2), "ansi"
328 end
329 -- it's an ANSI sequence, marked with <ESC>O
330 if (key3 >= 65 and key3 <= 90) or (key3 >= 97 and key3 <= 126) then
331 -- end of sequence, return the full sequence
332 return string.char(key, key2, key3), "ansi"
333 end
334 sequence = { key, key2, key3 }
335
336 else
337 -- not an ANSI sequence, but an <alt>+<key2> key stroke, so report as ANSI
338 sequence = nil
339 return string.char(key, key2), "ansi"
318 end 340 end
319 341
320 -- escape sequence detected
321 sequence = { key, key2 }
322 else 342 else
323 -- check UTF8 length 343 -- check UTF8 length
324 utf8_length = key < 128 and 1 or key < 224 and 2 or key < 240 and 3 or key < 248 and 4 344 utf8_length = key < 128 and 1 or key < 224 and 2 or key < 240 and 3 or key < 248 and 4