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