diff options
author | Thijs <thijs@thijsschreijer.nl> | 2023-11-16 09:09:54 +0100 |
---|---|---|
committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2024-04-30 09:28:01 +0200 |
commit | bd994461ef7c2553da9a6945c685152bad50eb8f (patch) | |
tree | 28adc32712f00a200a34357e731a570bf1a359dc | |
parent | 47c24eed0191f8f72646be63dee94ac2b35eb062 (diff) | |
download | luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.tar.gz luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.tar.bz2 luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.zip |
feat(term): getting/setting terminal config flags
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | config.ld | 2 | ||||
-rw-r--r-- | examples/compat.lua | 37 | ||||
-rw-r--r-- | examples/flag_debugging.lua | 7 | ||||
-rw-r--r-- | examples/password_input.lua | 59 | ||||
-rw-r--r-- | examples/read.lua | 119 | ||||
-rw-r--r-- | examples/spinner.lua | 64 | ||||
-rw-r--r-- | examples/spiral_snake.lua | 72 | ||||
-rw-r--r-- | luasystem-scm-0.rockspec | 1 | ||||
-rw-r--r-- | spec/04-term_spec.lua | 20 | ||||
-rw-r--r-- | spec/05-bitflags_spec.lua | 108 | ||||
-rw-r--r-- | src/bitflags.c | 235 | ||||
-rw-r--r-- | src/bitflags.h | 21 | ||||
-rw-r--r-- | src/compat.h | 11 | ||||
-rw-r--r-- | src/core.c | 2 | ||||
-rw-r--r-- | src/term.c | 822 | ||||
-rw-r--r-- | system/init.lua | 212 |
17 files changed, 1794 insertions, 8 deletions
@@ -45,3 +45,13 @@ install-all: | |||
45 | @cd src && $(MAKE) install LUA_VERSION=5.3 | 45 | @cd src && $(MAKE) install LUA_VERSION=5.3 |
46 | 46 | ||
47 | .PHONY: test | 47 | .PHONY: test |
48 | test: | ||
49 | busted | ||
50 | |||
51 | .PHONY: lint | ||
52 | lint: | ||
53 | luacheck . | ||
54 | |||
55 | .PHONY: docs | ||
56 | docs: | ||
57 | ldoc . | ||
@@ -8,7 +8,7 @@ style="./doc_topics/" | |||
8 | 8 | ||
9 | file={'./src/', './system/'} | 9 | file={'./src/', './system/'} |
10 | topics={'./doc_topics/', './LICENSE.md', './CHANGELOG.md'} | 10 | topics={'./doc_topics/', './LICENSE.md', './CHANGELOG.md'} |
11 | -- examples = {'./examples'} | 11 | examples = {'./examples'} |
12 | 12 | ||
13 | dir='docs' | 13 | dir='docs' |
14 | sort=true | 14 | sort=true |
diff --git a/examples/compat.lua b/examples/compat.lua new file mode 100644 index 0000000..c00d44a --- /dev/null +++ b/examples/compat.lua | |||
@@ -0,0 +1,37 @@ | |||
1 | -- This example shows how to remove platform differences to create a | ||
2 | -- cross-platform level playing field. | ||
3 | |||
4 | local sys = require "system" | ||
5 | |||
6 | |||
7 | |||
8 | if sys.is_windows then | ||
9 | -- Windows holds multiple copies of environment variables, to ensure `getenv` | ||
10 | -- returns what `setenv` sets we need to use the `system.getenv` instead of | ||
11 | -- `os.getenv`. | ||
12 | os.getenv = sys.getenv -- luacheck: ignore | ||
13 | |||
14 | -- Set up the terminal to handle ANSI escape sequences on Windows. | ||
15 | if sys.isatty(io.stdout) then | ||
16 | sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
17 | end | ||
18 | if sys.isatty(io.stderr) then | ||
19 | sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
20 | end | ||
21 | if sys.isatty(io.stdin) then | ||
22 | sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdout) + sys.ENABLE_VIRTUAL_TERMINAL_INPUT) | ||
23 | end | ||
24 | |||
25 | |||
26 | else | ||
27 | -- On Posix, one can set a variable to an empty string, but on Windows, this | ||
28 | -- will remove the variable from the environment. To make this consistent | ||
29 | -- across platforms, we will remove the variable from the environment if the | ||
30 | -- value is an empty string. | ||
31 | local old_setenv = sys.setenv | ||
32 | function sys.setenv(name, value) | ||
33 | if value == "" then value = nil end | ||
34 | return old_setenv(name, value) | ||
35 | end | ||
36 | end | ||
37 | |||
diff --git a/examples/flag_debugging.lua b/examples/flag_debugging.lua new file mode 100644 index 0000000..5f1d496 --- /dev/null +++ b/examples/flag_debugging.lua | |||
@@ -0,0 +1,7 @@ | |||
1 | local sys = require "system" | ||
2 | |||
3 | -- Print the Windows Console flags for stdin | ||
4 | sys.listconsoleflags(io.stdin) | ||
5 | |||
6 | -- Print the Posix termios flags for stdin | ||
7 | sys.listtermflags(io.stdin) | ||
diff --git a/examples/password_input.lua b/examples/password_input.lua new file mode 100644 index 0000000..2994062 --- /dev/null +++ b/examples/password_input.lua | |||
@@ -0,0 +1,59 @@ | |||
1 | local sys = require "system" | ||
2 | |||
3 | print [[ | ||
4 | |||
5 | This example shows how to disable the "echo" of characters read to the console, | ||
6 | useful for reading secrets from the user. | ||
7 | |||
8 | ]] | ||
9 | |||
10 | --- Function to read from stdin without echoing the input (for secrets etc). | ||
11 | -- It will (in a platform agnostic way) disable echo on the terminal, read the | ||
12 | -- input, and then re-enable echo. | ||
13 | -- @param ... Arguments to pass to `io.stdin:read()` | ||
14 | -- @return the results of `io.stdin:read(...)` | ||
15 | local function read_secret(...) | ||
16 | local w_oldflags, p_oldflags | ||
17 | |||
18 | if sys.isatty(io.stdin) then | ||
19 | -- backup settings, configure echo flags | ||
20 | w_oldflags = sys.getconsoleflags(io.stdin) | ||
21 | p_oldflags = sys.tcgetattr(io.stdin) | ||
22 | -- set echo off to not show password on screen | ||
23 | assert(sys.setconsoleflags(io.stdin, w_oldflags - sys.CIF_ECHO_INPUT)) | ||
24 | assert(sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = p_oldflags.lflag - sys.L_ECHO })) | ||
25 | end | ||
26 | |||
27 | local secret, err = io.stdin:read(...) | ||
28 | |||
29 | -- restore settings | ||
30 | if sys.isatty(io.stdin) then | ||
31 | io.stdout:write("\n") -- Add newline after reading the password | ||
32 | sys.setconsoleflags(io.stdin, w_oldflags) | ||
33 | sys.tcsetattr(io.stdin, sys.TCSANOW, p_oldflags) | ||
34 | end | ||
35 | |||
36 | return secret, err | ||
37 | end | ||
38 | |||
39 | |||
40 | |||
41 | -- Get username | ||
42 | io.write("Username: ") | ||
43 | local username = io.stdin:read("*l") | ||
44 | |||
45 | -- Get the secret | ||
46 | io.write("Password: ") | ||
47 | local password = read_secret("*l") | ||
48 | |||
49 | -- Get domainname | ||
50 | io.write("Domain : ") | ||
51 | local domain = io.stdin:read("*l") | ||
52 | |||
53 | |||
54 | -- Print the results | ||
55 | print("") | ||
56 | print("Here's what we got:") | ||
57 | print(" username: " .. username) | ||
58 | print(" password: " .. password) | ||
59 | print(" domain : " .. domain) | ||
diff --git a/examples/read.lua b/examples/read.lua new file mode 100644 index 0000000..7a1c747 --- /dev/null +++ b/examples/read.lua | |||
@@ -0,0 +1,119 @@ | |||
1 | local sys = require "system" | ||
2 | |||
3 | print [[ | ||
4 | |||
5 | This example shows how to do a non-blocking read from the cli. | ||
6 | |||
7 | ]] | ||
8 | |||
9 | -- setup Windows console to handle ANSI processing | ||
10 | local of_in = sys.getconsoleflags(io.stdin) | ||
11 | local of_out = sys.getconsoleflags(io.stdout) | ||
12 | sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
13 | sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT) | ||
14 | |||
15 | -- setup Posix terminal to use non-blocking mode, and disable line-mode | ||
16 | local of_attr = sys.tcgetattr(io.stdin) | ||
17 | local of_block = sys.getnonblock(io.stdin) | ||
18 | sys.setnonblock(io.stdin, true) | ||
19 | sys.tcsetattr(io.stdin, sys.TCSANOW, { | ||
20 | lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, -- disable canonical mode and echo | ||
21 | }) | ||
22 | |||
23 | -- cursor sequences | ||
24 | local get_cursor_pos = "\27[6n" | ||
25 | |||
26 | |||
27 | |||
28 | local read_input do | ||
29 | local left_over_key | ||
30 | |||
31 | -- Reads a single key, if it is a 27 (start of ansi escape sequence) then it reads | ||
32 | -- the rest of the sequence. | ||
33 | -- This function is non-blocking, and will return nil if no key is available. | ||
34 | -- In case of an ANSI sequence, it will return the full sequence as a string. | ||
35 | -- @return nil|string the key read, or nil if no key is available | ||
36 | function read_input() | ||
37 | if left_over_key then | ||
38 | -- we still have a cached key, return it | ||
39 | local key = left_over_key | ||
40 | left_over_key = nil | ||
41 | return string.char(key) | ||
42 | end | ||
43 | |||
44 | local key = sys.readkey() | ||
45 | if key == nil then | ||
46 | return nil | ||
47 | end | ||
48 | |||
49 | if key ~= 27 then | ||
50 | return string.char(key) | ||
51 | end | ||
52 | |||
53 | -- looks like an ansi escape sequence, immediately read next char | ||
54 | -- as an heuristic against manually typing escape sequences | ||
55 | local brack = sys.readkey() | ||
56 | if brack ~= 91 then | ||
57 | -- not the expected [ character, so we return the key as is | ||
58 | -- and store the extra key read for the next call | ||
59 | left_over_key = brack | ||
60 | return string.char(key) | ||
61 | end | ||
62 | |||
63 | -- escape sequence detected, read the rest of the sequence | ||
64 | local seq = { key, brack } | ||
65 | while true do | ||
66 | key = sys.readkey() | ||
67 | table.insert(seq, key) | ||
68 | if (key >= 65 and key <= 90) or (key >= 97 and key <= 126) then | ||
69 | -- end of sequence, return the full sequence | ||
70 | return string.char((unpack or table.unpack)(seq)) | ||
71 | end | ||
72 | end | ||
73 | -- unreachable | ||
74 | end | ||
75 | end | ||
76 | |||
77 | |||
78 | |||
79 | print("Press a key, or 'A' to get cursor position, 'ESC' to exit") | ||
80 | while true do | ||
81 | local key | ||
82 | |||
83 | -- wait for a key, and sleep a bit to not do a busy-wait | ||
84 | while not key do | ||
85 | key = read_input() | ||
86 | if not key then sys.sleep(0.1) end | ||
87 | end | ||
88 | |||
89 | if key == "A" then io.write(get_cursor_pos); io.flush() end | ||
90 | |||
91 | -- check if we got a key or ANSI sequence | ||
92 | if #key == 1 then | ||
93 | -- just a key | ||
94 | local b = key:byte() | ||
95 | if b < 32 then | ||
96 | key = "." -- replace control characters with a simple "." to not mess up the screen | ||
97 | end | ||
98 | |||
99 | print("you pressed: " .. key .. " (" .. b .. ")") | ||
100 | if b == 27 then | ||
101 | print("Escape pressed, exiting") | ||
102 | break | ||
103 | end | ||
104 | |||
105 | else | ||
106 | -- we got an ANSI sequence | ||
107 | local seq = { key:byte(1, #key) } | ||
108 | print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")") | ||
109 | end | ||
110 | end | ||
111 | |||
112 | |||
113 | |||
114 | -- Clean up afterwards | ||
115 | sys.setnonblock(io.stdin, false) | ||
116 | sys.setconsoleflags(io.stdout, of_out) | ||
117 | sys.setconsoleflags(io.stdin, of_in) | ||
118 | sys.tcsetattr(io.stdin, sys.TCSANOW, of_attr) | ||
119 | sys.setnonblock(io.stdin, of_block) | ||
diff --git a/examples/spinner.lua b/examples/spinner.lua new file mode 100644 index 0000000..5526adc --- /dev/null +++ b/examples/spinner.lua | |||
@@ -0,0 +1,64 @@ | |||
1 | local sys = require("system") | ||
2 | |||
3 | print [[ | ||
4 | |||
5 | An example to display a spinner, whilst a long running task executes. | ||
6 | |||
7 | ]] | ||
8 | |||
9 | |||
10 | -- start make backup, to auto-restore on exit | ||
11 | sys.autotermrestore() | ||
12 | -- configure console | ||
13 | sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT) | ||
14 | local of = sys.tcgetattr(io.stdin) | ||
15 | sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = of.lflag - sys.L_ICANON - sys.L_ECHO }) | ||
16 | sys.setnonblock(io.stdin, true) | ||
17 | |||
18 | |||
19 | |||
20 | local function hideCursor() | ||
21 | io.write("\27[?25l") | ||
22 | io.flush() | ||
23 | end | ||
24 | |||
25 | local function showCursor() | ||
26 | io.write("\27[?25h") | ||
27 | io.flush() | ||
28 | end | ||
29 | |||
30 | local function left(n) | ||
31 | io.write("\27[",n or 1,"D") | ||
32 | io.flush() | ||
33 | end | ||
34 | |||
35 | |||
36 | |||
37 | local spinner do | ||
38 | local spin = [[|/-\]] | ||
39 | local i = 1 | ||
40 | spinner = function() | ||
41 | hideCursor() | ||
42 | io.write(spin:sub(i, i)) | ||
43 | left() | ||
44 | i = i + 1 | ||
45 | if i > #spin then i = 1 end | ||
46 | |||
47 | if sys.keypressed() then | ||
48 | sys.readkey() -- consume key pressed | ||
49 | io.write(" "); | ||
50 | left() | ||
51 | showCursor() | ||
52 | return true | ||
53 | else | ||
54 | return false | ||
55 | end | ||
56 | end | ||
57 | end | ||
58 | |||
59 | io.stdout:write("press any key to stop the spinner... ") | ||
60 | while not spinner() do | ||
61 | sys.sleep(0.1) | ||
62 | end | ||
63 | |||
64 | print("Done!") | ||
diff --git a/examples/spiral_snake.lua b/examples/spiral_snake.lua new file mode 100644 index 0000000..84a2040 --- /dev/null +++ b/examples/spiral_snake.lua | |||
@@ -0,0 +1,72 @@ | |||
1 | local sys = require "system" | ||
2 | |||
3 | print [[ | ||
4 | |||
5 | This example will draw a snake like spiral on the screen. Showing ANSI escape | ||
6 | codes for moving the cursor around. | ||
7 | |||
8 | ]] | ||
9 | |||
10 | -- backup term settings with auto-restore on exit | ||
11 | sys.autotermrestore() | ||
12 | |||
13 | -- setup Windows console to handle ANSI processing | ||
14 | sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
15 | |||
16 | -- start drawing the spiral. | ||
17 | -- start from current pos, then right, then up, then left, then down, and again. | ||
18 | local x, y = 1, 1 -- current position | ||
19 | local dx, dy = 1, 0 -- direction after each step | ||
20 | local wx, wy = 30, 30 -- width and height of the room | ||
21 | local mx, my = 1, 1 -- margin | ||
22 | |||
23 | -- commands to move the cursor | ||
24 | local move_left = "\27[1D" | ||
25 | local move_right = "\27[1C" | ||
26 | local move_up = "\27[1A" | ||
27 | local move_down = "\27[1B" | ||
28 | |||
29 | -- create room: 30 empty lines | ||
30 | print(("\n"):rep(wy)) | ||
31 | local move = move_right | ||
32 | |||
33 | while wx > 0 and wy > 0 do | ||
34 | sys.sleep(0.01) -- slow down the drawing a little | ||
35 | io.write("*" .. move_left .. move ) | ||
36 | io.flush() | ||
37 | x = x + dx | ||
38 | y = y + dy | ||
39 | |||
40 | if x > wx and move == move_right then | ||
41 | -- end of move right | ||
42 | dx = 0 | ||
43 | dy = 1 | ||
44 | move = move_up | ||
45 | wy = wy - 1 | ||
46 | my = my + 1 | ||
47 | elseif y > wy and move == move_up then | ||
48 | -- end of move up | ||
49 | dx = -1 | ||
50 | dy = 0 | ||
51 | move = move_left | ||
52 | wx = wx - 1 | ||
53 | mx = mx + 1 | ||
54 | elseif x < mx and move == move_left then | ||
55 | -- end of move left | ||
56 | dx = 0 | ||
57 | dy = -1 | ||
58 | move = move_down | ||
59 | wy = wy - 1 | ||
60 | my = my + 1 | ||
61 | elseif y < my and move == move_down then | ||
62 | -- end of move down | ||
63 | dx = 1 | ||
64 | dy = 0 | ||
65 | move = move_right | ||
66 | wx = wx - 1 | ||
67 | mx = mx + 1 | ||
68 | end | ||
69 | end | ||
70 | |||
71 | io.write(move_down:rep(15)) | ||
72 | print("\nDone!") | ||
diff --git a/luasystem-scm-0.rockspec b/luasystem-scm-0.rockspec index ff5af61..dac3d9b 100644 --- a/luasystem-scm-0.rockspec +++ b/luasystem-scm-0.rockspec | |||
@@ -59,6 +59,7 @@ local function make_platform(plat) | |||
59 | 'src/environment.c', | 59 | 'src/environment.c', |
60 | 'src/random.c', | 60 | 'src/random.c', |
61 | 'src/term.c', | 61 | 'src/term.c', |
62 | 'src/bitflags.c', | ||
62 | }, | 63 | }, |
63 | defines = defines[plat], | 64 | defines = defines[plat], |
64 | libraries = libraries[plat], | 65 | libraries = libraries[plat], |
diff --git a/spec/04-term_spec.lua b/spec/04-term_spec.lua index a2034aa..9ca37e9 100644 --- a/spec/04-term_spec.lua +++ b/spec/04-term_spec.lua | |||
@@ -91,4 +91,24 @@ describe("Terminal:", function() | |||
91 | 91 | ||
92 | end) | 92 | end) |
93 | 93 | ||
94 | |||
95 | |||
96 | describe("getconsoleflags()", function() | ||
97 | |||
98 | pending("returns the consoleflags, if called without flags", function() | ||
99 | print"1" | ||
100 | package.loaded["system"] = nil | ||
101 | package.loaded["system.core"] = nil | ||
102 | print"2" | ||
103 | local system = require "system" | ||
104 | print"3" | ||
105 | for k,v in pairs(system) do print(k,v) end | ||
106 | for k,v in pairs(debug.getinfo(system.isatty)) do print(k,v) end | ||
107 | |||
108 | local flags, err = system.getconsoleflags(io.stdin) | ||
109 | assert.is_nil(err) | ||
110 | assert.is_integer(flags) | ||
111 | end) | ||
112 | |||
113 | end) | ||
94 | end) | 114 | end) |
diff --git a/spec/05-bitflags_spec.lua b/spec/05-bitflags_spec.lua new file mode 100644 index 0000000..01bf958 --- /dev/null +++ b/spec/05-bitflags_spec.lua | |||
@@ -0,0 +1,108 @@ | |||
1 | describe("BitFlags library", function() | ||
2 | |||
3 | local sys = require("system") | ||
4 | |||
5 | it("creates new flag objects", function() | ||
6 | local bf = sys.bitflag(255) | ||
7 | assert.is_not_nil(bf) | ||
8 | assert.are.equal(255, bf:value()) | ||
9 | assert.is.userdata(bf) | ||
10 | end) | ||
11 | |||
12 | it("converts to a hex string", function() | ||
13 | local bf = sys.bitflag(255) | ||
14 | assert.are.equal("bitflags: 255", tostring(bf)) | ||
15 | end) | ||
16 | |||
17 | it("handles OR/ADD operations", function() | ||
18 | -- one at a time | ||
19 | local bf1 = sys.bitflag(1) -- b0001 | ||
20 | local bf2 = sys.bitflag(2) -- b0010 | ||
21 | local bf3 = bf1 + bf2 -- b0011 | ||
22 | assert.are.equal(3, bf3:value()) | ||
23 | -- multiple at once | ||
24 | local bf4 = sys.bitflag(4+8) -- b1100 | ||
25 | local bf5 = bf3 + bf4 -- b1111 | ||
26 | assert.are.equal(15, bf5:value()) | ||
27 | -- multiple that were already set | ||
28 | local bf6 = sys.bitflag(15) -- b1111 | ||
29 | local bf7 = sys.bitflag(8+2) -- b1010 | ||
30 | local bf8 = bf6 + bf7 -- b1111 | ||
31 | assert.are.equal(15, bf8:value()) | ||
32 | end) | ||
33 | |||
34 | it("handles AND-NOT/SUBSTRACT operations", function() | ||
35 | -- one at a time | ||
36 | local bf1 = sys.bitflag(3) -- b0011 | ||
37 | local bf2 = sys.bitflag(1) -- b0001 | ||
38 | local bf3 = bf1 - bf2 -- b0010 | ||
39 | assert.are.equal(2, bf3:value()) | ||
40 | -- multiple at once | ||
41 | local bf4 = sys.bitflag(15) -- b1111 | ||
42 | local bf5 = sys.bitflag(8+2) -- b1010 | ||
43 | local bf6 = bf4 - bf5 -- b0101 | ||
44 | assert.are.equal(5, bf6:value()) | ||
45 | -- multiple that were not set | ||
46 | local bf7 = sys.bitflag(3) -- b0011 | ||
47 | local bf8 = sys.bitflag(15) -- b1111 | ||
48 | local bf9 = bf7 - bf8 -- b0000 | ||
49 | assert.are.equal(0, bf9:value()) | ||
50 | end) | ||
51 | |||
52 | it("checks for equality", function() | ||
53 | local bf1 = sys.bitflag(4) | ||
54 | local bf2 = sys.bitflag(4) | ||
55 | local bf3 = sys.bitflag(5) | ||
56 | assert.is.True(bf1 == bf2) | ||
57 | assert.is.False(bf1 == bf3) | ||
58 | end) | ||
59 | |||
60 | it("indexes bits correctly", function() | ||
61 | local bf = sys.bitflag(4) -- b100 | ||
62 | assert.is_true(bf[2]) | ||
63 | assert.is_false(bf[1]) | ||
64 | end) | ||
65 | |||
66 | it("errors on reading invalid bit indexes", function() | ||
67 | local bf = sys.bitflag(4) | ||
68 | assert.has_error(function() return bf[-10] end, "index out of range") | ||
69 | assert.has_error(function() return bf[10000] end, "index out of range") | ||
70 | assert.has_no_error(function() return bf.not_a_number end) | ||
71 | end) | ||
72 | |||
73 | it("sets and clears bits correctly", function() | ||
74 | local bf = sys.bitflag(0) | ||
75 | bf[1] = true | ||
76 | assert.is_true(bf[1]) | ||
77 | bf[1] = false | ||
78 | assert.is_false(bf[1]) | ||
79 | end) | ||
80 | |||
81 | it("errors on setting invalid bit indexes", function() | ||
82 | local bf = sys.bitflag(0) | ||
83 | assert.has_error(function() bf[-10] = true end, "index out of range") | ||
84 | assert.has_error(function() bf[10000] = true end, "index out of range") | ||
85 | assert.has_error(function() bf.not_a_number = true end, "index must be a number") | ||
86 | end) | ||
87 | |||
88 | it("handles <= and >= operations", function() | ||
89 | local bf1 = sys.bitflag(3) -- b0011 | ||
90 | local bf2 = sys.bitflag(15) -- b1111 | ||
91 | assert.is_true(bf2 >= bf1) -- all bits in bf1 are set in bf2 | ||
92 | assert.is_true(bf2 > bf1) -- all bits in bf1 are set in bf2 and some more | ||
93 | assert.is_false(bf2 <= bf1) -- not all bits in bf2 are set in bf1 | ||
94 | assert.is_false(bf2 < bf1) -- not all bits in bf2 are set in bf1 | ||
95 | end) | ||
96 | |||
97 | it("checks for a subset using 'has'", function() | ||
98 | local bf1 = sys.bitflag(3) -- b0011 | ||
99 | local bf2 = sys.bitflag(3) -- b0011 | ||
100 | local bf3 = sys.bitflag(15) -- b1111 | ||
101 | local bf0 = sys.bitflag(0) -- b0000 | ||
102 | assert.is_true(bf1:has(bf2)) -- equal | ||
103 | assert.is_true(bf3:has(bf1)) -- is a subset, and has more flags | ||
104 | assert.is_false(bf1:has(bf3)) -- not a subset, bf3 has more flags | ||
105 | assert.is_false(bf1:has(bf0)) -- bf0 is unset, always returns false | ||
106 | end) | ||
107 | |||
108 | end) | ||
diff --git a/src/bitflags.c b/src/bitflags.c new file mode 100644 index 0000000..89a88b7 --- /dev/null +++ b/src/bitflags.c | |||
@@ -0,0 +1,235 @@ | |||
1 | /// Bitflags module. | ||
2 | // The bitflag object makes it easy to manipulate flags in a bitmask. | ||
3 | // | ||
4 | // It has metamethods that do the hard work, adding flags sets them, substracting | ||
5 | // unsets them. Comparing flags checks if all flags in the second set are also set | ||
6 | // in the first set. The `has` method checks if all flags in the second set are | ||
7 | // also set in the first set, but behaves slightly different. | ||
8 | // | ||
9 | // Indexing allows checking values or setting them by bit index (eg. 0-7 for flags | ||
10 | // in the first byte). | ||
11 | // | ||
12 | // _NOTE_: unavailable flags (eg. Windows flags on a Posix system) should not be | ||
13 | // omitted, but be assigned a value of 0. This is because the `has` method will | ||
14 | // return `false` if the flags are checked and the value is 0. | ||
15 | // | ||
16 | // See `system.bitflag` (the constructor) for extensive examples on usage. | ||
17 | // @classmod bitflags | ||
18 | #include "bitflags.h" | ||
19 | |||
20 | #define BITFLAGS_MT_NAME "LuaSystem.BitFlags" | ||
21 | |||
22 | typedef struct { | ||
23 | LSBF_BITFLAG flags; | ||
24 | } LS_BitFlags; | ||
25 | |||
26 | /// Bit flags. | ||
27 | // Bitflag objects can be used to easily manipulate and compare bit flags. | ||
28 | // These are primarily for use with the terminal functions, but can be used | ||
29 | // in other places as well. | ||
30 | // @section bitflags | ||
31 | |||
32 | |||
33 | // pushes a new LS_BitFlags object with the given value onto the stack | ||
34 | void lsbf_pushbitflags(lua_State *L, LSBF_BITFLAG value) { | ||
35 | LS_BitFlags *obj = (LS_BitFlags *)lua_newuserdata(L, sizeof(LS_BitFlags)); | ||
36 | if (!obj) luaL_error(L, "Memory allocation failed"); | ||
37 | luaL_getmetatable(L, BITFLAGS_MT_NAME); | ||
38 | lua_setmetatable(L, -2); | ||
39 | obj->flags = value; | ||
40 | } | ||
41 | |||
42 | // gets the LS_BitFlags value at the given index. Returns a Lua error if it is not | ||
43 | // a LS_BitFlags object. | ||
44 | LSBF_BITFLAG lsbf_checkbitflags(lua_State *L, int index) { | ||
45 | LS_BitFlags *obj = (LS_BitFlags *)luaL_checkudata(L, index, BITFLAGS_MT_NAME); | ||
46 | return obj->flags; | ||
47 | } | ||
48 | |||
49 | /*** | ||
50 | Creates a new bitflag object from the given value. | ||
51 | @function system.bitflag | ||
52 | @tparam[opt=0] number value the value to create the bitflag object from. | ||
53 | @treturn bitflag bitflag object with the given values set. | ||
54 | @usage | ||
55 | local sys = require 'system' | ||
56 | local flags = sys.bitflag(2) -- b0010 | ||
57 | |||
58 | -- get state of individual bits | ||
59 | print(flags[0]) -- false | ||
60 | print(flags[1]) -- true | ||
61 | |||
62 | -- set individual bits | ||
63 | flags[0] = true -- b0011 | ||
64 | print(flags:value()) -- 3 | ||
65 | print(flags) -- "bitflags: 3" | ||
66 | |||
67 | -- adding flags (bitwise OR) | ||
68 | local flags1 = sys.bitflag(1) -- b0001 | ||
69 | local flags2 = sys.bitflag(2) -- b0010 | ||
70 | local flags3 = flags1 + flags2 -- b0011 | ||
71 | |||
72 | -- substracting flags (bitwise AND NOT) | ||
73 | print(flags3:value()) -- 3 | ||
74 | flag3 = flag3 - flag3 -- b0000 | ||
75 | print(flags3:value()) -- 0 | ||
76 | |||
77 | -- comparing flags | ||
78 | local flags4 = sys.bitflag(7) -- b0111 | ||
79 | local flags5 = sys.bitflag(255) -- b11111111 | ||
80 | print(flags5 >= flags4) -- true, all bits in flags4 are set in flags5 | ||
81 | |||
82 | -- comparing with 0 flags: comparison and `has` behave differently | ||
83 | local flags6 = sys.bitflag(0) -- b0000 | ||
84 | local flags7 = sys.bitflag(1) -- b0001 | ||
85 | print(flags6 < flags7) -- true, flags6 is a subset of flags7 | ||
86 | print(flags7:has(flags6)) -- false, flags6 is not set in flags7 | ||
87 | */ | ||
88 | static int lsbf_new(lua_State *L) { | ||
89 | LSBF_BITFLAG flags = 0; | ||
90 | if (lua_gettop(L) > 0) { | ||
91 | flags = luaL_checkinteger(L, 1); | ||
92 | } | ||
93 | lsbf_pushbitflags(L, flags); | ||
94 | return 1; | ||
95 | } | ||
96 | |||
97 | /*** | ||
98 | Retrieves the numeric value of the bitflag object. | ||
99 | @function bitflag:value | ||
100 | @treturn number the numeric value of the bitflags. | ||
101 | @usage | ||
102 | local sys = require 'system' | ||
103 | local flags = sys.bitflag() -- b0000 | ||
104 | flags[0] = true -- b0001 | ||
105 | flags[2] = true -- b0101 | ||
106 | print(flags:value()) -- 5 | ||
107 | */ | ||
108 | static int lsbf_value(lua_State *L) { | ||
109 | lua_pushinteger(L, lsbf_checkbitflags(L, 1)); | ||
110 | return 1; | ||
111 | } | ||
112 | |||
113 | static int lsbf_tostring(lua_State *L) { | ||
114 | lua_pushfstring(L, "bitflags: %d", lsbf_checkbitflags(L, 1)); | ||
115 | return 1; | ||
116 | } | ||
117 | |||
118 | static int lsbf_add(lua_State *L) { | ||
119 | lsbf_pushbitflags(L, lsbf_checkbitflags(L, 1) | lsbf_checkbitflags(L, 2)); | ||
120 | return 1; | ||
121 | } | ||
122 | |||
123 | static int lsbf_sub(lua_State *L) { | ||
124 | lsbf_pushbitflags(L, lsbf_checkbitflags(L, 1) & ~lsbf_checkbitflags(L, 2)); | ||
125 | return 1; | ||
126 | } | ||
127 | |||
128 | static int lsbf_eq(lua_State *L) { | ||
129 | lua_pushboolean(L, lsbf_checkbitflags(L, 1) == lsbf_checkbitflags(L, 2)); | ||
130 | return 1; | ||
131 | } | ||
132 | |||
133 | static int lsbf_le(lua_State *L) { | ||
134 | LSBF_BITFLAG a = lsbf_checkbitflags(L, 1); | ||
135 | LSBF_BITFLAG b = lsbf_checkbitflags(L, 2); | ||
136 | // Check if all bits in b are also set in a | ||
137 | lua_pushboolean(L, (a & b) == a); | ||
138 | return 1; | ||
139 | } | ||
140 | |||
141 | /*** | ||
142 | Checks if the given flags are set. | ||
143 | This is different from the `>=` and `<=` operators because if the flag to check | ||
144 | has a value `0`, it will always return `false`. So if there are flags that are | ||
145 | unsupported on a platform, they can be set to 0 and the `has` function will | ||
146 | return `false` if the flags are checked. | ||
147 | @function bitflag:has | ||
148 | @tparam bitflag subset the flags to check for. | ||
149 | @treturn boolean true if all the flags are set, false otherwise. | ||
150 | @usage | ||
151 | local sys = require 'system' | ||
152 | local flags = sys.bitflag(12) -- b1100 | ||
153 | local myflags = sys.bitflag(15) -- b1111 | ||
154 | print(flags:has(myflags)) -- false, not all bits in myflags are set in flags | ||
155 | print(myflags:has(flags)) -- true, all bits in flags are set in myflags | ||
156 | */ | ||
157 | static int lsbf_has(lua_State *L) { | ||
158 | LSBF_BITFLAG a = lsbf_checkbitflags(L, 1); | ||
159 | LSBF_BITFLAG b = lsbf_checkbitflags(L, 2); | ||
160 | // Check if all bits in b are also set in a, and b is not 0 | ||
161 | lua_pushboolean(L, (a | b) == a && b != 0); | ||
162 | return 1; | ||
163 | } | ||
164 | |||
165 | static int lsbf_lt(lua_State *L) { | ||
166 | LSBF_BITFLAG a = lsbf_checkbitflags(L, 1); | ||
167 | LSBF_BITFLAG b = lsbf_checkbitflags(L, 2); | ||
168 | // Check if a is strictly less than b, meaning a != b and a is a subset of b | ||
169 | lua_pushboolean(L, (a != b) && ((a & b) == a)); | ||
170 | return 1; | ||
171 | } | ||
172 | |||
173 | static int lsbf_index(lua_State *L) { | ||
174 | if (!lua_isnumber(L, 2)) { | ||
175 | // the parameter isn't a number, just lookup the key in the metatable | ||
176 | lua_getmetatable(L, 1); | ||
177 | lua_pushvalue(L, 2); | ||
178 | lua_gettable(L, -2); | ||
179 | return 1; | ||
180 | } | ||
181 | |||
182 | int index = luaL_checkinteger(L, 2); | ||
183 | if (index < 0 || index >= sizeof(LSBF_BITFLAG) * 8) { | ||
184 | return luaL_error(L, "index out of range"); | ||
185 | } | ||
186 | lua_pushboolean(L, (lsbf_checkbitflags(L, 1) & (1 << index)) != 0); | ||
187 | return 1; | ||
188 | } | ||
189 | |||
190 | static int lsbf_newindex(lua_State *L) { | ||
191 | LS_BitFlags *obj = (LS_BitFlags *)luaL_checkudata(L, 1, BITFLAGS_MT_NAME); | ||
192 | |||
193 | if (!lua_isnumber(L, 2)) { | ||
194 | return luaL_error(L, "index must be a number"); | ||
195 | } | ||
196 | int index = luaL_checkinteger(L, 2); | ||
197 | if (index < 0 || index >= sizeof(LSBF_BITFLAG) * 8) { | ||
198 | return luaL_error(L, "index out of range"); | ||
199 | } | ||
200 | |||
201 | luaL_checkany(L, 3); | ||
202 | if (lua_toboolean(L, 3)) { | ||
203 | obj->flags |= (1 << index); | ||
204 | } else { | ||
205 | obj->flags &= ~(1 << index); | ||
206 | } | ||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | static const struct luaL_Reg lsbf_funcs[] = { | ||
211 | {"bitflag", lsbf_new}, | ||
212 | {NULL, NULL} | ||
213 | }; | ||
214 | |||
215 | static const struct luaL_Reg lsbf_methods[] = { | ||
216 | {"value", lsbf_value}, | ||
217 | {"has", lsbf_has}, | ||
218 | {"__tostring", lsbf_tostring}, | ||
219 | {"__add", lsbf_add}, | ||
220 | {"__sub", lsbf_sub}, | ||
221 | {"__eq", lsbf_eq}, | ||
222 | {"__le", lsbf_le}, | ||
223 | {"__lt", lsbf_lt}, | ||
224 | {"__index", lsbf_index}, | ||
225 | {"__newindex", lsbf_newindex}, | ||
226 | {NULL, NULL} | ||
227 | }; | ||
228 | |||
229 | void bitflags_open(lua_State *L) { | ||
230 | luaL_newmetatable(L, BITFLAGS_MT_NAME); | ||
231 | luaL_setfuncs(L, lsbf_methods, 0); | ||
232 | lua_pop(L, 1); | ||
233 | |||
234 | luaL_setfuncs(L, lsbf_funcs, 0); | ||
235 | } | ||
diff --git a/src/bitflags.h b/src/bitflags.h new file mode 100644 index 0000000..0e47246 --- /dev/null +++ b/src/bitflags.h | |||
@@ -0,0 +1,21 @@ | |||
1 | #ifndef LSBITFLAGS_H | ||
2 | #define LSBITFLAGS_H | ||
3 | |||
4 | #include <lua.h> | ||
5 | #include "compat.h" | ||
6 | #include <lauxlib.h> | ||
7 | #include <stdlib.h> | ||
8 | |||
9 | // type used to store the bitflags | ||
10 | #define LSBF_BITFLAG lua_Integer | ||
11 | |||
12 | // Validates that the given index is a bitflag object and returns its value. | ||
13 | // If the index is not a bitflag object, a Lua error is raised. | ||
14 | // The value will be left on the stack. | ||
15 | LSBF_BITFLAG lsbf_checkbitflags(lua_State *L, int index); | ||
16 | |||
17 | // Pushes a new bitflag object with the given value onto the stack. | ||
18 | // Might raise a Lua error if memory allocation fails. | ||
19 | void lsbf_pushbitflags(lua_State *L, LSBF_BITFLAG value); | ||
20 | |||
21 | #endif | ||
diff --git a/src/compat.h b/src/compat.h index 35f9ef2..7a1fcee 100644 --- a/src/compat.h +++ b/src/compat.h | |||
@@ -13,6 +13,17 @@ void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup); | |||
13 | #include <sys/types.h> | 13 | #include <sys/types.h> |
14 | #endif | 14 | #endif |
15 | 15 | ||
16 | // Windows compatibility; define DWORD and TRUE/FALSE on non-Windows | ||
17 | #ifndef _WIN32 | ||
18 | #ifndef DWORD | ||
19 | #define DWORD unsigned long | ||
20 | #endif | ||
21 | #ifndef TRUE | ||
22 | #define TRUE 1 | ||
23 | #define FALSE 0 | ||
24 | #endif | ||
25 | #endif | ||
26 | |||
16 | #ifdef _MSC_VER | 27 | #ifdef _MSC_VER |
17 | // MSVC Windows doesn't have ssize_t, so we define it here | 28 | // MSVC Windows doesn't have ssize_t, so we define it here |
18 | #if SIZE_MAX == UINT_MAX | 29 | #if SIZE_MAX == UINT_MAX |
@@ -16,6 +16,7 @@ void time_open(lua_State *L); | |||
16 | void environment_open(lua_State *L); | 16 | void environment_open(lua_State *L); |
17 | void random_open(lua_State *L); | 17 | void random_open(lua_State *L); |
18 | void term_open(lua_State *L); | 18 | void term_open(lua_State *L); |
19 | void bitflags_open(lua_State *L); | ||
19 | 20 | ||
20 | /*------------------------------------------------------------------------- | 21 | /*------------------------------------------------------------------------- |
21 | * Initializes all library modules. | 22 | * Initializes all library modules. |
@@ -32,6 +33,7 @@ LUAEXPORT int luaopen_system_core(lua_State *L) { | |||
32 | lua_pushboolean(L, 0); | 33 | lua_pushboolean(L, 0); |
33 | #endif | 34 | #endif |
34 | lua_rawset(L, -3); | 35 | lua_rawset(L, -3); |
36 | bitflags_open(L); // must be first, used by others | ||
35 | time_open(L); | 37 | time_open(L); |
36 | random_open(L); | 38 | random_open(L); |
37 | term_open(L); | 39 | term_open(L); |
@@ -1,37 +1,849 @@ | |||
1 | /// @submodule system | 1 | /// @submodule system |
2 | |||
3 | // Unix: see https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/ | ||
4 | // Windows: see https://learn.microsoft.com/en-us/windows/console/console-reference | ||
5 | |||
2 | #include <lua.h> | 6 | #include <lua.h> |
3 | #include <lauxlib.h> | 7 | #include <lauxlib.h> |
4 | #include <lualib.h> | 8 | #include <lualib.h> |
5 | #include "compat.h" | 9 | #include "compat.h" |
10 | #include "bitflags.h" | ||
6 | 11 | ||
7 | #ifndef _MSC_VER | 12 | #ifndef _MSC_VER |
8 | # include <unistd.h> | 13 | # include <unistd.h> |
9 | #endif | 14 | #endif |
10 | 15 | ||
16 | #ifdef _WIN32 | ||
17 | # include <windows.h> | ||
18 | #else | ||
19 | # include <termios.h> | ||
20 | # include <string.h> | ||
21 | # include <errno.h> | ||
22 | # include <fcntl.h> | ||
23 | # include <sys/ioctl.h> | ||
24 | # include <unistd.h> | ||
25 | #endif | ||
26 | |||
27 | #ifdef _WIN32 | ||
28 | // after an error is returned, GetLastError() result can be passed to this function to get a string | ||
29 | // representation of the error on the stack. | ||
30 | // result will be nil+error on the stack, always 2 results. | ||
31 | static void termFormatError(lua_State *L, DWORD errorCode, const char* prefix) { | ||
32 | //static void FormatErrorAndReturn(lua_State *L, DWORD errorCode, const char* prefix) { | ||
33 | LPSTR messageBuffer = NULL; | ||
34 | FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||
35 | NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); | ||
36 | |||
37 | lua_pushnil(L); | ||
38 | if (messageBuffer) { | ||
39 | if (prefix) { | ||
40 | lua_pushfstring(L, "%s: %s", prefix, messageBuffer); | ||
41 | } else { | ||
42 | lua_pushstring(L, messageBuffer); | ||
43 | } | ||
44 | LocalFree(messageBuffer); | ||
45 | } else { | ||
46 | lua_pushfstring(L, "%sError code %d", prefix ? prefix : "", errorCode); | ||
47 | } | ||
48 | } | ||
49 | #else | ||
50 | static int pusherror(lua_State *L, const char *info) | ||
51 | { | ||
52 | lua_pushnil(L); | ||
53 | if (info==NULL) | ||
54 | lua_pushstring(L, strerror(errno)); | ||
55 | else | ||
56 | lua_pushfstring(L, "%s: %s", info, strerror(errno)); | ||
57 | lua_pushinteger(L, errno); | ||
58 | return 3; | ||
59 | } | ||
60 | #endif | ||
11 | 61 | ||
12 | /*** | 62 | /*** |
13 | Checks if a file-handle is a TTY. | 63 | Checks if a file-handle is a TTY. |
14 | 64 | ||
15 | @function isatty | 65 | @function isatty |
16 | @tparam file file the file-handle to check | 66 | @tparam file file the file-handle to check, one of `io.stdin`, `io.stdout`, `io.stderr`. |
17 | @treturn boolean true if the file is a tty | 67 | @treturn boolean true if the file is a tty |
68 | @usage | ||
69 | local system = require('system') | ||
70 | if system.isatty(io.stdin) then | ||
71 | -- enable ANSI coloring etc on Windows, does nothing in Posix. | ||
72 | local flags = system.getconsoleflags(io.stdout) | ||
73 | system.setconsoleflags(io.stdout, flags + sys.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
74 | end | ||
18 | */ | 75 | */ |
19 | static int lua_isatty(lua_State* L) { | 76 | static int lst_isatty(lua_State* L) { |
20 | FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE); | 77 | FILE **fh = (FILE **) luaL_checkudata(L, 1, LUA_FILEHANDLE); |
21 | lua_pushboolean(L, isatty(fileno(*fh))); | 78 | lua_pushboolean(L, isatty(fileno(*fh))); |
22 | return 1; | 79 | return 1; |
23 | } | 80 | } |
24 | 81 | ||
25 | 82 | ||
83 | /*------------------------------------------------------------------------- | ||
84 | * Windows Get/SetConsoleMode functions | ||
85 | *-------------------------------------------------------------------------*/ | ||
26 | 86 | ||
27 | static luaL_Reg func[] = { | 87 | typedef struct ls_RegConst { |
28 | { "isatty", lua_isatty }, | 88 | const char *name; |
29 | { NULL, NULL } | 89 | DWORD value; |
90 | } ls_RegConst; | ||
91 | |||
92 | // Define a macro to check if a constant is defined and set it to 0 if not. | ||
93 | // This is needed because some flags are not defined on all platforms. So we | ||
94 | // still export the constants, but they will be all 0, and hence not do anything. | ||
95 | #ifdef _WIN32 | ||
96 | #define CHECK_WIN_FLAG_OR_ZERO(flag) flag | ||
97 | #define CHECK_NIX_FLAG_OR_ZERO(flag) 0 | ||
98 | #else | ||
99 | #define CHECK_WIN_FLAG_OR_ZERO(flag) 0 | ||
100 | #define CHECK_NIX_FLAG_OR_ZERO(flag) flag | ||
101 | #endif | ||
102 | |||
103 | // Export Windows constants to Lua | ||
104 | static const struct ls_RegConst win_console_in_flags[] = { | ||
105 | // Console Input Flags | ||
106 | {"CIF_ECHO_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_ECHO_INPUT)}, | ||
107 | {"CIF_INSERT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_INSERT_MODE)}, | ||
108 | {"CIF_LINE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LINE_INPUT)}, | ||
109 | {"CIF_MOUSE_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_MOUSE_INPUT)}, | ||
110 | {"CIF_PROCESSED_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_INPUT)}, | ||
111 | {"CIF_QUICK_EDIT_MODE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_QUICK_EDIT_MODE)}, | ||
112 | {"CIF_WINDOW_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WINDOW_INPUT)}, | ||
113 | {"CIF_VIRTUAL_TERMINAL_INPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_INPUT)}, | ||
114 | {"CIF_EXTENDED_FLAGS", CHECK_WIN_FLAG_OR_ZERO(ENABLE_EXTENDED_FLAGS)}, | ||
115 | {"CIF_AUTO_POSITION", CHECK_WIN_FLAG_OR_ZERO(ENABLE_AUTO_POSITION)}, | ||
116 | {NULL, 0} | ||
117 | }; | ||
118 | |||
119 | static const struct ls_RegConst win_console_out_flags[] = { | ||
120 | // Console Output Flags | ||
121 | {"COF_PROCESSED_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_PROCESSED_OUTPUT)}, | ||
122 | {"COF_WRAP_AT_EOL_OUTPUT", CHECK_WIN_FLAG_OR_ZERO(ENABLE_WRAP_AT_EOL_OUTPUT)}, | ||
123 | {"COF_VIRTUAL_TERMINAL_PROCESSING", CHECK_WIN_FLAG_OR_ZERO(ENABLE_VIRTUAL_TERMINAL_PROCESSING)}, | ||
124 | {"COF_DISABLE_NEWLINE_AUTO_RETURN", CHECK_WIN_FLAG_OR_ZERO(DISABLE_NEWLINE_AUTO_RETURN)}, | ||
125 | {"COF_ENABLE_LVB_GRID_WORLDWIDE", CHECK_WIN_FLAG_OR_ZERO(ENABLE_LVB_GRID_WORLDWIDE)}, | ||
126 | {NULL, 0} | ||
127 | }; | ||
128 | |||
129 | |||
130 | // Export Unix constants to Lua | ||
131 | static const struct ls_RegConst nix_tcsetattr_actions[] = { | ||
132 | // The optional actions for tcsetattr | ||
133 | {"TCSANOW", CHECK_NIX_FLAG_OR_ZERO(TCSANOW)}, | ||
134 | {"TCSADRAIN", CHECK_NIX_FLAG_OR_ZERO(TCSADRAIN)}, | ||
135 | {"TCSAFLUSH", CHECK_NIX_FLAG_OR_ZERO(TCSAFLUSH)}, | ||
136 | {NULL, 0} | ||
137 | }; | ||
138 | |||
139 | static const struct ls_RegConst nix_console_i_flags[] = { | ||
140 | // Input flags (c_iflag) | ||
141 | {"I_IGNBRK", CHECK_NIX_FLAG_OR_ZERO(IGNBRK)}, | ||
142 | {"I_BRKINT", CHECK_NIX_FLAG_OR_ZERO(BRKINT)}, | ||
143 | {"I_IGNPAR", CHECK_NIX_FLAG_OR_ZERO(IGNPAR)}, | ||
144 | {"I_PARMRK", CHECK_NIX_FLAG_OR_ZERO(PARMRK)}, | ||
145 | {"I_INPCK", CHECK_NIX_FLAG_OR_ZERO(INPCK)}, | ||
146 | {"I_ISTRIP", CHECK_NIX_FLAG_OR_ZERO(ISTRIP)}, | ||
147 | {"I_INLCR", CHECK_NIX_FLAG_OR_ZERO(INLCR)}, | ||
148 | {"I_IGNCR", CHECK_NIX_FLAG_OR_ZERO(IGNCR)}, | ||
149 | {"I_ICRNL", CHECK_NIX_FLAG_OR_ZERO(ICRNL)}, | ||
150 | #ifndef __APPLE__ | ||
151 | {"I_IUCLC", CHECK_NIX_FLAG_OR_ZERO(IUCLC)}, // Might not be available on all systems | ||
152 | #else | ||
153 | {"I_IUCLC", 0}, | ||
154 | #endif | ||
155 | {"I_IXON", CHECK_NIX_FLAG_OR_ZERO(IXON)}, | ||
156 | {"I_IXANY", CHECK_NIX_FLAG_OR_ZERO(IXANY)}, | ||
157 | {"I_IXOFF", CHECK_NIX_FLAG_OR_ZERO(IXOFF)}, | ||
158 | {"I_IMAXBEL", CHECK_NIX_FLAG_OR_ZERO(IMAXBEL)}, | ||
159 | {NULL, 0} | ||
30 | }; | 160 | }; |
31 | 161 | ||
162 | static const struct ls_RegConst nix_console_o_flags[] = { | ||
163 | // Output flags (c_oflag) | ||
164 | {"O_OPOST", CHECK_NIX_FLAG_OR_ZERO(OPOST)}, | ||
165 | #ifndef __APPLE__ | ||
166 | {"O_OLCUC", CHECK_NIX_FLAG_OR_ZERO(OLCUC)}, // Might not be available on all systems | ||
167 | #else | ||
168 | {"O_OLCUC", 0}, | ||
169 | #endif | ||
170 | {"O_ONLCR", CHECK_NIX_FLAG_OR_ZERO(ONLCR)}, | ||
171 | {"O_OCRNL", CHECK_NIX_FLAG_OR_ZERO(OCRNL)}, | ||
172 | {"O_ONOCR", CHECK_NIX_FLAG_OR_ZERO(ONOCR)}, | ||
173 | {"O_ONLRET", CHECK_NIX_FLAG_OR_ZERO(ONLRET)}, | ||
174 | {"O_OFILL", CHECK_NIX_FLAG_OR_ZERO(OFILL)}, | ||
175 | {"O_OFDEL", CHECK_NIX_FLAG_OR_ZERO(OFDEL)}, | ||
176 | {"O_NLDLY", CHECK_NIX_FLAG_OR_ZERO(NLDLY)}, | ||
177 | {"O_CRDLY", CHECK_NIX_FLAG_OR_ZERO(CRDLY)}, | ||
178 | {"O_TABDLY", CHECK_NIX_FLAG_OR_ZERO(TABDLY)}, | ||
179 | {"O_BSDLY", CHECK_NIX_FLAG_OR_ZERO(BSDLY)}, | ||
180 | {"O_VTDLY", CHECK_NIX_FLAG_OR_ZERO(VTDLY)}, | ||
181 | {"O_FFDLY", CHECK_NIX_FLAG_OR_ZERO(FFDLY)}, | ||
182 | {NULL, 0} | ||
183 | }; | ||
184 | |||
185 | static const struct ls_RegConst nix_console_l_flags[] = { | ||
186 | // Local flags (c_lflag) | ||
187 | {"L_ISIG", CHECK_NIX_FLAG_OR_ZERO(ISIG)}, | ||
188 | {"L_ICANON", CHECK_NIX_FLAG_OR_ZERO(ICANON)}, | ||
189 | #ifndef __APPLE__ | ||
190 | {"L_XCASE", CHECK_NIX_FLAG_OR_ZERO(XCASE)}, // Might not be available on all systems | ||
191 | #else | ||
192 | {"L_XCASE", 0}, | ||
193 | #endif | ||
194 | {"L_ECHO", CHECK_NIX_FLAG_OR_ZERO(ECHO)}, | ||
195 | {"L_ECHOE", CHECK_NIX_FLAG_OR_ZERO(ECHOE)}, | ||
196 | {"L_ECHOK", CHECK_NIX_FLAG_OR_ZERO(ECHOK)}, | ||
197 | {"L_ECHONL", CHECK_NIX_FLAG_OR_ZERO(ECHONL)}, | ||
198 | {"L_NOFLSH", CHECK_NIX_FLAG_OR_ZERO(NOFLSH)}, | ||
199 | {"L_TOSTOP", CHECK_NIX_FLAG_OR_ZERO(TOSTOP)}, | ||
200 | {"L_ECHOCTL", CHECK_NIX_FLAG_OR_ZERO(ECHOCTL)}, // Might not be available on all systems | ||
201 | {"L_ECHOPRT", CHECK_NIX_FLAG_OR_ZERO(ECHOPRT)}, // Might not be available on all systems | ||
202 | {"L_ECHOKE", CHECK_NIX_FLAG_OR_ZERO(ECHOKE)}, // Might not be available on all systems | ||
203 | {"L_FLUSHO", CHECK_NIX_FLAG_OR_ZERO(FLUSHO)}, | ||
204 | {"L_PENDIN", CHECK_NIX_FLAG_OR_ZERO(PENDIN)}, | ||
205 | {"L_IEXTEN", CHECK_NIX_FLAG_OR_ZERO(IEXTEN)}, | ||
206 | {NULL, 0} | ||
207 | }; | ||
208 | |||
209 | static DWORD win_valid_in_flags = 0; | ||
210 | static DWORD win_valid_out_flags = 0; | ||
211 | static DWORD nix_valid_i_flags = 0; | ||
212 | static DWORD nix_valid_o_flags = 0; | ||
213 | static DWORD nix_valid_l_flags = 0; | ||
214 | static void initialize_valid_flags() | ||
215 | { | ||
216 | win_valid_in_flags = 0; | ||
217 | for (int i = 0; win_console_in_flags[i].name != NULL; i++) | ||
218 | { | ||
219 | win_valid_in_flags |= win_console_in_flags[i].value; | ||
220 | } | ||
221 | win_valid_out_flags = 0; | ||
222 | for (int i = 0; win_console_out_flags[i].name != NULL; i++) | ||
223 | { | ||
224 | win_valid_out_flags |= win_console_out_flags[i].value; | ||
225 | } | ||
226 | nix_valid_i_flags = 0; | ||
227 | for (int i = 0; nix_console_i_flags[i].name != NULL; i++) | ||
228 | { | ||
229 | nix_valid_i_flags |= nix_console_i_flags[i].value; | ||
230 | } | ||
231 | nix_valid_o_flags = 0; | ||
232 | for (int i = 0; nix_console_o_flags[i].name != NULL; i++) | ||
233 | { | ||
234 | nix_valid_o_flags |= nix_console_o_flags[i].value; | ||
235 | } | ||
236 | nix_valid_l_flags = 0; | ||
237 | for (int i = 0; nix_console_l_flags[i].name != NULL; i++) | ||
238 | { | ||
239 | nix_valid_l_flags |= nix_console_l_flags[i].value; | ||
240 | } | ||
241 | } | ||
242 | |||
243 | #ifdef _WIN32 | ||
244 | // first item on the stack should be io.stdin, io.stderr, or io.stdout, second item | ||
245 | // should be the flags to validate. | ||
246 | // If it returns NULL, then it leaves nil+err on the stack | ||
247 | static HANDLE get_console_handle(lua_State *L, int flags_optional) | ||
248 | { | ||
249 | if (lua_gettop(L) < 1) { | ||
250 | luaL_argerror(L, 1, "expected file handle"); | ||
251 | } | ||
252 | |||
253 | HANDLE handle; | ||
254 | DWORD valid; | ||
255 | FILE *file = *(FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE); | ||
256 | if (file == stdin && file != NULL) { | ||
257 | handle = GetStdHandle(STD_INPUT_HANDLE); | ||
258 | valid = win_valid_in_flags; | ||
259 | |||
260 | } else if (file == stdout && file != NULL) { | ||
261 | handle = GetStdHandle(STD_OUTPUT_HANDLE); | ||
262 | valid = win_valid_out_flags; | ||
263 | |||
264 | } else if (file == stderr && file != NULL) { | ||
265 | handle = GetStdHandle(STD_ERROR_HANDLE); | ||
266 | valid = win_valid_out_flags; | ||
267 | |||
268 | } else { | ||
269 | luaL_argerror(L, 1, "invalid file handle"); // does not return | ||
270 | } | ||
271 | |||
272 | if (handle == INVALID_HANDLE_VALUE) { | ||
273 | termFormatError(L, GetLastError(), "failed to retrieve std handle"); | ||
274 | lua_error(L); // does not return | ||
275 | } | ||
276 | |||
277 | if (handle == NULL) { | ||
278 | lua_pushnil(L); | ||
279 | lua_pushliteral(L, "failed to get console handle"); | ||
280 | return NULL; | ||
281 | } | ||
282 | |||
283 | if (flags_optional && lua_gettop(L) < 2) { | ||
284 | return handle; | ||
285 | } | ||
286 | |||
287 | if (lua_gettop(L) < 2) { | ||
288 | luaL_argerror(L, 2, "expected flags"); | ||
289 | } | ||
290 | |||
291 | LSBF_BITFLAG flags = lsbf_checkbitflags(L, 2); | ||
292 | if ((flags & ~valid) != 0) { | ||
293 | luaL_argerror(L, 2, "invalid flags"); | ||
294 | } | ||
295 | |||
296 | return handle; | ||
297 | } | ||
298 | #else | ||
299 | // first item on the stack should be io.stdin, io.stderr, or io.stdout. Throws a | ||
300 | // Lua error if the file is not one of these. | ||
301 | static int get_console_handle(lua_State *L) | ||
302 | { | ||
303 | FILE **file = (FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE); | ||
304 | if (file == NULL || *file == NULL) { | ||
305 | return luaL_argerror(L, 1, "expected file handle"); // call doesn't return | ||
306 | } | ||
307 | |||
308 | // Check if the file is stdin, stdout, or stderr | ||
309 | if (*file == stdin || *file == stdout || *file == stderr) { | ||
310 | // Push the file descriptor onto the Lua stack | ||
311 | return fileno(*file); | ||
312 | } | ||
313 | |||
314 | return luaL_argerror(L, 1, "invalid file handle"); // does not return | ||
315 | } | ||
316 | #endif | ||
317 | |||
318 | |||
319 | |||
320 | /*** | ||
321 | Sets the console flags (Windows). | ||
322 | The `CIF_` and `COF_` constants are available on the module table. Where `CIF` are the | ||
323 | input flags (for use with `io.stdin`) and `COF` are the output flags (for use with | ||
324 | `io.stdout`/`io.stderr`). | ||
325 | |||
326 | To see flag status and constant names check `listconsoleflags`. | ||
327 | |||
328 | Note: not all combinations of flags are allowed, as some are mutually exclusive or mutually required. | ||
329 | See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode) | ||
330 | @function setconsoleflags | ||
331 | @tparam file file the file-handle to set the flags on | ||
332 | @tparam bitflags bitflags the flags to set/unset | ||
333 | @treturn[1] boolean `true` on success | ||
334 | @treturn[2] nil | ||
335 | @treturn[2] string error message | ||
336 | @usage | ||
337 | local system = require('system') | ||
338 | system.listconsoleflags(io.stdout) -- List all the available flags and their current status | ||
339 | |||
340 | local flags = system.getconsoleflags(io.stdout) | ||
341 | assert(system.setconsoleflags(io.stdout, | ||
342 | flags + system.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
343 | |||
344 | system.listconsoleflags(io.stdout) -- List again to check the differences | ||
345 | */ | ||
346 | static int lst_setconsoleflags(lua_State *L) | ||
347 | { | ||
348 | #ifdef _WIN32 | ||
349 | HANDLE console_handle = get_console_handle(L, 0); | ||
350 | if (console_handle == NULL) { | ||
351 | return 2; // error message is already on the stack | ||
352 | } | ||
353 | LSBF_BITFLAG new_console_mode = lsbf_checkbitflags(L, 2); | ||
354 | |||
355 | DWORD prev_console_mode; | ||
356 | if (GetConsoleMode(console_handle, &prev_console_mode) == 0) | ||
357 | { | ||
358 | termFormatError(L, GetLastError(), "failed to get console mode"); | ||
359 | return 2; | ||
360 | } | ||
361 | |||
362 | int success = SetConsoleMode(console_handle, new_console_mode) != 0; | ||
363 | if (!success) | ||
364 | { | ||
365 | termFormatError(L, GetLastError(), "failed to set console mode"); | ||
366 | return 2; | ||
367 | } | ||
368 | |||
369 | #endif | ||
370 | lua_pushboolean(L, 1); | ||
371 | return 1; | ||
372 | } | ||
373 | |||
374 | |||
375 | |||
376 | /*** | ||
377 | Gets console flags (Windows). | ||
378 | @function getconsoleflags | ||
379 | @tparam file file the file-handle to get the flags from. | ||
380 | @treturn[1] bitflags the current console flags. | ||
381 | @treturn[2] nil | ||
382 | @treturn[2] string error message | ||
383 | @usage | ||
384 | local system = require('system') | ||
385 | |||
386 | local flags = system.getconsoleflags(io.stdout) | ||
387 | print("Current stdout flags:", tostring(flags)) | ||
388 | |||
389 | if flags:has(system.COF_VIRTUAL_TERMINAL_PROCESSING + system.COF_PROCESSED_OUTPUT) then | ||
390 | print("Both flags are set") | ||
391 | else | ||
392 | print("At least one flag is not set") | ||
393 | end | ||
394 | */ | ||
395 | static int lst_getconsoleflags(lua_State *L) | ||
396 | { | ||
397 | DWORD console_mode = 0; | ||
398 | |||
399 | #ifdef _WIN32 | ||
400 | HANDLE console_handle = get_console_handle(L, 1); | ||
401 | if (console_handle == NULL) { | ||
402 | return 2; // error message is already on the stack | ||
403 | } | ||
404 | |||
405 | if (GetConsoleMode(console_handle, &console_mode) == 0) | ||
406 | { | ||
407 | lua_pushnil(L); | ||
408 | lua_pushliteral(L, "failed to get console mode"); | ||
409 | return 2; | ||
410 | } | ||
411 | |||
412 | #endif | ||
413 | lsbf_pushbitflags(L, console_mode); | ||
414 | return 1; | ||
415 | } | ||
416 | |||
417 | |||
418 | |||
419 | /*------------------------------------------------------------------------- | ||
420 | * Unix tcgetattr/tcsetattr functions | ||
421 | *-------------------------------------------------------------------------*/ | ||
422 | // Code modified from the LuaPosix library by Gary V. Vaughan | ||
423 | // see https://github.com/luaposix/luaposix | ||
424 | |||
425 | /*** | ||
426 | Get termios state. | ||
427 | The terminal attributes is a table with the following fields: | ||
428 | |||
429 | - `iflag` input flags | ||
430 | - `oflag` output flags | ||
431 | - `cflag` control flags | ||
432 | - `lflag` local flags | ||
433 | - `ispeed` input speed | ||
434 | - `ospeed` output speed | ||
435 | - `cc` control characters | ||
436 | |||
437 | @function tcgetattr | ||
438 | @tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` | ||
439 | @treturn[1] termios terminal attributes, if successful. On Windows the bitflags are all 0, and the `cc` table is empty. | ||
440 | @treturn[2] nil | ||
441 | @treturn[2] string error message | ||
442 | @treturn[2] int errnum | ||
443 | @return error message if failed | ||
444 | @usage | ||
445 | local system = require('system') | ||
446 | |||
447 | local status = assert(tcgetattr(io.stdin)) | ||
448 | if status.iflag:has(system.I_IGNBRK) then | ||
449 | print("Ignoring break condition") | ||
450 | end | ||
451 | */ | ||
452 | static int lst_tcgetattr(lua_State *L) | ||
453 | { | ||
454 | #ifndef _WIN32 | ||
455 | int r, i; | ||
456 | struct termios t; | ||
457 | int fd = get_console_handle(L); | ||
458 | |||
459 | r = tcgetattr(fd, &t); | ||
460 | if (r == -1) return pusherror(L, NULL); | ||
461 | |||
462 | lua_newtable(L); | ||
463 | lsbf_pushbitflags(L, t.c_iflag); | ||
464 | lua_setfield(L, -2, "iflag"); | ||
465 | |||
466 | lsbf_pushbitflags(L, t.c_oflag); | ||
467 | lua_setfield(L, -2, "oflag"); | ||
468 | |||
469 | lsbf_pushbitflags(L, t.c_lflag); | ||
470 | lua_setfield(L, -2, "lflag"); | ||
471 | |||
472 | lsbf_pushbitflags(L, t.c_cflag); | ||
473 | lua_setfield(L, -2, "cflag"); | ||
474 | |||
475 | lua_pushinteger(L, cfgetispeed(&t)); | ||
476 | lua_setfield(L, -2, "ispeed"); | ||
477 | |||
478 | lua_pushinteger(L, cfgetospeed(&t)); | ||
479 | lua_setfield(L, -2, "ospeed"); | ||
480 | |||
481 | lua_newtable(L); | ||
482 | for (i=0; i<NCCS; i++) | ||
483 | { | ||
484 | lua_pushinteger(L, i); | ||
485 | lua_pushinteger(L, t.c_cc[i]); | ||
486 | lua_settable(L, -3); | ||
487 | } | ||
488 | lua_setfield(L, -2, "cc"); | ||
489 | |||
490 | #else | ||
491 | lua_newtable(L); | ||
492 | lsbf_pushbitflags(L, 0); | ||
493 | lua_setfield(L, -2, "iflag"); | ||
494 | lsbf_pushbitflags(L, 0); | ||
495 | lua_setfield(L, -2, "oflag"); | ||
496 | lsbf_pushbitflags(L, 0); | ||
497 | lua_setfield(L, -2, "lflag"); | ||
498 | lsbf_pushbitflags(L, 0); | ||
499 | lua_setfield(L, -2, "cflag"); | ||
500 | lua_pushinteger(L, 0); | ||
501 | lua_setfield(L, -2, "ispeed"); | ||
502 | lua_pushinteger(L, 0); | ||
503 | lua_setfield(L, -2, "ospeed"); | ||
504 | lua_newtable(L); | ||
505 | lua_setfield(L, -2, "cc"); | ||
506 | |||
507 | #endif | ||
508 | return 1; | ||
509 | } | ||
510 | |||
511 | |||
512 | |||
513 | /*** | ||
514 | Set termios state. | ||
515 | This function will set the flags as given. | ||
516 | |||
517 | The `I_`, `O_`, and `L_` constants are available on the module table. They are the respective | ||
518 | flags for the `iflags`, `oflags`, and `lflags` bitmasks. | ||
519 | |||
520 | To see flag status and constant names check `listtermflags`. For their meaning check | ||
521 | [the manpage](https://www.man7.org/linux/man-pages/man3/termios.3.html). | ||
522 | |||
523 | _Note_: not all combinations of flags are allowed, as some are mutually exclusive or mutually required. | ||
524 | See [setconsolemode documentation](https://learn.microsoft.com/en-us/windows/console/setconsolemode) | ||
525 | |||
526 | _Note_: only `iflag`, `oflag`, and `lflag` are supported at the moment. The other fields are ignored. | ||
527 | @function tcsetattr | ||
528 | @tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` | ||
529 | @int actions one of `TCSANOW`, `TCSADRAIN`, `TCSAFLUSH` | ||
530 | @tparam table termios a table with bitflag fields: | ||
531 | @tparam[opt] bitflags termios.iflag if given will set the input flags | ||
532 | @tparam[opt] bitflags termios.oflag if given will set the output flags | ||
533 | @tparam[opt] bitflags termios.lflag if given will set the local flags | ||
534 | @treturn[1] bool `true`, if successful. Always returns `true` on Windows. | ||
535 | @return[2] nil | ||
536 | @treturn[2] string error message | ||
537 | @treturn[2] int errnum | ||
538 | @usage | ||
539 | local system = require('system') | ||
540 | |||
541 | local status = assert(tcgetattr(io.stdin)) | ||
542 | if not status.lflag:has(system.L_ECHO) then | ||
543 | -- if echo is off, turn echoing newlines on | ||
544 | tcsetattr(io.stdin, system.TCSANOW, { lflag = status.lflag + system.L_ECHONL })) | ||
545 | end | ||
546 | */ | ||
547 | static int lst_tcsetattr(lua_State *L) | ||
548 | { | ||
549 | #ifndef _WIN32 | ||
550 | struct termios t; | ||
551 | int r, i; | ||
552 | int fd = get_console_handle(L); // first is the console handle | ||
553 | int act = luaL_checkinteger(L, 2); // second is the action to take | ||
554 | luaL_checktype(L, 3, LUA_TTABLE); // third is the termios table with fields | ||
555 | |||
556 | r = tcgetattr(fd, &t); | ||
557 | if (r == -1) return pusherror(L, NULL); | ||
558 | |||
559 | lua_getfield(L, 3, "iflag"); | ||
560 | if (!lua_isnil(L, -1)) { | ||
561 | t.c_iflag = lsbf_checkbitflags(L, -1); | ||
562 | } | ||
563 | lua_pop(L, 1); | ||
564 | |||
565 | lua_getfield(L, 3, "oflag"); | ||
566 | if (!lua_isnil(L, -1)) { | ||
567 | t.c_oflag = lsbf_checkbitflags(L, -1); | ||
568 | } | ||
569 | lua_pop(L, 1); | ||
570 | |||
571 | lua_getfield(L, 3, "lflag"); | ||
572 | if (!lua_isnil(L, -1)) { | ||
573 | t.c_lflag = lsbf_checkbitflags(L, -1); | ||
574 | } | ||
575 | lua_pop(L, 1); | ||
576 | |||
577 | // Skipping the others for now | ||
578 | |||
579 | // lua_getfield(L, 3, "cflag"); t.c_cflag = optint(L, -1, 0); lua_pop(L, 1); | ||
580 | // lua_getfield(L, 3, "ispeed"); cfsetispeed( &t, optint(L, -1, B0) ); lua_pop(L, 1); | ||
581 | // lua_getfield(L, 3, "ospeed"); cfsetospeed( &t, optint(L, -1, B0) ); lua_pop(L, 1); | ||
582 | |||
583 | // lua_getfield(L, 3, "cc"); | ||
584 | // for (i=0; i<NCCS; i++) | ||
585 | // { | ||
586 | // lua_pushinteger(L, i); | ||
587 | // lua_gettable(L, -2); | ||
588 | // t.c_cc[i] = optint(L, -1, 0); | ||
589 | // lua_pop(L, 1); | ||
590 | // } | ||
591 | |||
592 | r = tcsetattr(fd, act, &t); | ||
593 | if (r == -1) return pusherror(L, NULL); | ||
594 | #endif | ||
595 | |||
596 | lua_pushboolean(L, 1); | ||
597 | return 1; | ||
598 | } | ||
599 | |||
600 | |||
601 | |||
602 | /*** | ||
603 | Enables or disables non-blocking mode for a file (Posix). | ||
604 | @function setnonblock | ||
605 | @tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` | ||
606 | @tparam boolean make_non_block a truthy value will enable non-blocking mode, a falsy value will disable it. | ||
607 | @treturn[1] bool `true`, if successful | ||
608 | @treturn[2] nil | ||
609 | @treturn[2] string error message | ||
610 | @treturn[2] int errnum | ||
611 | @see getnonblock | ||
612 | @usage | ||
613 | local sys = require('system') | ||
614 | |||
615 | -- set io.stdin to non-blocking mode | ||
616 | local old_setting = sys.getnonblock(io.stdin) | ||
617 | sys.setnonblock(io.stdin, true) | ||
618 | |||
619 | -- do stuff | ||
620 | |||
621 | -- restore old setting | ||
622 | sys.setnonblock(io.stdin, old_setting) | ||
623 | */ | ||
624 | static int lst_setnonblock(lua_State *L) | ||
625 | { | ||
626 | #ifndef _WIN32 | ||
627 | |||
628 | int fd = get_console_handle(L); | ||
629 | |||
630 | int flags = fcntl(fd, F_GETFL, 0); | ||
631 | if (flags == -1) { | ||
632 | return pusherror(L, "Error getting handle flags: "); | ||
633 | } | ||
634 | if (lua_toboolean(L, 2)) { | ||
635 | // truthy: set non-blocking | ||
636 | flags |= O_NONBLOCK; | ||
637 | } else { | ||
638 | // falsy: set disable non-blocking | ||
639 | flags &= ~O_NONBLOCK; | ||
640 | } | ||
641 | if (fcntl(fd, F_SETFL, flags) == -1) { | ||
642 | return pusherror(L, "Error changing O_NONBLOCK: "); | ||
643 | } | ||
644 | |||
645 | #endif | ||
646 | |||
647 | lua_pushboolean(L, 1); | ||
648 | return 1; | ||
649 | } | ||
650 | |||
651 | |||
652 | |||
653 | /*** | ||
654 | Gets non-blocking mode status for a file (Posix). | ||
655 | @function getnonblock | ||
656 | @tparam file fd file handle to operate on, one of `io.stdin`, `io.stdout`, `io.stderr` | ||
657 | @treturn[1] bool `true` if set to non-blocking, `false` if not. Always returns `false` on Windows. | ||
658 | @treturn[2] nil | ||
659 | @treturn[2] string error message | ||
660 | @treturn[2] int errnum | ||
661 | */ | ||
662 | static int lst_getnonblock(lua_State *L) | ||
663 | { | ||
664 | #ifndef _WIN32 | ||
665 | |||
666 | int fd = get_console_handle(L); | ||
667 | |||
668 | // Set O_NONBLOCK | ||
669 | int flags = fcntl(fd, F_GETFL, 0); | ||
670 | if (flags == -1) { | ||
671 | return pusherror(L, "Error getting handle flags: "); | ||
672 | } | ||
673 | if (flags & O_NONBLOCK) { | ||
674 | lua_pushboolean(L, 1); | ||
675 | } else { | ||
676 | lua_pushboolean(L, 0); | ||
677 | } | ||
678 | |||
679 | #else | ||
680 | lua_pushboolean(L, 0); | ||
681 | |||
682 | #endif | ||
683 | return 1; | ||
684 | } | ||
685 | |||
686 | |||
687 | |||
688 | /*------------------------------------------------------------------------- | ||
689 | * Reading keyboard input | ||
690 | *-------------------------------------------------------------------------*/ | ||
691 | |||
692 | /*** | ||
693 | Reads a key from the console non-blocking. | ||
694 | On Posix, `io.stdin` must be set to non-blocking mode using `setnonblock` | ||
695 | before calling this function. Otherwise it will block. | ||
696 | |||
697 | @function readkey | ||
698 | @treturn[1] integer the key code of the key that was pressed | ||
699 | @treturn[2] nil if no key was pressed | ||
700 | */ | ||
701 | static int lst_readkey(lua_State *L) { | ||
702 | #ifdef _WIN32 | ||
703 | if (_kbhit()) { | ||
704 | lua_pushinteger(L, _getch()); | ||
705 | return 1; | ||
706 | } | ||
707 | return 0; | ||
708 | |||
709 | #else | ||
710 | char ch; | ||
711 | if (read(STDIN_FILENO, &ch, 1) > 0) { | ||
712 | lua_pushinteger(L, ch); | ||
713 | return 1; | ||
714 | } | ||
715 | return 0; | ||
716 | |||
717 | #endif | ||
718 | } | ||
719 | |||
720 | /*** | ||
721 | Checks if a key has been pressed without reading it. | ||
722 | On Posix, `io.stdin` must be set to non-blocking mode using `setnonblock` | ||
723 | before calling this function. Otherwise it will block. | ||
724 | |||
725 | @function keypressed | ||
726 | @treturn boolean true if a key has been pressed, nil if not. | ||
727 | */ | ||
728 | static int lst_keypressed(lua_State *L) { | ||
729 | #ifdef _WIN32 | ||
730 | if (kbhit()) { | ||
731 | lua_pushboolean(L, 1); | ||
732 | return 1; | ||
733 | } | ||
734 | return 0; | ||
735 | |||
736 | #else | ||
737 | char ch; | ||
738 | if (read(STDIN_FILENO, &ch, 1) > 0) { | ||
739 | // key was read, push back to stdin | ||
740 | ungetc(ch, stdin); | ||
741 | lua_pushboolean(L, 1); | ||
742 | return 1; | ||
743 | } | ||
744 | return 0; | ||
745 | |||
746 | #endif | ||
747 | } | ||
748 | |||
749 | /*------------------------------------------------------------------------- | ||
750 | * Retrieve terminal size | ||
751 | *-------------------------------------------------------------------------*/ | ||
752 | |||
753 | |||
754 | /*** | ||
755 | Get the size of the terminal in columns and rows. | ||
756 | @function termsize | ||
757 | @treturn[1] int the number of columns | ||
758 | @treturn[1] int the number of rows | ||
759 | @treturn[2] nil | ||
760 | @treturn[2] string error message | ||
761 | */ | ||
762 | static int lst_termsize(lua_State *L) { | ||
763 | int columns, rows; | ||
764 | |||
765 | #ifdef _WIN32 | ||
766 | CONSOLE_SCREEN_BUFFER_INFO csbi; | ||
767 | if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { | ||
768 | termFormatError(L, GetLastError(), "Failed to get terminal size."); | ||
769 | return 2; | ||
770 | } | ||
771 | columns = csbi.srWindow.Right - csbi.srWindow.Left + 1; | ||
772 | rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; | ||
773 | |||
774 | #else | ||
775 | struct winsize ws; | ||
776 | if (ioctl(1, TIOCGWINSZ, &ws) == -1) { | ||
777 | return pusherror(L, "Failed to get terminal size."); | ||
778 | } | ||
779 | columns = ws.ws_col; | ||
780 | rows = ws.ws_row; | ||
781 | |||
782 | #endif | ||
783 | lua_pushinteger(L, columns); | ||
784 | lua_pushinteger(L, rows); | ||
785 | return 2; | ||
786 | } | ||
787 | |||
788 | |||
789 | |||
32 | /*------------------------------------------------------------------------- | 790 | /*------------------------------------------------------------------------- |
33 | * Initializes module | 791 | * Initializes module |
34 | *-------------------------------------------------------------------------*/ | 792 | *-------------------------------------------------------------------------*/ |
793 | |||
794 | static luaL_Reg func[] = { | ||
795 | { "isatty", lst_isatty }, | ||
796 | { "getconsoleflags", lst_getconsoleflags }, | ||
797 | { "setconsoleflags", lst_setconsoleflags }, | ||
798 | { "tcgetattr", lst_tcgetattr }, | ||
799 | { "tcsetattr", lst_tcsetattr }, | ||
800 | { "getnonblock", lst_setnonblock }, | ||
801 | { "setnonblock", lst_setnonblock }, | ||
802 | { "readkey", lst_readkey }, | ||
803 | { "keypressed", lst_keypressed }, | ||
804 | { "termsize", lst_termsize }, | ||
805 | { NULL, NULL } | ||
806 | }; | ||
807 | |||
808 | |||
809 | |||
35 | void term_open(lua_State *L) { | 810 | void term_open(lua_State *L) { |
811 | // set up constants and export the constants in module table | ||
812 | initialize_valid_flags(); | ||
813 | // Windows flags | ||
814 | for (int i = 0; win_console_in_flags[i].name != NULL; i++) | ||
815 | { | ||
816 | lsbf_pushbitflags(L, win_console_in_flags[i].value); | ||
817 | lua_setfield(L, -2, win_console_in_flags[i].name); | ||
818 | } | ||
819 | for (int i = 0; win_console_out_flags[i].name != NULL; i++) | ||
820 | { | ||
821 | lsbf_pushbitflags(L, win_console_out_flags[i].value); | ||
822 | lua_setfield(L, -2, win_console_out_flags[i].name); | ||
823 | } | ||
824 | // Unix flags | ||
825 | for (int i = 0; nix_console_i_flags[i].name != NULL; i++) | ||
826 | { | ||
827 | lsbf_pushbitflags(L, nix_console_i_flags[i].value); | ||
828 | lua_setfield(L, -2, nix_console_i_flags[i].name); | ||
829 | } | ||
830 | for (int i = 0; nix_console_o_flags[i].name != NULL; i++) | ||
831 | { | ||
832 | lsbf_pushbitflags(L, nix_console_o_flags[i].value); | ||
833 | lua_setfield(L, -2, nix_console_o_flags[i].name); | ||
834 | } | ||
835 | for (int i = 0; nix_console_l_flags[i].name != NULL; i++) | ||
836 | { | ||
837 | lsbf_pushbitflags(L, nix_console_l_flags[i].value); | ||
838 | lua_setfield(L, -2, nix_console_l_flags[i].name); | ||
839 | } | ||
840 | // Unix tcsetattr actions | ||
841 | for (int i = 0; nix_tcsetattr_actions[i].name != NULL; i++) | ||
842 | { | ||
843 | lua_pushinteger(L, nix_tcsetattr_actions[i].value); | ||
844 | lua_setfield(L, -2, nix_tcsetattr_actions[i].name); | ||
845 | } | ||
846 | |||
847 | // export functions | ||
36 | luaL_setfuncs(L, func, 0); | 848 | luaL_setfuncs(L, func, 0); |
37 | } | 849 | } |
diff --git a/system/init.lua b/system/init.lua index 77e0c3b..c3ea94d 100644 --- a/system/init.lua +++ b/system/init.lua | |||
@@ -1,2 +1,210 @@ | |||
1 | local system = require 'system.core' | 1 | --- Lua System Library. |
2 | return system | 2 | -- @module init |
3 | |||
4 | local sys = require 'system.core' | ||
5 | local global_backup -- global backup for terminal settings | ||
6 | |||
7 | |||
8 | |||
9 | local add_gc_method do | ||
10 | -- feature detection; __GC meta-method, not available in all Lua versions | ||
11 | local has_gc = false | ||
12 | local tt = setmetatable({}, { -- luacheck: ignore | ||
13 | __gc = function() has_gc = true end | ||
14 | }) | ||
15 | |||
16 | -- clear table and run GC to trigger | ||
17 | tt = nil | ||
18 | collectgarbage() | ||
19 | collectgarbage() | ||
20 | |||
21 | |||
22 | if has_gc then | ||
23 | -- use default GC mechanism since it is available | ||
24 | function add_gc_method(t, f) | ||
25 | setmetatable(t, { __gc = f }) | ||
26 | end | ||
27 | else | ||
28 | -- create workaround using a proxy userdata, typical for Lua 5.1 | ||
29 | function add_gc_method(t, f) | ||
30 | local proxy = newproxy(true) | ||
31 | getmetatable(proxy).__gc = function() | ||
32 | t["__gc_proxy"] = nil | ||
33 | f(t) | ||
34 | end | ||
35 | t["__gc_proxy"] = proxy | ||
36 | end | ||
37 | end | ||
38 | end | ||
39 | |||
40 | |||
41 | |||
42 | --- Returns a backup of terminal setting for stdin/out/err. | ||
43 | -- Handles terminal/console flags and non-block flags on the streams. | ||
44 | -- Backs up terminal/console flags only if a stream is a tty. | ||
45 | -- @return table with backup of terminal settings | ||
46 | function sys.termbackup() | ||
47 | local backup = {} | ||
48 | |||
49 | if sys.isatty(io.stdin) then | ||
50 | backup.console_in = sys.getconsoleflags(io.stdin) | ||
51 | backup.term_in = sys.tcgetattr(io.stdin) | ||
52 | end | ||
53 | if sys.isatty(io.stdout) then | ||
54 | backup.console_out = sys.getconsoleflags(io.stdout) | ||
55 | backup.term_out = sys.tcgetattr(io.stdout) | ||
56 | end | ||
57 | if sys.isatty(io.stderr) then | ||
58 | backup.console_err = sys.getconsoleflags(io.stderr) | ||
59 | backup.term_err = sys.tcgetattr(io.stderr) | ||
60 | end | ||
61 | |||
62 | backup.block_in = sys.getnonblock(io.stdin) | ||
63 | backup.block_out = sys.getnonblock(io.stdout) | ||
64 | backup.block_err = sys.getnonblock(io.stderr) | ||
65 | |||
66 | return backup | ||
67 | end | ||
68 | |||
69 | |||
70 | |||
71 | --- Restores terminal settings from a backup | ||
72 | -- @tparam table backup the backup of terminal settings, see `termbackup`. | ||
73 | -- @treturn boolean true | ||
74 | function sys.termrestore(backup) | ||
75 | if backup.console_in then sys.setconsoleflags(io.stdin, backup.console_in) end | ||
76 | if backup.term_in then sys.tcsetattr(io.stdin, sys.TCSANOW, backup.term_in) end | ||
77 | if backup.console_out then sys.setconsoleflags(io.stdout, backup.console_out) end | ||
78 | if backup.term_out then sys.tcsetattr(io.stdout, sys.TCSANOW, backup.term_out) end | ||
79 | if backup.console_err then sys.setconsoleflags(io.stderr, backup.console_err) end | ||
80 | if backup.term_err then sys.tcsetattr(io.stderr, sys.TCSANOW, backup.term_err) end | ||
81 | |||
82 | if backup.block_in ~= nil then sys.setnonblock(io.stdin, backup.block_in) end | ||
83 | if backup.block_out ~= nil then sys.setnonblock(io.stdout, backup.block_out) end | ||
84 | if backup.block_err ~= nil then sys.setnonblock(io.stderr, backup.block_err) end | ||
85 | return true | ||
86 | end | ||
87 | |||
88 | |||
89 | |||
90 | --- Backs up terminal settings and restores them on application exit. | ||
91 | -- Calls `termbackup` to back up terminal settings and sets up a GC method to | ||
92 | -- automatically restore them on application exit (also works on Lua 5.1). | ||
93 | -- @treturn[1] boolean true | ||
94 | -- @treturn[2] nil if the backup was already created | ||
95 | -- @treturn[2] string error message | ||
96 | function sys.autotermrestore() | ||
97 | if global_backup then | ||
98 | return nil, "global terminal backup was already set up" | ||
99 | end | ||
100 | global_backup = sys.termbackup() | ||
101 | add_gc_method(global_backup, function(self) | ||
102 | sys.termrestore(self) end) | ||
103 | return true | ||
104 | end | ||
105 | |||
106 | |||
107 | |||
108 | do | ||
109 | local oldunpack = unpack or table.unpack | ||
110 | local pack = function(...) return { n = select("#", ...), ... } end | ||
111 | local unpack = function(t) return oldunpack(t, 1, t.n) end | ||
112 | |||
113 | --- Wraps a function to automatically restore terminal settings upon returning. | ||
114 | -- Calls `termbackup` before calling the function and `termrestore` after. | ||
115 | -- @tparam function f function to wrap | ||
116 | -- @treturn function wrapped function | ||
117 | function sys.termwrap(f) | ||
118 | if type(f) ~= "function" then | ||
119 | error("arg #1 to wrap, expected function, got " .. type(f), 2) | ||
120 | end | ||
121 | |||
122 | return function(...) | ||
123 | local bu = sys.termbackup() | ||
124 | local results = pack(f(...)) | ||
125 | sys.termrestore(bu) | ||
126 | return unpack(results) | ||
127 | end | ||
128 | end | ||
129 | end | ||
130 | |||
131 | |||
132 | |||
133 | --- Debug function for console flags (Windows). | ||
134 | -- Pretty prints the current flags set for the handle. | ||
135 | -- @param fh file handle (`io.stdin`, `io.stdout`, `io.stderr`) | ||
136 | -- @usage -- Print the flags for stdin/out/err | ||
137 | -- system.listconsoleflags(io.stdin) | ||
138 | -- system.listconsoleflags(io.stdout) | ||
139 | -- system.listconsoleflags(io.stderr) | ||
140 | function sys.listconsoleflags(fh) | ||
141 | local flagtype | ||
142 | if fh == io.stdin then | ||
143 | print "------ STDIN FLAGS WINDOWS ------" | ||
144 | flagtype = "CIF_" | ||
145 | elseif fh == io.stdout then | ||
146 | print "------ STDOUT FLAGS WINDOWS ------" | ||
147 | flagtype = "COF_" | ||
148 | elseif fh == io.stderr then | ||
149 | print "------ STDERR FLAGS WINDOWS ------" | ||
150 | flagtype = "COF_" | ||
151 | end | ||
152 | |||
153 | local flags = assert(sys.getconsoleflags(fh)) | ||
154 | local out = {} | ||
155 | for k,v in pairs(sys) do | ||
156 | if type(k) == "string" and k:sub(1,4) == flagtype then | ||
157 | if flags:has(v) then | ||
158 | out[#out+1] = string.format("%10d [x] %s",v:value(),k) | ||
159 | else | ||
160 | out[#out+1] = string.format("%10d [ ] %s",v:value(),k) | ||
161 | end | ||
162 | end | ||
163 | end | ||
164 | table.sort(out) | ||
165 | for k,v in pairs(out) do | ||
166 | print(v) | ||
167 | end | ||
168 | end | ||
169 | |||
170 | |||
171 | |||
172 | --- Debug function for terminal flags (Posix). | ||
173 | -- Pretty prints the current flags set for the handle. | ||
174 | -- @param fh file handle (`io.stdin`, `io.stdout`, `io.stderr`) | ||
175 | -- @usage -- Print the flags for stdin/out/err | ||
176 | -- system.listconsoleflags(io.stdin) | ||
177 | -- system.listconsoleflags(io.stdout) | ||
178 | -- system.listconsoleflags(io.stderr) | ||
179 | function sys.listtermflags(fh) | ||
180 | if fh == io.stdin then | ||
181 | print "------ STDIN FLAGS POSIX ------" | ||
182 | elseif fh == io.stdout then | ||
183 | print "------ STDOUT FLAGS POSIX ------" | ||
184 | elseif fh == io.stderr then | ||
185 | print "------ STDERR FLAGS POSIX ------" | ||
186 | end | ||
187 | |||
188 | local flags = assert(sys.tcgetattr(fh)) | ||
189 | for _, flagtype in ipairs { "iflag", "oflag", "lflag" } do | ||
190 | local prefix = flagtype:sub(1,1):upper() .. "_" -- I_, O_, or L_, the constant prefixes | ||
191 | local out = {} | ||
192 | for k,v in pairs(sys) do | ||
193 | if type(k) == "string" and k:sub(1,2) == prefix then | ||
194 | if flags[flagtype]:has(v) then | ||
195 | out[#out+1] = string.format("%10d [x] %s",v:value(),k) | ||
196 | else | ||
197 | out[#out+1] = string.format("%10d [ ] %s",v:value(),k) | ||
198 | end | ||
199 | end | ||
200 | end | ||
201 | table.sort(out) | ||
202 | for k,v in pairs(out) do | ||
203 | print(v) | ||
204 | end | ||
205 | end | ||
206 | end | ||
207 | |||
208 | |||
209 | |||
210 | return sys | ||