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 | ||
