edit/tty: Add Reader implementation for Windows.

This commit is contained in:
Qi Xiao 2017-12-14 00:36:12 +00:00
parent 0f7f29a51d
commit 74a7f50855
2 changed files with 220 additions and 0 deletions

View File

@ -23,6 +23,8 @@ type Reader interface {
}
// NewReader creates a new Reader on the given terminal file.
// TODO: NewReader should return an error as well. Right now failure to
// initialize Reader panics.
func NewReader(f *os.File) Reader {
return newReader(f)
}

218
edit/tty/reader_windows.go Normal file
View File

@ -0,0 +1,218 @@
package tty
import (
"errors"
"fmt"
"log"
"os"
"github.com/elves/elvish/edit/ui"
"github.com/elves/elvish/sys"
"golang.org/x/sys/windows"
)
type reader struct {
console windows.Handle
stopEvent windows.Handle
stopChan chan struct{}
eventChan chan Event
}
// NewReader 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))
}
cancelEvent, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
panic(fmt.Errorf("CreateEvent: %v", err))
}
return &reader{
console, cancelEvent, make(chan struct{}), make(chan Event)}
}
func (r *reader) SetRaw(bool) {
// NOP on Windows.
}
func (r *reader) EventChan() <-chan Event {
return r.eventChan
}
func (r *reader) Start() {
go r.run()
}
var errNr0 = errors.New("ReadConsoleInput reads 0 input")
func (r *reader) run() {
handles := []windows.Handle{r.console, r.stopEvent}
for {
triggered, _, err := sys.WaitForMultipleObjects(handles, false, sys.INFINITE)
if err != nil {
r.fatal(err)
return
}
if triggered == 1 {
<-r.stopChan
return
}
var buf [1]sys.InputRecord
nr, err := sys.ReadConsoleInput(r.console, buf[:])
if nr == 0 {
r.fatal(errNr0)
return
}
if err != nil {
r.fatal(err)
return
}
event := convertEvent(buf[0].GetEvent())
if event != nil {
r.send(event)
}
}
}
func (r *reader) nonFatal(err error) {
r.send(NonfatalErrorEvent{err})
}
func (r *reader) fatal(err error) {
if !r.send(FatalErrorEvent{err}) {
<-r.stopChan
r.resetStopEvent()
}
}
func (r *reader) send(event Event) (stopped bool) {
select {
case r.eventChan <- event:
return false
case <-r.stopChan:
r.resetStopEvent()
return true
}
}
func (r *reader) resetStopEvent() {
err := windows.ResetEvent(r.stopEvent)
if err != nil {
panic(err)
}
}
func (r *reader) Stop() {
err := windows.SetEvent(r.stopEvent)
if err != nil {
log.Println("SetEvent:", err)
}
close(r.stopChan)
}
func (r *reader) Close() {
err := windows.CloseHandle(r.stopEvent)
if err != nil {
log.Println("Closing stopEvent handle for reader:", err)
}
close(r.stopChan)
close(r.eventChan)
}
// 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
)
// convertEvent converts the native sys.InputEvent type to a suitable Event
// type. It returns nil if the event should be ignored.
func convertEvent(event sys.InputEvent) Event {
switch event := event.(type) {
case *sys.KeyEvent:
if event.BKeyDown == 0 {
// Ignore keyup events.
return nil
}
r := rune(event.UChar[0]) + rune(event.UChar[1])<<8
if event.DwControlKeyState == 0 {
// No modifier
// TODO: Deal with surrogate pairs
return KeyEvent(ui.Key{Rune: r})
} else if event.DwControlKeyState == shift {
// If only the shift is held down, we try and see if this is a
// non-functional key by looking if the rune generated is a
// printable ASCII character.
if 0x20 <= r && r < 0x7f {
return KeyEvent(ui.Key{Rune: r})
}
}
r = convertRune(event.WVirtualKeyCode)
if r == 0 {
return nil
}
return KeyEvent(ui.Key{
Rune: r,
Mod: convertMod(event.DwControlKeyState),
})
//case *sys.MouseEvent:
//case *sys.WindowBufferSizeEvent:
default:
// Other events are ignored.
return nil
}
}
func convertRune(keyCode uint16) rune {
r, ok := keyCodeToRune[keyCode]
if ok {
return r
}
if '0' <= r && r <= '9' {
return r
}
if 'A' <= r && r <= 'Z' {
return r - '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
}