v / vlib / term / term_nix.c.v
283 lines · 246 sloc · 6.34 KB · 8e35f4d9848f7ad35d857a187dddbfd2eca5e19d
Raw
1module term
2
3import os
4import term.termios
5import time
6
7#include <sys/ioctl.h>
8
9pub struct C.winsize {
10pub:
11 ws_row u16
12 ws_col u16
13 ws_xpixel u16
14 ws_ypixel u16
15}
16
17fn C.ioctl(fd i32, request u64, args ...voidptr) i32
18
19// get_terminal_size returns a number of columns and rows of terminal window.
20pub fn get_terminal_size() (int, int) {
21 if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
22 return default_columns_size, default_rows_size
23 }
24 w := C.winsize{}
25 unsafe { C.ioctl(1, u64(C.TIOCGWINSZ), &w) }
26 return int(w.ws_col), int(w.ws_row)
27}
28
29// get_cursor_position returns a Coord containing the current cursor position.
30pub fn get_cursor_position() !Coord {
31 if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
32 return Coord{0, 0}
33 }
34
35 mut old_state := termios.Termios{}
36 if termios.tcgetattr(0, mut old_state) != 0 {
37 return os.last_error()
38 }
39 defer {
40 // restore the old terminal state:
41 termios.tcsetattr(0, C.TCSANOW, mut old_state)
42 }
43
44 mut state := termios.Termios{}
45 if termios.tcgetattr(0, mut state) != 0 {
46 return os.last_error()
47 }
48
49 state.c_lflag &= termios.invert(termios.flag(int(C.ICANON) | int(C.ECHO)))
50 termios.tcsetattr(0, C.TCSANOW, mut state)
51
52 print('\e[6n')
53 flush_stdout()
54
55 mut x := 0
56 mut y := 0
57 mut stage := u8(0)
58
59 // ESC [ YYY `;` XXX `R`
60
61 for {
62 w := unsafe { C.getchar() }
63 if w < 0 {
64 return error_with_code('Failed to read from stdin', 888)
65 } else if w == `[` || w == `;` {
66 stage++
67 } else if `0` <= w && w <= `9` {
68 match stage {
69 // converting string values to int:
70 1 { y = y * 10 + int(w - `0`) }
71 2 { x = x * 10 + int(w - `0`) }
72 else {}
73 }
74 } else if w == `R` {
75 break
76 }
77 }
78 return Coord{x, y}
79}
80
81// set_terminal_title change the terminal title (usually that is the containing terminal window title).
82pub fn set_terminal_title(title string) bool {
83 if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
84 return false
85 }
86 print('\033]0;')
87 print(title)
88 print('\007')
89 flush_stdout()
90 return true
91}
92
93// set_tab_title changes the terminal *tab title*, for terminal emulators like Konsole, that support several tabs.
94pub fn set_tab_title(title string) bool {
95 if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
96 return false
97 }
98 print('\033]30;')
99 print(title)
100 print('\007')
101 flush_stdout()
102 return true
103}
104
105// clear clears current terminal screen.
106pub fn clear() bool {
107 if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
108 return false
109 }
110 print('\x1b[2J')
111 print('\x1b[H')
112 flush_stdout()
113 return true
114}
115
116// supports_sixel returns `true` if the terminal supports Sixel graphics
117//
118// For more info on the sixel format:
119// See https://en.wikipedia.org/wiki/Sixel
120// See https://www.digiater.nl/openvms/decus/vax90b1/krypton-nasa/all-about-sixels.text
121// For more info on terminal support:
122// See https://www.arewesixelyet.com
123pub fn supports_sixel() bool {
124 if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
125 return false
126 }
127 if os.getenv('TERM') == 'yaft' {
128 return true
129 }
130
131 mut old_state := termios.Termios{}
132 if termios.tcgetattr(0, mut old_state) != 0 {
133 return false
134 }
135 defer {
136 // restore the old terminal state:
137 termios.tcsetattr(0, C.TCSANOW, mut old_state)
138 }
139
140 mut state := termios.Termios{}
141 if termios.tcgetattr(0, mut state) != 0 {
142 return false
143 }
144
145 state.c_lflag &= termios.invert(termios.flag(int(C.ICANON) | int(C.ECHO)))
146 termios.tcsetattr(0, C.TCSANOW, mut state)
147
148 // Send a "Query Device Code"
149 print('\e[c')
150 flush_stdout()
151
152 // Terminal answers with a "Report Device Code" in the format `\e[<code>`
153 mut buf := []u8{cap: 64} // xterm's output can be long
154 for {
155 w := unsafe { C.getchar() }
156 if w < 0 {
157 return false
158 } else if w == `c` {
159 break
160 } else {
161 buf << u8(w)
162 }
163 }
164 sa := buf.bytestr().all_after('?').split(';')
165 // returns `true` if the digit literal "4" is found in a "slot" in the response, before the ending `c`.
166 return '4' in sa
167}
168
169// graphics_num_colors returns the number of color registers the terminal
170// graphic attribute is set to use. This can be useful to know if the terminal
171// is configured to support Sixel graphics.
172//
173// See "CSI ? Pi ; Pa ; Pv S" from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
174pub fn graphics_num_colors() u16 {
175 if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
176 return 0
177 }
178
179 if os.getenv('TERM') == 'yaft' {
180 return 256
181 }
182
183 mut old_state := termios.Termios{}
184 if termios.tcgetattr(0, mut old_state) != 0 {
185 return 0
186 }
187 defer {
188 // restore the old terminal state:
189 termios.tcsetattr(0, C.TCSANOW, mut old_state)
190 }
191
192 mut state := termios.Termios{}
193 if termios.tcgetattr(0, mut state) != 0 {
194 return 0
195 }
196
197 state.c_lflag &= termios.invert(termios.flag(int(C.ICANON) | int(C.ECHO)))
198 termios.tcsetattr(0, C.TCSANOW, mut state)
199
200 // Send "CSI ? Pi ; Pa ; Pv S"
201 print('\e[?1;1;0S')
202 flush_stdout()
203
204 mut buf := []u8{cap: 20}
205 for {
206 w := unsafe { C.getchar() }
207 if w < 0 {
208 return 0
209 } else if w == `S` {
210 break
211 } else if w == `;` {
212 buf.clear()
213 } else {
214 buf << u8(w)
215 }
216 }
217 return buf.bytestr().u16()
218}
219
220// enable_echo enable/disable echo input characters.
221pub fn enable_echo(enable bool) {
222 mut state := termios.Termios{}
223 termios.tcgetattr(0, mut state)
224 if enable {
225 state.c_lflag |= C.ECHO
226 } else {
227 state.c_lflag &= ~C.ECHO
228 }
229 termios.tcsetattr(0, C.TCSANOW, mut state)
230}
231
232// KeyPressedParams contains the optional parameters that you can pass to key_pressed.
233@[params]
234pub struct KeyPressedParams {
235pub mut:
236 blocking bool // whether to wait for a pressed key
237 echo bool // whether to output the pressed key to stdout
238}
239
240// key_pressed gives back a single character, read from the standard input.
241// It returns -1 on error or no character in non-blocking mode
242pub fn key_pressed(params KeyPressedParams) i64 {
243 mut state := termios.Termios{}
244 if termios.tcgetattr(0, mut state) != 0 {
245 return -1
246 }
247 mut old_state := state
248 defer {
249 // restore the old terminal state:
250 termios.tcsetattr(0, C.TCSANOW, mut old_state)
251 }
252
253 // disable line by line input
254 state.c_lflag &= ~C.ICANON
255
256 if params.echo {
257 state.c_lflag |= C.ECHO
258 } else {
259 state.c_lflag &= ~C.ECHO
260 }
261 termios.tcsetattr(0, C.TCSANOW, mut state)
262
263 mut ret := i64(0)
264
265 for {
266 pending := os.fd_is_pending(0)
267 if pending {
268 r := C.read(0, &ret, 8)
269 if r < 0 {
270 return r
271 } else {
272 return ret
273 }
274 }
275 if !params.blocking {
276 // in non-blocking mode, we need to return immediately
277 return -1
278 }
279 time.sleep(1 * time.millisecond)
280 }
281
282 return ret
283}
284