mirror of
https://github.com/sst/opencode.git
synced 2025-08-03 13:22:19 +00:00
642 lines
17 KiB
Go
642 lines
17 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package input
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
"unicode/utf16"
|
|
"unicode/utf8"
|
|
|
|
"github.com/charmbracelet/x/ansi"
|
|
xwindows "github.com/charmbracelet/x/windows"
|
|
"github.com/muesli/cancelreader"
|
|
"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 *Reader) ReadEvents() ([]Event, error) {
|
|
events, err := d.handleConInput()
|
|
if errors.Is(err, errNotConInputReader) {
|
|
return d.readEvents()
|
|
}
|
|
return events, err
|
|
}
|
|
|
|
var errNotConInputReader = fmt.Errorf("handleConInput: not a conInputReader")
|
|
|
|
func (d *Reader) handleConInput() ([]Event, error) {
|
|
cc, ok := d.rd.(*conInputReader)
|
|
if !ok {
|
|
return nil, errNotConInputReader
|
|
}
|
|
|
|
var (
|
|
events []xwindows.InputRecord
|
|
err error
|
|
)
|
|
for {
|
|
// Peek up to 256 events, this is to allow for sequences events reported as
|
|
// key events.
|
|
events, err = peekNConsoleInputs(cc.conin, 256)
|
|
if cc.isCanceled() {
|
|
return nil, cancelreader.ErrCanceled
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("peek coninput events: %w", err)
|
|
}
|
|
if len(events) > 0 {
|
|
break
|
|
}
|
|
|
|
// Sleep for a bit to avoid busy waiting.
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
events, err = readNConsoleInputs(cc.conin, uint32(len(events)))
|
|
if cc.isCanceled() {
|
|
return nil, cancelreader.ErrCanceled
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read coninput events: %w", err)
|
|
}
|
|
|
|
var evs []Event
|
|
for _, event := range events {
|
|
if e := d.parser.parseConInputEvent(event, &d.keyState); e != nil {
|
|
if multi, ok := e.(MultiEvent); ok {
|
|
evs = append(evs, multi...)
|
|
} else {
|
|
evs = append(evs, e)
|
|
}
|
|
}
|
|
}
|
|
|
|
return evs, nil
|
|
}
|
|
|
|
func (p *Parser) parseConInputEvent(event xwindows.InputRecord, keyState *win32InputState) Event {
|
|
switch event.EventType {
|
|
case xwindows.KEY_EVENT:
|
|
kevent := event.KeyEvent()
|
|
return p.parseWin32InputKeyEvent(keyState, kevent.VirtualKeyCode, kevent.VirtualScanCode,
|
|
kevent.Char, kevent.KeyDown, kevent.ControlKeyState, kevent.RepeatCount)
|
|
|
|
case xwindows.WINDOW_BUFFER_SIZE_EVENT:
|
|
wevent := event.WindowBufferSizeEvent()
|
|
if wevent.Size.X != keyState.lastWinsizeX || wevent.Size.Y != keyState.lastWinsizeY {
|
|
keyState.lastWinsizeX, keyState.lastWinsizeY = wevent.Size.X, wevent.Size.Y
|
|
return WindowSizeEvent{
|
|
Width: int(wevent.Size.X),
|
|
Height: int(wevent.Size.Y),
|
|
}
|
|
}
|
|
case xwindows.MOUSE_EVENT:
|
|
mevent := event.MouseEvent()
|
|
Event := mouseEvent(keyState.lastMouseBtns, mevent)
|
|
keyState.lastMouseBtns = mevent.ButtonState
|
|
return Event
|
|
case xwindows.FOCUS_EVENT:
|
|
fevent := event.FocusEvent()
|
|
if fevent.SetFocus {
|
|
return FocusEvent{}
|
|
}
|
|
return BlurEvent{}
|
|
case xwindows.MENU_EVENT:
|
|
// ignore
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func mouseEventButton(p, s uint32) (MouseButton, bool) {
|
|
var isRelease bool
|
|
button := MouseNone
|
|
btn := p ^ s
|
|
if btn&s == 0 {
|
|
isRelease = true
|
|
}
|
|
|
|
if btn == 0 {
|
|
switch {
|
|
case s&xwindows.FROM_LEFT_1ST_BUTTON_PRESSED > 0:
|
|
button = MouseLeft
|
|
case s&xwindows.FROM_LEFT_2ND_BUTTON_PRESSED > 0:
|
|
button = MouseMiddle
|
|
case s&xwindows.RIGHTMOST_BUTTON_PRESSED > 0:
|
|
button = MouseRight
|
|
case s&xwindows.FROM_LEFT_3RD_BUTTON_PRESSED > 0:
|
|
button = MouseBackward
|
|
case s&xwindows.FROM_LEFT_4TH_BUTTON_PRESSED > 0:
|
|
button = MouseForward
|
|
}
|
|
return button, isRelease
|
|
}
|
|
|
|
switch btn {
|
|
case xwindows.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
|
|
button = MouseLeft
|
|
case xwindows.RIGHTMOST_BUTTON_PRESSED: // right button
|
|
button = MouseRight
|
|
case xwindows.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
|
|
button = MouseMiddle
|
|
case xwindows.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
|
|
button = MouseBackward
|
|
case xwindows.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
|
|
button = MouseForward
|
|
}
|
|
|
|
return button, isRelease
|
|
}
|
|
|
|
func mouseEvent(p uint32, e xwindows.MouseEventRecord) (ev Event) {
|
|
var mod KeyMod
|
|
var isRelease bool
|
|
if e.ControlKeyState&(xwindows.LEFT_ALT_PRESSED|xwindows.RIGHT_ALT_PRESSED) != 0 {
|
|
mod |= ModAlt
|
|
}
|
|
if e.ControlKeyState&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_CTRL_PRESSED) != 0 {
|
|
mod |= ModCtrl
|
|
}
|
|
if e.ControlKeyState&(xwindows.SHIFT_PRESSED) != 0 {
|
|
mod |= ModShift
|
|
}
|
|
|
|
m := Mouse{
|
|
X: int(e.MousePositon.X),
|
|
Y: int(e.MousePositon.Y),
|
|
Mod: mod,
|
|
}
|
|
|
|
wheelDirection := int16(highWord(e.ButtonState)) //nolint:gosec
|
|
switch e.EventFlags {
|
|
case 0, xwindows.DOUBLE_CLICK:
|
|
m.Button, isRelease = mouseEventButton(p, e.ButtonState)
|
|
case xwindows.MOUSE_WHEELED:
|
|
if wheelDirection > 0 {
|
|
m.Button = MouseWheelUp
|
|
} else {
|
|
m.Button = MouseWheelDown
|
|
}
|
|
case xwindows.MOUSE_HWHEELED:
|
|
if wheelDirection > 0 {
|
|
m.Button = MouseWheelRight
|
|
} else {
|
|
m.Button = MouseWheelLeft
|
|
}
|
|
case xwindows.MOUSE_MOVED:
|
|
m.Button, _ = mouseEventButton(p, e.ButtonState)
|
|
return MouseMotionEvent(m)
|
|
}
|
|
|
|
if isWheel(m.Button) {
|
|
return MouseWheelEvent(m)
|
|
} else if isRelease {
|
|
return MouseReleaseEvent(m)
|
|
}
|
|
|
|
return MouseClickEvent(m)
|
|
}
|
|
|
|
func highWord(data uint32) uint16 {
|
|
return uint16((data & 0xFFFF0000) >> 16) //nolint:gosec
|
|
}
|
|
|
|
func readNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) {
|
|
if maxEvents == 0 {
|
|
return nil, fmt.Errorf("maxEvents cannot be zero")
|
|
}
|
|
|
|
records := make([]xwindows.InputRecord, maxEvents)
|
|
n, err := readConsoleInput(console, records)
|
|
return records[:n], err
|
|
}
|
|
|
|
func readConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
|
|
if len(inputRecords) == 0 {
|
|
return 0, fmt.Errorf("size of input record buffer cannot be zero")
|
|
}
|
|
|
|
var read uint32
|
|
|
|
err := xwindows.ReadConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec
|
|
|
|
return read, err //nolint:wrapcheck
|
|
}
|
|
|
|
func peekConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
|
|
if len(inputRecords) == 0 {
|
|
return 0, fmt.Errorf("size of input record buffer cannot be zero")
|
|
}
|
|
|
|
var read uint32
|
|
|
|
err := xwindows.PeekConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec
|
|
|
|
return read, err //nolint:wrapcheck
|
|
}
|
|
|
|
func peekNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) {
|
|
if maxEvents == 0 {
|
|
return nil, fmt.Errorf("maxEvents cannot be zero")
|
|
}
|
|
|
|
records := make([]xwindows.InputRecord, maxEvents)
|
|
n, err := peekConsoleInput(console, records)
|
|
return records[:n], err
|
|
}
|
|
|
|
// parseWin32InputKeyEvent parses a single key event from either the Windows
|
|
// Console API or win32-input-mode events. When state is nil, it means this is
|
|
// an event from win32-input-mode. Otherwise, it's a key event from the Windows
|
|
// Console API and needs a state to decode ANSI escape sequences and utf16
|
|
// runes.
|
|
func (p *Parser) parseWin32InputKeyEvent(state *win32InputState, vkc uint16, _ uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) (event Event) {
|
|
defer func() {
|
|
// Respect the repeat count.
|
|
if repeatCount > 1 {
|
|
var multi MultiEvent
|
|
for i := 0; i < int(repeatCount); i++ {
|
|
multi = append(multi, event)
|
|
}
|
|
event = multi
|
|
}
|
|
}()
|
|
if state != nil {
|
|
defer func() {
|
|
state.lastCks = cks
|
|
}()
|
|
}
|
|
|
|
var utf8Buf [utf8.UTFMax]byte
|
|
var key Key
|
|
if state != nil && state.utf16Half {
|
|
state.utf16Half = false
|
|
state.utf16Buf[1] = r
|
|
codepoint := utf16.DecodeRune(state.utf16Buf[0], state.utf16Buf[1])
|
|
rw := utf8.EncodeRune(utf8Buf[:], codepoint)
|
|
r, _ = utf8.DecodeRune(utf8Buf[:rw])
|
|
key.Code = r
|
|
key.Text = string(r)
|
|
key.Mod = translateControlKeyState(cks)
|
|
key = ensureKeyCase(key, cks)
|
|
if keyDown {
|
|
return KeyPressEvent(key)
|
|
}
|
|
return KeyReleaseEvent(key)
|
|
}
|
|
|
|
var baseCode rune
|
|
switch {
|
|
case vkc == 0:
|
|
// Zero means this event is either an escape code or a unicode
|
|
// codepoint.
|
|
if state != nil && state.ansiIdx == 0 && r != ansi.ESC {
|
|
// This is a unicode codepoint.
|
|
baseCode = r
|
|
break
|
|
}
|
|
|
|
if state != nil {
|
|
// Collect ANSI escape code.
|
|
state.ansiBuf[state.ansiIdx] = byte(r)
|
|
state.ansiIdx++
|
|
if state.ansiIdx <= 2 {
|
|
// We haven't received enough bytes to determine if this is an
|
|
// ANSI escape code.
|
|
return nil
|
|
}
|
|
if r == ansi.ESC {
|
|
// We're expecting a closing String Terminator [ansi.ST].
|
|
return nil
|
|
}
|
|
|
|
n, event := p.parseSequence(state.ansiBuf[:state.ansiIdx])
|
|
if n == 0 {
|
|
return nil
|
|
}
|
|
if _, ok := event.(UnknownEvent); ok {
|
|
return nil
|
|
}
|
|
|
|
state.ansiIdx = 0
|
|
return event
|
|
}
|
|
case vkc == xwindows.VK_BACK:
|
|
baseCode = KeyBackspace
|
|
case vkc == xwindows.VK_TAB:
|
|
baseCode = KeyTab
|
|
case vkc == xwindows.VK_RETURN:
|
|
baseCode = KeyEnter
|
|
case vkc == xwindows.VK_SHIFT:
|
|
//nolint:nestif
|
|
if cks&xwindows.SHIFT_PRESSED != 0 {
|
|
if cks&xwindows.ENHANCED_KEY != 0 {
|
|
baseCode = KeyRightShift
|
|
} else {
|
|
baseCode = KeyLeftShift
|
|
}
|
|
} else if state != nil {
|
|
if state.lastCks&xwindows.SHIFT_PRESSED != 0 {
|
|
if state.lastCks&xwindows.ENHANCED_KEY != 0 {
|
|
baseCode = KeyRightShift
|
|
} else {
|
|
baseCode = KeyLeftShift
|
|
}
|
|
}
|
|
}
|
|
case vkc == xwindows.VK_CONTROL:
|
|
if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
|
|
baseCode = KeyLeftCtrl
|
|
} else if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
|
|
baseCode = KeyRightCtrl
|
|
} else if state != nil {
|
|
if state.lastCks&xwindows.LEFT_CTRL_PRESSED != 0 {
|
|
baseCode = KeyLeftCtrl
|
|
} else if state.lastCks&xwindows.RIGHT_CTRL_PRESSED != 0 {
|
|
baseCode = KeyRightCtrl
|
|
}
|
|
}
|
|
case vkc == xwindows.VK_MENU:
|
|
if cks&xwindows.LEFT_ALT_PRESSED != 0 {
|
|
baseCode = KeyLeftAlt
|
|
} else if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
|
|
baseCode = KeyRightAlt
|
|
} else if state != nil {
|
|
if state.lastCks&xwindows.LEFT_ALT_PRESSED != 0 {
|
|
baseCode = KeyLeftAlt
|
|
} else if state.lastCks&xwindows.RIGHT_ALT_PRESSED != 0 {
|
|
baseCode = KeyRightAlt
|
|
}
|
|
}
|
|
case vkc == xwindows.VK_PAUSE:
|
|
baseCode = KeyPause
|
|
case vkc == xwindows.VK_CAPITAL:
|
|
baseCode = KeyCapsLock
|
|
case vkc == xwindows.VK_ESCAPE:
|
|
baseCode = KeyEscape
|
|
case vkc == xwindows.VK_SPACE:
|
|
baseCode = KeySpace
|
|
case vkc == xwindows.VK_PRIOR:
|
|
baseCode = KeyPgUp
|
|
case vkc == xwindows.VK_NEXT:
|
|
baseCode = KeyPgDown
|
|
case vkc == xwindows.VK_END:
|
|
baseCode = KeyEnd
|
|
case vkc == xwindows.VK_HOME:
|
|
baseCode = KeyHome
|
|
case vkc == xwindows.VK_LEFT:
|
|
baseCode = KeyLeft
|
|
case vkc == xwindows.VK_UP:
|
|
baseCode = KeyUp
|
|
case vkc == xwindows.VK_RIGHT:
|
|
baseCode = KeyRight
|
|
case vkc == xwindows.VK_DOWN:
|
|
baseCode = KeyDown
|
|
case vkc == xwindows.VK_SELECT:
|
|
baseCode = KeySelect
|
|
case vkc == xwindows.VK_SNAPSHOT:
|
|
baseCode = KeyPrintScreen
|
|
case vkc == xwindows.VK_INSERT:
|
|
baseCode = KeyInsert
|
|
case vkc == xwindows.VK_DELETE:
|
|
baseCode = KeyDelete
|
|
case vkc >= '0' && vkc <= '9':
|
|
baseCode = rune(vkc)
|
|
case vkc >= 'A' && vkc <= 'Z':
|
|
// Convert to lowercase.
|
|
baseCode = rune(vkc) + 32
|
|
case vkc == xwindows.VK_LWIN:
|
|
baseCode = KeyLeftSuper
|
|
case vkc == xwindows.VK_RWIN:
|
|
baseCode = KeyRightSuper
|
|
case vkc == xwindows.VK_APPS:
|
|
baseCode = KeyMenu
|
|
case vkc >= xwindows.VK_NUMPAD0 && vkc <= xwindows.VK_NUMPAD9:
|
|
baseCode = rune(vkc-xwindows.VK_NUMPAD0) + KeyKp0
|
|
case vkc == xwindows.VK_MULTIPLY:
|
|
baseCode = KeyKpMultiply
|
|
case vkc == xwindows.VK_ADD:
|
|
baseCode = KeyKpPlus
|
|
case vkc == xwindows.VK_SEPARATOR:
|
|
baseCode = KeyKpComma
|
|
case vkc == xwindows.VK_SUBTRACT:
|
|
baseCode = KeyKpMinus
|
|
case vkc == xwindows.VK_DECIMAL:
|
|
baseCode = KeyKpDecimal
|
|
case vkc == xwindows.VK_DIVIDE:
|
|
baseCode = KeyKpDivide
|
|
case vkc >= xwindows.VK_F1 && vkc <= xwindows.VK_F24:
|
|
baseCode = rune(vkc-xwindows.VK_F1) + KeyF1
|
|
case vkc == xwindows.VK_NUMLOCK:
|
|
baseCode = KeyNumLock
|
|
case vkc == xwindows.VK_SCROLL:
|
|
baseCode = KeyScrollLock
|
|
case vkc == xwindows.VK_LSHIFT:
|
|
baseCode = KeyLeftShift
|
|
case vkc == xwindows.VK_RSHIFT:
|
|
baseCode = KeyRightShift
|
|
case vkc == xwindows.VK_LCONTROL:
|
|
baseCode = KeyLeftCtrl
|
|
case vkc == xwindows.VK_RCONTROL:
|
|
baseCode = KeyRightCtrl
|
|
case vkc == xwindows.VK_LMENU:
|
|
baseCode = KeyLeftAlt
|
|
case vkc == xwindows.VK_RMENU:
|
|
baseCode = KeyRightAlt
|
|
case vkc == xwindows.VK_VOLUME_MUTE:
|
|
baseCode = KeyMute
|
|
case vkc == xwindows.VK_VOLUME_DOWN:
|
|
baseCode = KeyLowerVol
|
|
case vkc == xwindows.VK_VOLUME_UP:
|
|
baseCode = KeyRaiseVol
|
|
case vkc == xwindows.VK_MEDIA_NEXT_TRACK:
|
|
baseCode = KeyMediaNext
|
|
case vkc == xwindows.VK_MEDIA_PREV_TRACK:
|
|
baseCode = KeyMediaPrev
|
|
case vkc == xwindows.VK_MEDIA_STOP:
|
|
baseCode = KeyMediaStop
|
|
case vkc == xwindows.VK_MEDIA_PLAY_PAUSE:
|
|
baseCode = KeyMediaPlayPause
|
|
case vkc == xwindows.VK_OEM_1, vkc == xwindows.VK_OEM_PLUS, vkc == xwindows.VK_OEM_COMMA,
|
|
vkc == xwindows.VK_OEM_MINUS, vkc == xwindows.VK_OEM_PERIOD, vkc == xwindows.VK_OEM_2,
|
|
vkc == xwindows.VK_OEM_3, vkc == xwindows.VK_OEM_4, vkc == xwindows.VK_OEM_5,
|
|
vkc == xwindows.VK_OEM_6, vkc == xwindows.VK_OEM_7:
|
|
// Use the actual character provided by Windows for current keyboard layout
|
|
// instead of hardcoded US layout mappings
|
|
if !unicode.IsControl(r) && unicode.IsPrint(r) {
|
|
baseCode = r
|
|
} else {
|
|
// Fallback to original hardcoded mappings for non-printable cases
|
|
switch vkc {
|
|
case xwindows.VK_OEM_1:
|
|
baseCode = ';'
|
|
case xwindows.VK_OEM_PLUS:
|
|
baseCode = '+'
|
|
case xwindows.VK_OEM_COMMA:
|
|
baseCode = ','
|
|
case xwindows.VK_OEM_MINUS:
|
|
baseCode = '-'
|
|
case xwindows.VK_OEM_PERIOD:
|
|
baseCode = '.'
|
|
case xwindows.VK_OEM_2:
|
|
baseCode = '/'
|
|
case xwindows.VK_OEM_3:
|
|
baseCode = '`'
|
|
case xwindows.VK_OEM_4:
|
|
baseCode = '['
|
|
case xwindows.VK_OEM_5:
|
|
baseCode = '\\'
|
|
case xwindows.VK_OEM_6:
|
|
baseCode = ']'
|
|
case xwindows.VK_OEM_7:
|
|
baseCode = '\''
|
|
}
|
|
}
|
|
}
|
|
|
|
if utf16.IsSurrogate(r) {
|
|
if state != nil {
|
|
state.utf16Buf[0] = r
|
|
state.utf16Half = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AltGr is left ctrl + right alt. On non-US keyboards, this is used to type
|
|
// special characters and produce printable events.
|
|
// XXX: Should this be a KeyMod?
|
|
altGr := cks&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_ALT_PRESSED) == xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_ALT_PRESSED
|
|
|
|
// FIXED: Remove numlock and scroll lock states when checking for printable text
|
|
// These lock states shouldn't affect normal typing
|
|
cksForTextCheck := cks &^ (xwindows.NUMLOCK_ON | xwindows.SCROLLLOCK_ON)
|
|
|
|
var text string
|
|
keyCode := baseCode
|
|
if !unicode.IsControl(r) {
|
|
rw := utf8.EncodeRune(utf8Buf[:], r)
|
|
keyCode, _ = utf8.DecodeRune(utf8Buf[:rw])
|
|
if unicode.IsPrint(keyCode) && (cksForTextCheck == 0 ||
|
|
cksForTextCheck == xwindows.SHIFT_PRESSED ||
|
|
cksForTextCheck == xwindows.CAPSLOCK_ON ||
|
|
altGr) {
|
|
// If the control key state is 0, shift is pressed, or caps lock
|
|
// then the key event is a printable event i.e. [text] is not empty.
|
|
text = string(keyCode)
|
|
}
|
|
}
|
|
|
|
// Special case: numeric keypad divide should produce "/" text on all layouts (fix french keyboard layout)
|
|
if baseCode == KeyKpDivide {
|
|
text = "/"
|
|
}
|
|
|
|
key.Code = keyCode
|
|
key.Text = text
|
|
key.Mod = translateControlKeyState(cks)
|
|
key.BaseCode = baseCode
|
|
key = ensureKeyCase(key, cks)
|
|
if keyDown {
|
|
return KeyPressEvent(key)
|
|
}
|
|
|
|
return KeyReleaseEvent(key)
|
|
}
|
|
|
|
// ensureKeyCase ensures that the key's text is in the correct case based on the
|
|
// control key state.
|
|
func ensureKeyCase(key Key, cks uint32) Key {
|
|
if len(key.Text) == 0 {
|
|
return key
|
|
}
|
|
|
|
hasShift := cks&xwindows.SHIFT_PRESSED != 0
|
|
hasCaps := cks&xwindows.CAPSLOCK_ON != 0
|
|
if hasShift || hasCaps {
|
|
if unicode.IsLower(key.Code) {
|
|
key.ShiftedCode = unicode.ToUpper(key.Code)
|
|
key.Text = string(key.ShiftedCode)
|
|
}
|
|
} else {
|
|
if unicode.IsUpper(key.Code) {
|
|
key.ShiftedCode = unicode.ToLower(key.Code)
|
|
key.Text = string(key.ShiftedCode)
|
|
}
|
|
}
|
|
|
|
return key
|
|
}
|
|
|
|
// translateControlKeyState translates the control key state from the Windows
|
|
// Console API into a Mod bitmask.
|
|
func translateControlKeyState(cks uint32) (m KeyMod) {
|
|
if cks&xwindows.LEFT_CTRL_PRESSED != 0 || cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
|
|
m |= ModCtrl
|
|
}
|
|
if cks&xwindows.LEFT_ALT_PRESSED != 0 || cks&xwindows.RIGHT_ALT_PRESSED != 0 {
|
|
m |= ModAlt
|
|
}
|
|
if cks&xwindows.SHIFT_PRESSED != 0 {
|
|
m |= ModShift
|
|
}
|
|
if cks&xwindows.CAPSLOCK_ON != 0 {
|
|
m |= ModCapsLock
|
|
}
|
|
if cks&xwindows.NUMLOCK_ON != 0 {
|
|
m |= ModNumLock
|
|
}
|
|
if cks&xwindows.SCROLLLOCK_ON != 0 {
|
|
m |= ModScrollLock
|
|
}
|
|
return
|
|
}
|
|
|
|
//nolint:unused
|
|
func keyEventString(vkc, sc uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) string {
|
|
var s strings.Builder
|
|
s.WriteString("vkc: ")
|
|
s.WriteString(fmt.Sprintf("%d, 0x%02x", vkc, vkc))
|
|
s.WriteString(", sc: ")
|
|
s.WriteString(fmt.Sprintf("%d, 0x%02x", sc, sc))
|
|
s.WriteString(", r: ")
|
|
s.WriteString(fmt.Sprintf("%q", r))
|
|
s.WriteString(", down: ")
|
|
s.WriteString(fmt.Sprintf("%v", keyDown))
|
|
s.WriteString(", cks: [")
|
|
if cks&xwindows.LEFT_ALT_PRESSED != 0 {
|
|
s.WriteString("left alt, ")
|
|
}
|
|
if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
|
|
s.WriteString("right alt, ")
|
|
}
|
|
if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
|
|
s.WriteString("left ctrl, ")
|
|
}
|
|
if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
|
|
s.WriteString("right ctrl, ")
|
|
}
|
|
if cks&xwindows.SHIFT_PRESSED != 0 {
|
|
s.WriteString("shift, ")
|
|
}
|
|
if cks&xwindows.CAPSLOCK_ON != 0 {
|
|
s.WriteString("caps lock, ")
|
|
}
|
|
if cks&xwindows.NUMLOCK_ON != 0 {
|
|
s.WriteString("num lock, ")
|
|
}
|
|
if cks&xwindows.SCROLLLOCK_ON != 0 {
|
|
s.WriteString("scroll lock, ")
|
|
}
|
|
if cks&xwindows.ENHANCED_KEY != 0 {
|
|
s.WriteString("enhanced key, ")
|
|
}
|
|
s.WriteString("], repeat count: ")
|
|
s.WriteString(fmt.Sprintf("%d", repeatCount))
|
|
return s.String()
|
|
}
|