package coninput import ( "encoding/binary" "fmt" "strconv" "strings" ) const ( maxEventSize = 16 wordPaddingBytes = 2 ) // EventType denots the type of an event type EventType uint16 // EventUnion is the union data type that contains the data for any event. type EventUnion [maxEventSize]byte // InputRecord corresponds to the INPUT_RECORD structure from the Windows // console API (see // https://docs.microsoft.com/en-us/windows/console/input-record-str). type InputRecord struct { // EventType specifies the type of event that helt in Event. EventType EventType // Padding of the 16-bit EventType to a whole 32-bit dword. _ [wordPaddingBytes]byte // Event holds the actual event data. Use Unrap to access it as its // respective event type. Event EventUnion } // String implements fmt.Stringer for InputRecord. func (ir InputRecord) String() string { return ir.Unwrap().String() } // Unwrap parses the event data into an EventRecord of the respective event // type. The data in the returned EventRecord does not contain any references to // the passed InputRecord. func (ir InputRecord) Unwrap() EventRecord { switch ir.EventType { case FocusEventType: return FocusEventRecord{SetFocus: ir.Event[0] > 0} case KeyEventType: return KeyEventRecord{ KeyDown: binary.LittleEndian.Uint32(ir.Event[0:4]) > 0, RepeatCount: binary.LittleEndian.Uint16(ir.Event[4:6]), VirtualKeyCode: VirtualKeyCode(binary.LittleEndian.Uint16(ir.Event[6:8])), VirtualScanCode: VirtualKeyCode(binary.LittleEndian.Uint16(ir.Event[8:10])), Char: rune(binary.LittleEndian.Uint16(ir.Event[10:12])), ControlKeyState: ControlKeyState(binary.LittleEndian.Uint32(ir.Event[12:16])), } case MouseEventType: m := MouseEventRecord{ MousePositon: Coord{ X: binary.LittleEndian.Uint16(ir.Event[0:2]), Y: binary.LittleEndian.Uint16(ir.Event[2:4]), }, ButtonState: ButtonState(binary.LittleEndian.Uint32(ir.Event[4:8])), ControlKeyState: ControlKeyState(binary.LittleEndian.Uint32(ir.Event[8:12])), EventFlags: EventFlags(binary.LittleEndian.Uint32(ir.Event[12:16])), } if (m.EventFlags&MOUSE_WHEELED > 0) || (m.EventFlags&MOUSE_HWHEELED > 0) { if int16(highWord(uint32(m.ButtonState))) > 0 { m.WheelDirection = 1 } else { m.WheelDirection = -1 } } return m case WindowBufferSizeEventType: return WindowBufferSizeEventRecord{ Size: Coord{ X: binary.LittleEndian.Uint16(ir.Event[0:2]), Y: binary.LittleEndian.Uint16(ir.Event[2:4]), }, } case MenuEventType: return MenuEventRecord{ CommandID: binary.LittleEndian.Uint32(ir.Event[0:4]), } default: return &UnknownEvent{InputRecord: ir} } } // EventRecord represents one of the following event types: // TypeFocusEventRecord, TypeKeyEventRecord, TypeMouseEventRecord, // TypeWindowBufferSizeEvent, TypeMenuEventRecord and UnknownEvent. type EventRecord interface { Type() string fmt.Stringer } // FocusEventType is the event type for a FocusEventRecord (see // https://docs.microsoft.com/en-us/windows/console/input-record-str). const FocusEventType EventType = 0x0010 // FocusEventRecord represent the FOCUS_EVENT_RECORD structure from the Windows // console API (see // https://docs.microsoft.com/en-us/windows/console/focus-event-record-str). // These events are used internally by the Windows console API and should be // ignored. type FocusEventRecord struct { // SetFocus is reserved and should not be used. SetFocus bool } // Ensure that FocusEventRecord satisfies EventRecord interface. var _ EventRecord = FocusEventRecord{} // Type ensures that FocusEventRecord satisfies EventRecord interface. func (e FocusEventRecord) Type() string { return "FocusEvent" } // String ensures that FocusEventRecord satisfies EventRecord and fmt.Stringer // interfaces. func (e FocusEventRecord) String() string { return fmt.Sprintf("%s[%v]", e.Type(), e.SetFocus) } // KeyEventType is the event type for a KeyEventRecord (see // https://docs.microsoft.com/en-us/windows/console/input-record-str). const KeyEventType EventType = 0x0001 // KeyEventRecord represent the KEY_EVENT_RECORD structure from the Windows // console API (see // https://docs.microsoft.com/en-us/windows/console/key-event-record-str). type KeyEventRecord struct { // KeyDown specified whether the key is pressed or released. KeyDown bool // RepeatCount indicates that a key is being held down. For example, when a // key is held down, five events with RepeatCount equal to 1 may be // generated, one event with RepeatCount equal to 5, or multiple events // with RepeatCount greater than or equal to 1. RepeatCount uint16 // VirtualKeyCode identifies the given key in a device-independent manner // (see // https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). VirtualKeyCode VirtualKeyCode // VirtualScanCode represents the device-dependent value generated by the // keyboard hardware. VirtualScanCode VirtualKeyCode // Char is the character that corresponds to the pressed key. Char can be // zero for some keys. Char rune //ControlKeyState holds the state of the control keys. ControlKeyState ControlKeyState } // Ensure that KeyEventRecord satisfies EventRecord interface. var _ EventRecord = KeyEventRecord{} // Type ensures that KeyEventRecord satisfies EventRecord interface. func (e KeyEventRecord) Type() string { return "KeyEvent" } // String ensures that KeyEventRecord satisfies EventRecord and fmt.Stringer // interfaces. func (e KeyEventRecord) String() string { infos := []string{} repeat := "" if e.RepeatCount > 1 { repeat = "x" + strconv.Itoa(int(e.RepeatCount)) } infos = append(infos, fmt.Sprintf("%q%s", e.Char, repeat)) direction := "up" if e.KeyDown { direction = "down" } infos = append(infos, direction) if e.ControlKeyState != NO_CONTROL_KEY { infos = append(infos, e.ControlKeyState.String()) } infos = append(infos, fmt.Sprintf("KeyCode: %d", e.VirtualKeyCode)) infos = append(infos, fmt.Sprintf("ScanCode: %d", e.VirtualScanCode)) return fmt.Sprintf("%s[%s]", e.Type(), strings.Join(infos, ", ")) } // MenuEventType is the event type for a MenuEventRecord (see // https://docs.microsoft.com/en-us/windows/console/input-record-str). const MenuEventType EventType = 0x0008 // MenuEventRecord represent the MENU_EVENT_RECORD structure from the Windows // console API (see // https://docs.microsoft.com/en-us/windows/console/menu-event-record-str). // These events are deprecated by the Windows console API and should be ignored. type MenuEventRecord struct { CommandID uint32 } // Ensure that MenuEventRecord satisfies EventRecord interface. var _ EventRecord = MenuEventRecord{} // Type ensures that MenuEventRecord satisfies EventRecord interface. func (e MenuEventRecord) Type() string { return "MenuEvent" } // String ensures that MenuEventRecord satisfies EventRecord and fmt.Stringer // interfaces. func (e MenuEventRecord) String() string { return fmt.Sprintf("MenuEvent[%d]", e.CommandID) } // MouseEventType is the event type for a MouseEventRecord (see // https://docs.microsoft.com/en-us/windows/console/input-record-str). const MouseEventType EventType = 0x0002 // MouseEventRecord represent the MOUSE_EVENT_RECORD structure from the Windows // console API (see // https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str). type MouseEventRecord struct { // MousePosition contains the location of the cursor, in terms of the // console screen buffer's character-cell coordinates. MousePositon Coord // ButtonState holds the status of the mouse buttons. ButtonState ButtonState // ControlKeyState holds the state of the control keys. ControlKeyState ControlKeyState // EventFlags specify tge type of mouse event. EventFlags EventFlags // WheelDirection specified the direction in which the mouse wheel is // spinning when EventFlags contains MOUSE_HWHEELED or MOUSE_WHEELED. When // the event flags specify MOUSE_WHEELED it is 1 if the wheel rotated // forward (away from the user) or -1 when it rotates backwards. When // MOUSE_HWHEELED is specified it is 1 when the wheel rotates right and -1 // when it rotates left. When the EventFlags do not indicate a mouse wheel // event it is 0. WheelDirection int } // Ensure that MouseEventRecord satisfies EventRecord interface. var _ EventRecord = MouseEventRecord{} func (e MouseEventRecord) WheelDirectionName() string { if e.EventFlags&MOUSE_WHEELED > 0 { if e.WheelDirection > 0 { return "Forward" } return "Backward" } else if e.EventFlags&MOUSE_HWHEELED > 0 { if e.WheelDirection > 0 { return "Right" } return "Left" } return "" } // Type ensures that MouseEventRecord satisfies EventRecord interface. func (e MouseEventRecord) Type() string { return "MouseEvent" } // String ensures that MouseEventRecord satisfies EventRecord and fmt.Stringer // interfaces. func (e MouseEventRecord) String() string { infos := []string{e.MousePositon.String()} if e.ButtonState&0xFF != 0 { infos = append(infos, e.ButtonState.String()) } eventDescription := e.EventFlags.String() wheelDirection := e.WheelDirectionName() if wheelDirection != "" { eventDescription += "(" + wheelDirection + ")" } infos = append(infos, eventDescription) if e.ControlKeyState != NO_CONTROL_KEY { infos = append(infos, e.ControlKeyState.String()) } return fmt.Sprintf("%s[%s]", e.Type(), strings.Join(infos, ", ")) } // WindowBufferSizeEventType is the event type for a WindowBufferSizeEventRecord // (see https://docs.microsoft.com/en-us/windows/console/input-record-str). const WindowBufferSizeEventType EventType = 0x0004 // WindowBufferSizeEventRecord represent the WINDOW_BUFFER_SIZE_RECORD structure // from the Windows console API (see // https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str). type WindowBufferSizeEventRecord struct { // Size contains the size of the console screen buffer, in character cell columns and rows. Size Coord } // Ensure that WindowBufferSizeEventRecord satisfies EventRecord interface. var _ EventRecord = WindowBufferSizeEventRecord{} // Type ensures that WindowBufferSizeEventRecord satisfies EventRecord interface. func (e WindowBufferSizeEventRecord) Type() string { return "WindowBufferSizeEvent" } // String ensures that WindowBufferSizeEventRecord satisfies EventRecord and fmt.Stringer // interfaces. func (e WindowBufferSizeEventRecord) String() string { return fmt.Sprintf("WindowBufferSizeEvent[%s]", e.Size) } // UnknownEvent is generated when the event type does not match one of the // following types: TypeFocusEventRecord, TypeKeyEventRecord, // TypeMouseEventRecord, TypeWindowBufferSizeEvent, TypeMenuEventRecord and // UnknownEvent. type UnknownEvent struct { InputRecord } // Ensure that UnknownEvent satisfies EventRecord interface. var _ EventRecord = UnknownEvent{} // Type ensures that UnknownEvent satisfies EventRecord interface. func (e UnknownEvent) Type() string { return "UnknownEvent" } // String ensures that UnknownEvent satisfies EventRecord and fmt.Stringer // interfaces. func (e UnknownEvent) String() string { return fmt.Sprintf("%s[Type: %d, Data: %v]", e.Type(), e.InputRecord.EventType, e.InputRecord.Event[:]) } // Coord represent the COORD structure from the Windows // console API (see https://docs.microsoft.com/en-us/windows/console/coord-str). type Coord struct { // X is the horizontal coordinate or column value. The units depend on the function call. X uint16 // Y is the vertical coordinate or row value. The units depend on the function call. Y uint16 } // String ensures that Coord satisfies the fmt.Stringer interface. func (c Coord) String() string { return fmt.Sprintf("(%d, %d)", c.X, c.Y) } // ButtonState holds the state of the mouse buttons (see // https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str). type ButtonState uint32 func (bs ButtonState) Contains(state ButtonState) bool { return bs&state > 0 } // String ensures that ButtonState satisfies the fmt.Stringer interface. func (bs ButtonState) String() string { switch { case bs&FROM_LEFT_1ST_BUTTON_PRESSED > 0: return "Left" case bs&FROM_LEFT_2ND_BUTTON_PRESSED > 0: return "2" case bs&FROM_LEFT_3RD_BUTTON_PRESSED > 0: return "3" case bs&FROM_LEFT_4TH_BUTTON_PRESSED > 0: return "4" case bs&RIGHTMOST_BUTTON_PRESSED > 0: return "Right" case bs&0xFF == 0: return "No Button" default: return fmt.Sprintf("Unknown(%d)", bs) } } func (bs ButtonState) IsReleased() bool { return bs&0xff > 0 } // Valid values for ButtonState. const ( FROM_LEFT_1ST_BUTTON_PRESSED ButtonState = 0x0001 RIGHTMOST_BUTTON_PRESSED ButtonState = 0x0002 FROM_LEFT_2ND_BUTTON_PRESSED ButtonState = 0x0004 FROM_LEFT_3RD_BUTTON_PRESSED ButtonState = 0x0008 FROM_LEFT_4TH_BUTTON_PRESSED ButtonState = 0x0010 ) // ControlKeyState holds the state of the control keys for key and mouse events // (see https://docs.microsoft.com/en-us/windows/console/key-event-record-str // and https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str). type ControlKeyState uint32 func (cks ControlKeyState) Contains(state ControlKeyState) bool { return cks&state > 0 } // Valid values for ControlKeyState. const ( CAPSLOCK_ON ControlKeyState = 0x0080 ENHANCED_KEY ControlKeyState = 0x0100 LEFT_ALT_PRESSED ControlKeyState = 0x0002 LEFT_CTRL_PRESSED ControlKeyState = 0x0008 NUMLOCK_ON ControlKeyState = 0x0020 RIGHT_ALT_PRESSED ControlKeyState = 0x0001 RIGHT_CTRL_PRESSED ControlKeyState = 0x0004 SCROLLLOCK_ON ControlKeyState = 0x0040 SHIFT_PRESSED ControlKeyState = 0x0010 NO_CONTROL_KEY ControlKeyState = 0x0000 ) // String ensures that ControlKeyState satisfies the fmt.Stringer interface. func (cks ControlKeyState) String() string { controlKeys := []string{} switch { case cks&CAPSLOCK_ON > 0: controlKeys = append(controlKeys, "CapsLock") case cks&ENHANCED_KEY > 0: controlKeys = append(controlKeys, "Enhanced") case cks&LEFT_ALT_PRESSED > 0: controlKeys = append(controlKeys, "Alt") case cks&LEFT_CTRL_PRESSED > 0: controlKeys = append(controlKeys, "CTRL") case cks&NUMLOCK_ON > 0: controlKeys = append(controlKeys, "NumLock") case cks&RIGHT_ALT_PRESSED > 0: controlKeys = append(controlKeys, "RightAlt") case cks&RIGHT_CTRL_PRESSED > 0: controlKeys = append(controlKeys, "RightCTRL") case cks&SCROLLLOCK_ON > 0: controlKeys = append(controlKeys, "ScrollLock") case cks&SHIFT_PRESSED > 0: controlKeys = append(controlKeys, "Shift") case cks == NO_CONTROL_KEY: default: return fmt.Sprintf("Unknown(%d)", cks) } return strings.Join(controlKeys, ",") } // EventFlags specifies the type of a mouse event (see // https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str). type EventFlags uint32 // String ensures that EventFlags satisfies the fmt.Stringer interface. func (ef EventFlags) String() string { switch { case ef&DOUBLE_CLICK > 0: return "DoubleClick" case ef&MOUSE_WHEELED > 0: return "Wheeled" case ef&MOUSE_MOVED > 0: return "Moved" case ef&MOUSE_HWHEELED > 0: return "HWheeld" case ef == CLICK: return "Click" default: return fmt.Sprintf("Unknown(%d)", ef) } } func (ef EventFlags) Contains(flag EventFlags) bool { return ef&flag > 0 } // Valid values for EventFlags. const ( CLICK EventFlags = 0x0000 MOUSE_MOVED EventFlags = 0x0001 DOUBLE_CLICK EventFlags = 0x0002 MOUSE_WHEELED EventFlags = 0x0004 MOUSE_HWHEELED EventFlags = 0x0008 ) func highWord(data uint32) uint16 { return uint16((data & 0xFFFF0000) >> 16) }