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 /examples | |
parent | 47c24eed0191f8f72646be63dee94ac2b35eb062 (diff) | |
download | luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.zip |
feat(term): getting/setting terminal config flags
Diffstat (limited to 'examples')
-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 |
6 files changed, 358 insertions, 0 deletions
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 @@ +-- This example shows how to remove platform differences to create a +-- cross-platform level playing field. + +local sys = require "system" + + + +if sys.is_windows then + -- Windows holds multiple copies of environment variables, to ensure `getenv` + -- returns what `setenv` sets we need to use the `system.getenv` instead of + -- `os.getenv`. + os.getenv = sys.getenv -- luacheck: ignore + + -- Set up the terminal to handle ANSI escape sequences on Windows. + if sys.isatty(io.stdout) then + sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) + end + if sys.isatty(io.stderr) then + sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) + end + if sys.isatty(io.stdin) then + sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdout) + sys.ENABLE_VIRTUAL_TERMINAL_INPUT) + end + + +else + -- On Posix, one can set a variable to an empty string, but on Windows, this + -- will remove the variable from the environment. To make this consistent + -- across platforms, we will remove the variable from the environment if the + -- value is an empty string. + local old_setenv = sys.setenv + function sys.setenv(name, value) + if value == "" then value = nil end + return old_setenv(name, value) + end +end + 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 @@ +local sys = require "system" + +-- Print the Windows Console flags for stdin +sys.listconsoleflags(io.stdin) + +-- Print the Posix termios flags for stdin +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 @@ +local sys = require "system" + +print [[ + +This example shows how to disable the "echo" of characters read to the console, +useful for reading secrets from the user. + +]] + +--- Function to read from stdin without echoing the input (for secrets etc). +-- It will (in a platform agnostic way) disable echo on the terminal, read the +-- input, and then re-enable echo. +-- @param ... Arguments to pass to `io.stdin:read()` +-- @return the results of `io.stdin:read(...)` +local function read_secret(...) + local w_oldflags, p_oldflags + + if sys.isatty(io.stdin) then + -- backup settings, configure echo flags + w_oldflags = sys.getconsoleflags(io.stdin) + p_oldflags = sys.tcgetattr(io.stdin) + -- set echo off to not show password on screen + assert(sys.setconsoleflags(io.stdin, w_oldflags - sys.CIF_ECHO_INPUT)) + assert(sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = p_oldflags.lflag - sys.L_ECHO })) + end + + local secret, err = io.stdin:read(...) + + -- restore settings + if sys.isatty(io.stdin) then + io.stdout:write("\n") -- Add newline after reading the password + sys.setconsoleflags(io.stdin, w_oldflags) + sys.tcsetattr(io.stdin, sys.TCSANOW, p_oldflags) + end + + return secret, err +end + + + +-- Get username +io.write("Username: ") +local username = io.stdin:read("*l") + +-- Get the secret +io.write("Password: ") +local password = read_secret("*l") + +-- Get domainname +io.write("Domain : ") +local domain = io.stdin:read("*l") + + +-- Print the results +print("") +print("Here's what we got:") +print(" username: " .. username) +print(" password: " .. password) +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 @@ +local sys = require "system" + +print [[ + +This example shows how to do a non-blocking read from the cli. + +]] + +-- setup Windows console to handle ANSI processing +local of_in = sys.getconsoleflags(io.stdin) +local of_out = sys.getconsoleflags(io.stdout) +sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) +sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT) + +-- setup Posix terminal to use non-blocking mode, and disable line-mode +local of_attr = sys.tcgetattr(io.stdin) +local of_block = sys.getnonblock(io.stdin) +sys.setnonblock(io.stdin, true) +sys.tcsetattr(io.stdin, sys.TCSANOW, { + lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, -- disable canonical mode and echo +}) + +-- cursor sequences +local get_cursor_pos = "\27[6n" + + + +local read_input do + local left_over_key + + -- Reads a single key, if it is a 27 (start of ansi escape sequence) then it reads + -- the rest of the sequence. + -- This function is non-blocking, and will return nil if no key is available. + -- In case of an ANSI sequence, it will return the full sequence as a string. + -- @return nil|string the key read, or nil if no key is available + function read_input() + if left_over_key then + -- we still have a cached key, return it + local key = left_over_key + left_over_key = nil + return string.char(key) + end + + local key = sys.readkey() + if key == nil then + return nil + end + + if key ~= 27 then + return string.char(key) + end + + -- looks like an ansi escape sequence, immediately read next char + -- as an heuristic against manually typing escape sequences + local brack = sys.readkey() + if brack ~= 91 then + -- not the expected [ character, so we return the key as is + -- and store the extra key read for the next call + left_over_key = brack + return string.char(key) + end + + -- escape sequence detected, read the rest of the sequence + local seq = { key, brack } + while true do + key = sys.readkey() + table.insert(seq, key) + if (key >= 65 and key <= 90) or (key >= 97 and key <= 126) then + -- end of sequence, return the full sequence + return string.char((unpack or table.unpack)(seq)) + end + end + -- unreachable + end +end + + + +print("Press a key, or 'A' to get cursor position, 'ESC' to exit") +while true do + local key + + -- wait for a key, and sleep a bit to not do a busy-wait + while not key do + key = read_input() + if not key then sys.sleep(0.1) end + end + + if key == "A" then io.write(get_cursor_pos); io.flush() end + + -- check if we got a key or ANSI sequence + if #key == 1 then + -- just a key + local b = key:byte() + if b < 32 then + key = "." -- replace control characters with a simple "." to not mess up the screen + end + + print("you pressed: " .. key .. " (" .. b .. ")") + if b == 27 then + print("Escape pressed, exiting") + break + end + + else + -- we got an ANSI sequence + local seq = { key:byte(1, #key) } + print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")") + end +end + + + +-- Clean up afterwards +sys.setnonblock(io.stdin, false) +sys.setconsoleflags(io.stdout, of_out) +sys.setconsoleflags(io.stdin, of_in) +sys.tcsetattr(io.stdin, sys.TCSANOW, of_attr) +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 @@ +local sys = require("system") + +print [[ + +An example to display a spinner, whilst a long running task executes. + +]] + + +-- start make backup, to auto-restore on exit +sys.autotermrestore() +-- configure console +sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT) +local of = sys.tcgetattr(io.stdin) +sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = of.lflag - sys.L_ICANON - sys.L_ECHO }) +sys.setnonblock(io.stdin, true) + + + +local function hideCursor() + io.write("\27[?25l") + io.flush() +end + +local function showCursor() + io.write("\27[?25h") + io.flush() +end + +local function left(n) + io.write("\27[",n or 1,"D") + io.flush() +end + + + +local spinner do + local spin = [[|/-\]] + local i = 1 + spinner = function() + hideCursor() + io.write(spin:sub(i, i)) + left() + i = i + 1 + if i > #spin then i = 1 end + + if sys.keypressed() then + sys.readkey() -- consume key pressed + io.write(" "); + left() + showCursor() + return true + else + return false + end + end +end + +io.stdout:write("press any key to stop the spinner... ") +while not spinner() do + sys.sleep(0.1) +end + +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 @@ +local sys = require "system" + +print [[ + +This example will draw a snake like spiral on the screen. Showing ANSI escape +codes for moving the cursor around. + +]] + +-- backup term settings with auto-restore on exit +sys.autotermrestore() + +-- setup Windows console to handle ANSI processing +sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) + +-- start drawing the spiral. +-- start from current pos, then right, then up, then left, then down, and again. +local x, y = 1, 1 -- current position +local dx, dy = 1, 0 -- direction after each step +local wx, wy = 30, 30 -- width and height of the room +local mx, my = 1, 1 -- margin + +-- commands to move the cursor +local move_left = "\27[1D" +local move_right = "\27[1C" +local move_up = "\27[1A" +local move_down = "\27[1B" + +-- create room: 30 empty lines +print(("\n"):rep(wy)) +local move = move_right + +while wx > 0 and wy > 0 do + sys.sleep(0.01) -- slow down the drawing a little + io.write("*" .. move_left .. move ) + io.flush() + x = x + dx + y = y + dy + + if x > wx and move == move_right then + -- end of move right + dx = 0 + dy = 1 + move = move_up + wy = wy - 1 + my = my + 1 + elseif y > wy and move == move_up then + -- end of move up + dx = -1 + dy = 0 + move = move_left + wx = wx - 1 + mx = mx + 1 + elseif x < mx and move == move_left then + -- end of move left + dx = 0 + dy = -1 + move = move_down + wy = wy - 1 + my = my + 1 + elseif y < my and move == move_down then + -- end of move down + dx = 1 + dy = 0 + move = move_right + wx = wx - 1 + mx = mx + 1 + end +end + +io.write(move_down:rep(15)) +print("\nDone!") |