Detect arrow keys on windows (#3363)

* detect arrow keys on windows
* add some helpful comments
This commit is contained in:
Jeffrey Morgan 2024-03-26 18:21:56 -04:00 committed by GitHub
parent f5ca7f8c8e
commit 913306f4fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,29 +1,7 @@
package readline package readline
import ( import (
"syscall" "golang.org/x/sys/windows"
"unsafe"
)
const (
enableLineInput = 2
enableWindowInput = 8
enableMouseInput = 16
enableInsertMode = 32
enableQuickEditMode = 64
enableExtendedFlags = 128
enableProcessedOutput = 1
enableWrapAtEolOutput = 2
enableAutoPosition = 256 // Cursor position is not affected by writing data to the console.
enableEchoInput = 4 // Characters are written to the console as they're read.
enableProcessedInput = 1 // Enables input processing (like recognizing Ctrl+C).
)
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
) )
type State struct { type State struct {
@ -33,31 +11,28 @@ type State struct {
// IsTerminal checks if the given file descriptor is associated with a terminal // IsTerminal checks if the given file descriptor is associated with a terminal
func IsTerminal(fd int) bool { func IsTerminal(fd int) bool {
var st uint32 var st uint32
r, _, e := syscall.SyscallN(procGetConsoleMode.Addr(), uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) err := windows.GetConsoleMode(windows.Handle(fd), &st)
// if the call succeeds and doesn't produce an error, it's a terminal return err == nil
return r != 0 && e == 0
} }
func SetRawMode(fd int) (*State, error) { func SetRawMode(fd int) (*State, error) {
var st uint32 var st uint32
// retrieve the current mode of the terminal if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
_, _, e := syscall.SyscallN(procGetConsoleMode.Addr(), uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) return nil, err
if e != 0 {
return nil, error(e)
} }
// modify the mode to set it to raw
raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) // this enables raw mode by turning off various flags in the console mode: https://pkg.go.dev/golang.org/x/sys/windows#pkg-constants
// apply the new mode to the terminal raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
_, _, e = syscall.SyscallN(procSetConsoleMode.Addr(), uintptr(fd), uintptr(raw), 0)
if e != 0 { // turn on ENABLE_VIRTUAL_TERMINAL_INPUT to enable escape sequences
return nil, error(e) raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
return nil, err
} }
// return the original state so that it can be restored later
return &State{st}, nil return &State{st}, nil
} }
func UnsetRawMode(fd int, state any) error { func UnsetRawMode(fd int, state any) error {
s := state.(*State) s := state.(*State)
_, _, err := syscall.SyscallN(procSetConsoleMode.Addr(), uintptr(fd), uintptr(s.mode), 0) return windows.SetConsoleMode(windows.Handle(fd), s.mode)
return err
} }