From 5b44b6721140885da6a026c8647decfbe22118ba Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Fri, 27 Dec 2024 16:36:39 +0800 Subject: [PATCH] term: add key_pressed, enable_echo (fix #21826) (#23171) --- examples/term_key_pressed.v | 29 ++++++++++++++++ vlib/term/term_nix.c.v | 66 +++++++++++++++++++++++++++++++++++++ vlib/term/term_windows.c.v | 54 ++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 examples/term_key_pressed.v diff --git a/examples/term_key_pressed.v b/examples/term_key_pressed.v new file mode 100644 index 00000000000000..ef89f00e61c176 --- /dev/null +++ b/examples/term_key_pressed.v @@ -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') +} diff --git a/vlib/term/term_nix.c.v b/vlib/term/term_nix.c.v index d2fa5158d02304..5b8292fd86a0bc 100644 --- a/vlib/term/term_nix.c.v +++ b/vlib/term/term_nix.c.v @@ -2,6 +2,7 @@ module term import os import term.termios +import time #include @@ -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 +} diff --git a/vlib/term/term_windows.c.v b/vlib/term/term_windows.c.v index 04002d00ec5254..def43beac04b4b 100644 --- a/vlib/term/term_windows.c.v +++ b/vlib/term/term_windows.c.v @@ -1,6 +1,9 @@ module term import os +import time + +#include @[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 +}