Skip to content

Commit

Permalink
term: add key_pressed, enable_echo (fix #21826) (#23171)
Browse files Browse the repository at this point in the history
  • Loading branch information
kbkpbot authored Dec 27, 2024
1 parent 4a1c7ad commit 5b44b67
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 0 deletions.
29 changes: 29 additions & 0 deletions examples/term_key_pressed.v
Original file line number Diff line number Diff line change
@@ -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')
}
66 changes: 66 additions & 0 deletions vlib/term/term_nix.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module term

import os
import term.termios
import time

#include <sys/ioctl.h>

Expand Down Expand Up @@ -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
}
54 changes: 54 additions & 0 deletions vlib/term/term_windows.c.v
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module term

import os
import time

#include <conio.h>

@[typedef]
pub struct C.COORD {
Expand Down Expand Up @@ -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
}

0 comments on commit 5b44b67

Please sign in to comment.