Files
cclip/vendor/github.com/charmbracelet/x/input/driver_windows.go
PhatPhuckDave 3c5e991ec5 Add vendor
Specifically because I want to modify a dependency
2024-08-26 01:01:04 +02:00

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)
}