elvish/pkg/cli/term/reader_windows.go
Qi Xiao de3a7fd1b3 Standardize spelling: UNIX -> Unix.
https://en.wikipedia.org/wiki/Unix#Branding contains a history of the spelling.
Follow Wikipedia in using Unix instead of UNIX.
2023-01-04 18:09:11 +00:00

223 lines
6.1 KiB
Go

package term
import (
"fmt"
"io"
"log"
"os"
"sync"
"unicode/utf16"
"golang.org/x/sys/windows"
"src.elv.sh/pkg/sys/ewindows"
"src.elv.sh/pkg/ui"
)
type reader struct {
console windows.Handle
stopEvent windows.Handle
// A mutex that is held during ReadEvent.
mutex sync.Mutex
}
// Creates a new Reader instance.
func newReader(file *os.File) Reader {
console, err := windows.GetStdHandle(windows.STD_INPUT_HANDLE)
if err != nil {
panic(fmt.Errorf("GetStdHandle(STD_INPUT_HANDLE): %v", err))
}
stopEvent, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
panic(fmt.Errorf("CreateEvent: %v", err))
}
return &reader{console: console, stopEvent: stopEvent}
}
func (r *reader) ReadEvent() (Event, error) {
r.mutex.Lock()
defer r.mutex.Unlock()
handles := []windows.Handle{r.console, r.stopEvent}
var leadingSurrogate *surrogateKeyEvent
for {
triggered, _, err := ewindows.WaitForMultipleObjects(handles, false, ewindows.INFINITE)
if err != nil {
return nil, err
}
if triggered == 1 {
return nil, ErrStopped
}
var buf [1]ewindows.InputRecord
nr, err := ewindows.ReadConsoleInput(r.console, buf[:])
if nr == 0 {
return nil, io.ErrNoProgress
}
if err != nil {
return nil, err
}
event := convertEvent(buf[0].GetEvent())
if surrogate, ok := event.(surrogateKeyEvent); ok {
if leadingSurrogate == nil {
leadingSurrogate = &surrogate
// Keep reading the trailing surrogate.
continue
} else {
r := utf16.DecodeRune(leadingSurrogate.r, surrogate.r)
return KeyEvent{Rune: r}, nil
}
}
if event != nil {
return event, nil
}
// Got an event that should be ignored; keep going.
}
}
func (r *reader) ReadRawEvent() (Event, error) {
return r.ReadEvent()
}
func (r *reader) Close() {
err := windows.SetEvent(r.stopEvent)
if err != nil {
log.Println("SetEvent:", err)
}
r.mutex.Lock()
//lint:ignore SA2001 We only lock the mutex to make sure that ReadEvent has
//exited, so we unlock it immediately.
r.mutex.Unlock()
err = windows.CloseHandle(r.stopEvent)
if err != nil {
log.Println("Closing stopEvent handle for reader:", err)
}
}
// A subset of virtual key codes listed in
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
var keyCodeToRune = map[uint16]rune{
0x08: ui.Backspace, 0x09: ui.Tab,
0x0d: ui.Enter,
0x1b: '\x1b',
0x20: ' ',
0x23: ui.End, 0x24: ui.Home,
0x25: ui.Left, 0x26: ui.Up, 0x27: ui.Right, 0x28: ui.Down,
0x2d: ui.Insert, 0x2e: ui.Delete,
/* 0x30 - 0x39: digits, same with ASCII */
/* 0x41 - 0x5a: letters, same with ASCII */
/* 0x60 - 0x6f: numpads; currently ignored */
0x70: ui.F1, 0x71: ui.F2, 0x72: ui.F3, 0x73: ui.F4, 0x74: ui.F5, 0x75: ui.F6,
0x76: ui.F7, 0x77: ui.F8, 0x78: ui.F9, 0x79: ui.F10, 0x7a: ui.F11, 0x7b: ui.F12,
/* 0x7c - 0x87: F13 - F24; currently ignored */
0xba: ';', 0xbb: '=', 0xbc: ',', 0xbd: '-', 0xbe: '.', 0xbf: '/', 0xc0: '`',
0xdb: '[', 0xdc: '\\', 0xdd: ']', 0xde: '\'',
}
// A subset of constants listed in
// https://docs.microsoft.com/en-us/windows/console/key-event-record-str
const (
leftAlt = 0x02
leftCtrl = 0x08
rightAlt = 0x01
rightCtrl = 0x04
shift = 0x10
)
type surrogateKeyEvent struct{ r rune }
func (surrogateKeyEvent) isEvent() {}
// Converts the native ewindows.InputEvent type to a suitable Event type. It returns
// nil if the event should be ignored.
func convertEvent(event ewindows.InputEvent) Event {
switch event := event.(type) {
case *ewindows.KeyEvent:
if event.BKeyDown == 0 {
// Ignore keyup events.
return nil
}
r := rune(event.UChar[0]) + rune(event.UChar[1])<<8
filteredMod := event.DwControlKeyState & (leftAlt | leftCtrl | rightAlt | rightCtrl | shift)
if r >= 0x20 && r != 0x7f {
// This key inputs a character. The flags present in
// DwControlKeyState might indicate modifier keys that are needed to
// input this character (e.g. the Shift key when inputting 'A'), or
// modifier keys that are pressed in addition (e.g. the Alt key when
// pressing Alt-A). There doesn't seem to be an easy way to tell
// which is the case, so we rely on heuristics derived from
// real-world observations.
if filteredMod == 0 {
if utf16.IsSurrogate(r) {
return surrogateKeyEvent{r}
} else {
return KeyEvent(ui.Key{Rune: r})
}
} else if filteredMod == shift {
// A lone Shift seems to be always part of the character.
return KeyEvent(ui.Key{Rune: r})
} else if filteredMod == leftCtrl|rightAlt || filteredMod == leftCtrl|rightAlt|shift {
// The combination leftCtrl|rightAlt is used to represent AltGr.
// Furthermore, when the actual left Ctrl and right Alt are used
// together, the UChar field seems to be always 0; so if we are
// here, we can actually be sure that it's AltGr.
//
// Some characters require AltGr+Shift to input, such as the
// upper-case sharp S on a German keyboard.
return KeyEvent(ui.Key{Rune: r})
}
}
mod := convertMod(filteredMod)
if mod == 0 && event.WVirtualKeyCode == 0x1b {
// Special case: Normalize 0x1b to Ctrl-[.
//
// TODO(xiaq): This is Unix-centric. Maybe the normalized form
// should be Escape.
return KeyEvent(ui.Key{Rune: '[', Mod: ui.Ctrl})
}
r = convertRune(event.WVirtualKeyCode, mod)
if r == 0 {
return nil
}
return KeyEvent(ui.Key{Rune: r, Mod: mod})
default:
// Other events are ignored.
return nil
}
}
func convertRune(keyCode uint16, mod ui.Mod) rune {
r, ok := keyCodeToRune[keyCode]
if ok {
return r
}
if '0' <= keyCode && keyCode <= '9' {
return rune(keyCode)
}
if 'A' <= keyCode && keyCode <= 'Z' {
// If Ctrl is involved, emulate Unix's convention and use upper case;
// otherwise use lower case.
//
// TODO(xiaq): This is quite Unix-centric. Maybe we should make the
// base rune case-insensitive when there are modifiers involved.
if mod&ui.Ctrl != 0 {
return rune(keyCode)
}
return rune(keyCode - 'A' + 'a')
}
return 0
}
func convertMod(state uint32) ui.Mod {
mod := ui.Mod(0)
if state&(leftAlt|rightAlt) != 0 {
mod |= ui.Alt
}
if state&(leftCtrl|rightCtrl) != 0 {
mod |= ui.Ctrl
}
if state&shift != 0 {
mod |= ui.Shift
}
return mod
}