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.tar.gz luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.tar.bz2 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 @@ | |||
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!") | ||