| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- package input
- import (
- "encoding/binary"
- "image/color"
- "reflect"
- "testing"
- "unicode/utf16"
- "github.com/charmbracelet/x/ansi"
- xwindows "github.com/charmbracelet/x/windows"
- "golang.org/x/sys/windows"
- )
- func TestWindowsInputEvents(t *testing.T) {
- cases := []struct {
- name string
- events []xwindows.InputRecord
- expected []Event
- sequence bool // indicates that the input events are ANSI sequence or utf16
- }{
- {
- name: "single key event",
- events: []xwindows.InputRecord{
- encodeKeyEvent(xwindows.KeyEventRecord{
- KeyDown: true,
- Char: 'a',
- VirtualKeyCode: 'A',
- }),
- },
- expected: []Event{KeyPressEvent{Code: 'a', BaseCode: 'a', Text: "a"}},
- },
- {
- name: "single key event with control key",
- events: []xwindows.InputRecord{
- encodeKeyEvent(xwindows.KeyEventRecord{
- KeyDown: true,
- Char: 'a',
- VirtualKeyCode: 'A',
- ControlKeyState: xwindows.LEFT_CTRL_PRESSED,
- }),
- },
- expected: []Event{KeyPressEvent{Code: 'a', BaseCode: 'a', Mod: ModCtrl}},
- },
- {
- name: "escape alt key event",
- events: []xwindows.InputRecord{
- encodeKeyEvent(xwindows.KeyEventRecord{
- KeyDown: true,
- Char: ansi.ESC,
- VirtualKeyCode: ansi.ESC,
- ControlKeyState: xwindows.LEFT_ALT_PRESSED,
- }),
- },
- expected: []Event{KeyPressEvent{Code: ansi.ESC, BaseCode: ansi.ESC, Mod: ModAlt}},
- },
- {
- name: "single shifted key event",
- events: []xwindows.InputRecord{
- encodeKeyEvent(xwindows.KeyEventRecord{
- KeyDown: true,
- Char: 'A',
- VirtualKeyCode: 'A',
- ControlKeyState: xwindows.SHIFT_PRESSED,
- }),
- },
- expected: []Event{KeyPressEvent{Code: 'A', BaseCode: 'a', Text: "A", Mod: ModShift}},
- },
- {
- name: "utf16 rune",
- events: encodeUtf16Rune('😊'), // smiley emoji '😊'
- expected: []Event{
- KeyPressEvent{Code: '😊', Text: "😊"},
- },
- sequence: true,
- },
- {
- name: "background color response",
- events: encodeSequence("\x1b]11;rgb:ff/ff/ff\x07"),
- expected: []Event{BackgroundColorEvent{Color: color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}}},
- sequence: true,
- },
- {
- name: "st terminated background color response",
- events: encodeSequence("\x1b]11;rgb:ffff/ffff/ffff\x1b\\"),
- expected: []Event{BackgroundColorEvent{Color: color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}}},
- sequence: true,
- },
- {
- name: "simple mouse event",
- events: []xwindows.InputRecord{
- encodeMouseEvent(xwindows.MouseEventRecord{
- MousePositon: windows.Coord{X: 10, Y: 20},
- ButtonState: xwindows.FROM_LEFT_1ST_BUTTON_PRESSED,
- EventFlags: 0,
- }),
- encodeMouseEvent(xwindows.MouseEventRecord{
- MousePositon: windows.Coord{X: 10, Y: 20},
- EventFlags: 0,
- }),
- },
- expected: []Event{
- MouseClickEvent{Button: MouseLeft, X: 10, Y: 20},
- MouseReleaseEvent{Button: MouseLeft, X: 10, Y: 20},
- },
- },
- {
- name: "focus event",
- events: []xwindows.InputRecord{
- encodeFocusEvent(xwindows.FocusEventRecord{
- SetFocus: true,
- }),
- encodeFocusEvent(xwindows.FocusEventRecord{
- SetFocus: false,
- }),
- },
- expected: []Event{
- FocusEvent{},
- BlurEvent{},
- },
- },
- {
- name: "window size event",
- events: []xwindows.InputRecord{
- encodeWindowBufferSizeEvent(xwindows.WindowBufferSizeRecord{
- Size: windows.Coord{X: 10, Y: 20},
- }),
- },
- expected: []Event{
- WindowSizeEvent{Width: 10, Height: 20},
- },
- },
- }
- // p is the parser to parse the input events
- var p Parser
- // keep track of the state of the driver to handle ANSI sequences and utf16
- var state win32InputState
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.sequence {
- var Event Event
- for _, ev := range tc.events {
- if ev.EventType != xwindows.KEY_EVENT {
- t.Fatalf("expected key event, got %v", ev.EventType)
- }
- key := ev.KeyEvent()
- Event = p.parseWin32InputKeyEvent(&state, key.VirtualKeyCode, key.VirtualScanCode, key.Char, key.KeyDown, key.ControlKeyState, key.RepeatCount)
- }
- if len(tc.expected) != 1 {
- t.Fatalf("expected 1 event, got %d", len(tc.expected))
- }
- if !reflect.DeepEqual(Event, tc.expected[0]) {
- t.Errorf("expected %v, got %v", tc.expected[0], Event)
- }
- } else {
- if len(tc.events) != len(tc.expected) {
- t.Fatalf("expected %d events, got %d", len(tc.expected), len(tc.events))
- }
- for j, ev := range tc.events {
- Event := p.parseConInputEvent(ev, &state)
- if !reflect.DeepEqual(Event, tc.expected[j]) {
- t.Errorf("expected %#v, got %#v", tc.expected[j], Event)
- }
- }
- }
- })
- }
- }
- func boolToUint32(b bool) uint32 {
- if b {
- return 1
- }
- return 0
- }
- func encodeMenuEvent(menu xwindows.MenuEventRecord) xwindows.InputRecord {
- var bts [16]byte
- binary.LittleEndian.PutUint32(bts[0:4], menu.CommandID)
- return xwindows.InputRecord{
- EventType: xwindows.MENU_EVENT,
- Event: bts,
- }
- }
- func encodeWindowBufferSizeEvent(size xwindows.WindowBufferSizeRecord) xwindows.InputRecord {
- var bts [16]byte
- binary.LittleEndian.PutUint16(bts[0:2], uint16(size.Size.X))
- binary.LittleEndian.PutUint16(bts[2:4], uint16(size.Size.Y))
- return xwindows.InputRecord{
- EventType: xwindows.WINDOW_BUFFER_SIZE_EVENT,
- Event: bts,
- }
- }
- func encodeFocusEvent(focus xwindows.FocusEventRecord) xwindows.InputRecord {
- var bts [16]byte
- if focus.SetFocus {
- bts[0] = 1
- }
- return xwindows.InputRecord{
- EventType: xwindows.FOCUS_EVENT,
- Event: bts,
- }
- }
- func encodeMouseEvent(mouse xwindows.MouseEventRecord) xwindows.InputRecord {
- var bts [16]byte
- binary.LittleEndian.PutUint16(bts[0:2], uint16(mouse.MousePositon.X))
- binary.LittleEndian.PutUint16(bts[2:4], uint16(mouse.MousePositon.Y))
- binary.LittleEndian.PutUint32(bts[4:8], mouse.ButtonState)
- binary.LittleEndian.PutUint32(bts[8:12], mouse.ControlKeyState)
- binary.LittleEndian.PutUint32(bts[12:16], mouse.EventFlags)
- return xwindows.InputRecord{
- EventType: xwindows.MOUSE_EVENT,
- Event: bts,
- }
- }
- func encodeKeyEvent(key xwindows.KeyEventRecord) xwindows.InputRecord {
- var bts [16]byte
- binary.LittleEndian.PutUint32(bts[0:4], boolToUint32(key.KeyDown))
- binary.LittleEndian.PutUint16(bts[4:6], key.RepeatCount)
- binary.LittleEndian.PutUint16(bts[6:8], key.VirtualKeyCode)
- binary.LittleEndian.PutUint16(bts[8:10], key.VirtualScanCode)
- binary.LittleEndian.PutUint16(bts[10:12], uint16(key.Char))
- binary.LittleEndian.PutUint32(bts[12:16], key.ControlKeyState)
- return xwindows.InputRecord{
- EventType: xwindows.KEY_EVENT,
- Event: bts,
- }
- }
- // encodeSequence encodes a string of ANSI escape sequences into a slice of
- // Windows input key records.
- func encodeSequence(s string) (evs []xwindows.InputRecord) {
- var state byte
- for len(s) > 0 {
- seq, _, n, newState := ansi.DecodeSequence(s, state, nil)
- for i := 0; i < n; i++ {
- evs = append(evs, encodeKeyEvent(xwindows.KeyEventRecord{
- KeyDown: true,
- Char: rune(seq[i]),
- }))
- }
- state = newState
- s = s[n:]
- }
- return
- }
- func encodeUtf16Rune(r rune) []xwindows.InputRecord {
- r1, r2 := utf16.EncodeRune(r)
- return encodeUtf16Pair(r1, r2)
- }
- func encodeUtf16Pair(r1, r2 rune) []xwindows.InputRecord {
- return []xwindows.InputRecord{
- encodeKeyEvent(xwindows.KeyEventRecord{
- KeyDown: true,
- Char: r1,
- }),
- encodeKeyEvent(xwindows.KeyEventRecord{
- KeyDown: true,
- Char: r2,
- }),
- }
- }
|