term: add key_pressed, enable_echo (fix #21826) (#23171)

This commit is contained in:
kbkpbot 2024-12-27 16:36:39 +08:00 committed by GitHub
parent 4a1c7add24
commit 5b44b67211
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 149 additions and 0 deletions

View File

@ -0,0 +1,29 @@
module main
import term
import time
fn main() {
unbuffer_stdout()
println('Press Ctrl-D or ESC to exit.')
term.enable_echo(false)
mut frame := 0
for {
print('\r${time.now()} | frame: ${frame:06} | ')
x := term.key_pressed()
if x in [i64(0), 4, 27] {
// pressing Ctrl-D or ESC exits the loop
// Ctrl-D mean EOF sometimes, so return 0
break
}
if x > 0 {
println('${x:08x}')
}
time.sleep(16 * time.millisecond)
frame++
}
term.enable_echo(true)
println('done')
}

View File

@ -2,6 +2,7 @@ module term
import os
import term.termios
import time
#include <sys/ioctl.h>
@ -215,3 +216,68 @@ pub fn graphics_num_colors() u16 {
}
return buf.bytestr().u16()
}
// enable_echo enable/disable echo input characters
pub fn enable_echo(enable bool) {
mut state := termios.Termios{}
termios.tcgetattr(0, mut state)
if enable {
state.c_lflag |= C.ECHO
} else {
state.c_lflag &= ~C.ECHO
}
termios.tcsetattr(0, C.TCSANOW, mut state)
}
// KeyPressedParams contains the optional parameters that you can pass to key_pressed.
@[params]
pub struct KeyPressedParams {
pub mut:
blocking bool // whether to wait for a pressed key
echo bool // whether to output the pressed key to stdout
}
// key_pressed gives back a single character, read from the standard input.
// It returns -1 on error or no character in non-blocking mode
pub fn key_pressed(params KeyPressedParams) i64 {
mut state := termios.Termios{}
if termios.tcgetattr(0, mut state) != 0 {
return -1
}
mut old_state := state
defer {
// restore the old terminal state:
termios.tcsetattr(0, C.TCSANOW, mut old_state)
}
// disable line by line input
state.c_lflag &= ~C.ICANON
if params.echo {
state.c_lflag |= C.ECHO
} else {
state.c_lflag &= ~C.ECHO
}
termios.tcsetattr(0, C.TCSANOW, mut state)
mut ret := i64(0)
for {
pending := os.fd_is_pending(0)
if pending {
r := C.read(0, &ret, 8)
if r < 0 {
return r
} else {
return ret
}
}
if !params.blocking {
// in non-blocking mode, we need to return immediately
return -1
}
time.sleep(1 * time.millisecond)
}
return ret
}

View File

@ -1,6 +1,9 @@
module term
import os
import time
#include <conio.h>
@[typedef]
pub struct C.COORD {
@ -156,3 +159,54 @@ pub fn graphics_num_colors() u16 {
// does not have support for querying the graphics setup this call returns 0
return 0
}
// enable_echo enable/disable echo input characters
pub fn enable_echo(enable bool) {
// no need under windows, use key_pressed func's echo
}
fn C.kbhit() bool
fn C._getch() int
fn C._getche() int
// KeyPressedParams contains the optional parameters that you can pass to key_pressed.
@[params]
pub struct KeyPressedParams {
pub mut:
blocking bool // whether to wait for a pressed key
echo bool // whether to output the pressed key to stdout
}
// key_pressed gives back a single character, read from the standard input.
// It returns -1 on error or no character in non-blocking mode
pub fn key_pressed(params KeyPressedParams) i64 {
for {
if C.kbhit() {
res := if params.echo {
C._getche()
} else {
C._getch()
}
// see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getche-getwche?view=msvc-170
// > When _getche or _getwche reads a function key or an arrow key, the function must be called twice;
// > the first call returns 0 or 0xE0, and the second call returns the actual key code.
if res in [0, 0xe0] {
if C.kbhit() {
res2 := if params.echo {
C._getche()
} else {
C._getch()
}
return i64(u32(0xe0) << 16 | u32(res2))
}
}
return i64(res)
}
if !params.blocking {
// in non-blocking mode, we need to return immediately
return -1
}
time.sleep(1 * time.millisecond)
}
return 0
}