277 lines
6.5 KiB
Go
277 lines
6.5 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package input
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"unicode/utf16"
|
|
|
|
"github.com/charmbracelet/x/ansi"
|
|
termwindows "github.com/charmbracelet/x/windows"
|
|
"github.com/erikgeiser/coninput"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// ReadEvents reads input events from the terminal.
|
|
//
|
|
// It reads the events available in the input buffer and returns them.
|
|
func (d *Driver) ReadEvents() ([]Event, error) {
|
|
events, err := d.handleConInput(coninput.ReadConsoleInput)
|
|
if errors.Is(err, errNotConInputReader) {
|
|
return d.readEvents()
|
|
}
|
|
return events, err
|
|
}
|
|
|
|
var errNotConInputReader = fmt.Errorf("handleConInput: not a conInputReader")
|
|
|
|
func (d *Driver) handleConInput(
|
|
finput func(windows.Handle, []coninput.InputRecord) (uint32, error),
|
|
) ([]Event, error) {
|
|
cc, ok := d.rd.(*conInputReader)
|
|
if !ok {
|
|
return nil, errNotConInputReader
|
|
}
|
|
|
|
// read up to 256 events, this is to allow for sequences events reported as
|
|
// key events.
|
|
var events [256]coninput.InputRecord
|
|
_, err := finput(cc.conin, events[:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read coninput events: %w", err)
|
|
}
|
|
|
|
var evs []Event
|
|
for _, event := range events {
|
|
if e := parseConInputEvent(event, &d.prevMouseState); e != nil {
|
|
evs = append(evs, e)
|
|
}
|
|
}
|
|
|
|
return d.detectConInputQuerySequences(evs), nil
|
|
}
|
|
|
|
// Using ConInput API, Windows Terminal responds to sequence query events with
|
|
// KEY_EVENT_RECORDs so we need to collect them and parse them as a single
|
|
// sequence.
|
|
// Is this a hack?
|
|
func (d *Driver) detectConInputQuerySequences(events []Event) []Event {
|
|
var newEvents []Event
|
|
start, end := -1, -1
|
|
|
|
loop:
|
|
for i, e := range events {
|
|
switch e := e.(type) {
|
|
case KeyDownEvent:
|
|
switch e.Rune {
|
|
case ansi.ESC, ansi.CSI, ansi.OSC, ansi.DCS, ansi.APC:
|
|
// start of a sequence
|
|
if start == -1 {
|
|
start = i
|
|
}
|
|
}
|
|
default:
|
|
break loop
|
|
}
|
|
end = i
|
|
}
|
|
|
|
if start == -1 || end <= start {
|
|
return events
|
|
}
|
|
|
|
var seq []byte
|
|
for i := start; i <= end; i++ {
|
|
switch e := events[i].(type) {
|
|
case KeyDownEvent:
|
|
seq = append(seq, byte(e.Rune))
|
|
}
|
|
}
|
|
|
|
n, seqevent := ParseSequence(seq)
|
|
switch seqevent.(type) {
|
|
case UnknownEvent:
|
|
// We're not interested in unknown events
|
|
default:
|
|
if start+n > len(events) {
|
|
return events
|
|
}
|
|
newEvents = events[:start]
|
|
newEvents = append(newEvents, seqevent)
|
|
newEvents = append(newEvents, events[start+n:]...)
|
|
return d.detectConInputQuerySequences(newEvents)
|
|
}
|
|
|
|
return events
|
|
}
|
|
|
|
func parseConInputEvent(event coninput.InputRecord, ps *coninput.ButtonState) Event {
|
|
switch e := event.Unwrap().(type) {
|
|
case coninput.KeyEventRecord:
|
|
event := parseWin32InputKeyEvent(e.VirtualKeyCode, e.VirtualScanCode,
|
|
e.Char, e.KeyDown, e.ControlKeyState, e.RepeatCount)
|
|
|
|
var key Key
|
|
switch event := event.(type) {
|
|
case KeyDownEvent:
|
|
key = Key(event)
|
|
case KeyUpEvent:
|
|
key = Key(event)
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
// If the key is not printable, return the event as is
|
|
// (e.g. function keys, arrows, etc.)
|
|
// Otherwise, try to translate it to a rune based on the active keyboard
|
|
// layout.
|
|
if key.Rune == 0 {
|
|
return event
|
|
}
|
|
|
|
// Get active keyboard layout
|
|
fgWin := windows.GetForegroundWindow()
|
|
fgThread, err := windows.GetWindowThreadProcessId(fgWin, nil)
|
|
if err != nil {
|
|
return event
|
|
}
|
|
|
|
layout, err := termwindows.GetKeyboardLayout(fgThread)
|
|
if layout == windows.InvalidHandle || err != nil {
|
|
return event
|
|
}
|
|
|
|
// Translate key to rune
|
|
var keyState [256]byte
|
|
var utf16Buf [16]uint16
|
|
const dontChangeKernelKeyboardLayout = 0x4
|
|
ret, err := termwindows.ToUnicodeEx(
|
|
uint32(e.VirtualKeyCode),
|
|
uint32(e.VirtualScanCode),
|
|
&keyState[0],
|
|
&utf16Buf[0],
|
|
int32(len(utf16Buf)),
|
|
dontChangeKernelKeyboardLayout,
|
|
layout,
|
|
)
|
|
|
|
// -1 indicates a dead key
|
|
// 0 indicates no translation for this key
|
|
if ret < 1 || err != nil {
|
|
return event
|
|
}
|
|
|
|
runes := utf16.Decode(utf16Buf[:ret])
|
|
if len(runes) != 1 {
|
|
// Key doesn't translate to a single rune
|
|
return event
|
|
}
|
|
|
|
key.BaseRune = runes[0]
|
|
if e.KeyDown {
|
|
return KeyDownEvent(key)
|
|
}
|
|
|
|
return KeyUpEvent(key)
|
|
|
|
case coninput.WindowBufferSizeEventRecord:
|
|
return WindowSizeEvent{
|
|
Width: int(e.Size.X),
|
|
Height: int(e.Size.Y),
|
|
}
|
|
case coninput.MouseEventRecord:
|
|
mevent := mouseEvent(*ps, e)
|
|
*ps = e.ButtonState
|
|
return mevent
|
|
case coninput.FocusEventRecord, coninput.MenuEventRecord:
|
|
// ignore
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, isRelease bool) {
|
|
btn := p ^ s
|
|
if btn&s == 0 {
|
|
isRelease = true
|
|
}
|
|
|
|
if btn == 0 {
|
|
switch {
|
|
case s&coninput.FROM_LEFT_1ST_BUTTON_PRESSED > 0:
|
|
button = MouseLeft
|
|
case s&coninput.FROM_LEFT_2ND_BUTTON_PRESSED > 0:
|
|
button = MouseMiddle
|
|
case s&coninput.RIGHTMOST_BUTTON_PRESSED > 0:
|
|
button = MouseRight
|
|
case s&coninput.FROM_LEFT_3RD_BUTTON_PRESSED > 0:
|
|
button = MouseBackward
|
|
case s&coninput.FROM_LEFT_4TH_BUTTON_PRESSED > 0:
|
|
button = MouseForward
|
|
}
|
|
return
|
|
}
|
|
|
|
switch {
|
|
case btn == coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
|
|
button = MouseLeft
|
|
case btn == coninput.RIGHTMOST_BUTTON_PRESSED: // right button
|
|
button = MouseRight
|
|
case btn == coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
|
|
button = MouseMiddle
|
|
case btn == coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
|
|
button = MouseBackward
|
|
case btn == coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
|
|
button = MouseForward
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func mouseEvent(p coninput.ButtonState, e coninput.MouseEventRecord) (ev Event) {
|
|
var mod KeyMod
|
|
var isRelease bool
|
|
if e.ControlKeyState.Contains(coninput.LEFT_ALT_PRESSED | coninput.RIGHT_ALT_PRESSED) {
|
|
mod |= Alt
|
|
}
|
|
if e.ControlKeyState.Contains(coninput.LEFT_CTRL_PRESSED | coninput.RIGHT_CTRL_PRESSED) {
|
|
mod |= Ctrl
|
|
}
|
|
if e.ControlKeyState.Contains(coninput.SHIFT_PRESSED) {
|
|
mod |= Shift
|
|
}
|
|
m := Mouse{
|
|
X: int(e.MousePositon.X),
|
|
Y: int(e.MousePositon.Y),
|
|
Mod: mod,
|
|
}
|
|
switch e.EventFlags {
|
|
case coninput.CLICK, coninput.DOUBLE_CLICK:
|
|
m.Button, isRelease = mouseEventButton(p, e.ButtonState)
|
|
case coninput.MOUSE_WHEELED:
|
|
if e.WheelDirection > 0 {
|
|
m.Button = MouseWheelUp
|
|
} else {
|
|
m.Button = MouseWheelDown
|
|
}
|
|
case coninput.MOUSE_HWHEELED:
|
|
if e.WheelDirection > 0 {
|
|
m.Button = MouseWheelRight
|
|
} else {
|
|
m.Button = MouseWheelLeft
|
|
}
|
|
case coninput.MOUSE_MOVED:
|
|
m.Button, _ = mouseEventButton(p, e.ButtonState)
|
|
return MouseMotionEvent(m)
|
|
}
|
|
|
|
if isWheel(m.Button) {
|
|
return MouseWheelEvent(m)
|
|
} else if isRelease {
|
|
return MouseUpEvent(m)
|
|
}
|
|
|
|
return MouseDownEvent(m)
|
|
}
|