main
Raw Download raw file
  1package coninput
  2
  3import (
  4	"encoding/binary"
  5	"fmt"
  6	"strconv"
  7	"strings"
  8)
  9
 10const (
 11	maxEventSize     = 16
 12	wordPaddingBytes = 2
 13)
 14
 15// EventType denots the type of an event
 16type EventType uint16
 17
 18// EventUnion is the union data type that contains the data for any event.
 19type EventUnion [maxEventSize]byte
 20
 21// InputRecord corresponds to the INPUT_RECORD structure from the Windows
 22// console API (see
 23// https://docs.microsoft.com/en-us/windows/console/input-record-str).
 24type InputRecord struct {
 25	// EventType specifies the type of event that helt in Event.
 26	EventType EventType
 27
 28	// Padding of the 16-bit EventType to a whole 32-bit dword.
 29	_ [wordPaddingBytes]byte
 30
 31	// Event holds the actual event data. Use Unrap to access it as its
 32	// respective event type.
 33	Event EventUnion
 34}
 35
 36// String implements fmt.Stringer for InputRecord.
 37func (ir InputRecord) String() string {
 38	return ir.Unwrap().String()
 39}
 40
 41// Unwrap parses the event data into an EventRecord of the respective event
 42// type. The data in the returned EventRecord does not contain any references to
 43// the passed InputRecord.
 44func (ir InputRecord) Unwrap() EventRecord {
 45	switch ir.EventType {
 46	case FocusEventType:
 47		return FocusEventRecord{SetFocus: ir.Event[0] > 0}
 48	case KeyEventType:
 49		return KeyEventRecord{
 50			KeyDown:         binary.LittleEndian.Uint32(ir.Event[0:4]) > 0,
 51			RepeatCount:     binary.LittleEndian.Uint16(ir.Event[4:6]),
 52			VirtualKeyCode:  VirtualKeyCode(binary.LittleEndian.Uint16(ir.Event[6:8])),
 53			VirtualScanCode: VirtualKeyCode(binary.LittleEndian.Uint16(ir.Event[8:10])),
 54			Char:            rune(binary.LittleEndian.Uint16(ir.Event[10:12])),
 55			ControlKeyState: ControlKeyState(binary.LittleEndian.Uint32(ir.Event[12:16])),
 56		}
 57	case MouseEventType:
 58		m := MouseEventRecord{
 59			MousePositon: Coord{
 60				X: binary.LittleEndian.Uint16(ir.Event[0:2]),
 61				Y: binary.LittleEndian.Uint16(ir.Event[2:4]),
 62			},
 63			ButtonState:     ButtonState(binary.LittleEndian.Uint32(ir.Event[4:8])),
 64			ControlKeyState: ControlKeyState(binary.LittleEndian.Uint32(ir.Event[8:12])),
 65			EventFlags:      EventFlags(binary.LittleEndian.Uint32(ir.Event[12:16])),
 66		}
 67
 68		if (m.EventFlags&MOUSE_WHEELED > 0) || (m.EventFlags&MOUSE_HWHEELED > 0) {
 69			if int16(highWord(uint32(m.ButtonState))) > 0 {
 70				m.WheelDirection = 1
 71			} else {
 72				m.WheelDirection = -1
 73			}
 74		}
 75
 76		return m
 77	case WindowBufferSizeEventType:
 78		return WindowBufferSizeEventRecord{
 79			Size: Coord{
 80				X: binary.LittleEndian.Uint16(ir.Event[0:2]),
 81				Y: binary.LittleEndian.Uint16(ir.Event[2:4]),
 82			},
 83		}
 84	case MenuEventType:
 85		return MenuEventRecord{
 86			CommandID: binary.LittleEndian.Uint32(ir.Event[0:4]),
 87		}
 88	default:
 89		return &UnknownEvent{InputRecord: ir}
 90	}
 91}
 92
 93// EventRecord represents one of the following event types:
 94// TypeFocusEventRecord, TypeKeyEventRecord, TypeMouseEventRecord,
 95// TypeWindowBufferSizeEvent, TypeMenuEventRecord and UnknownEvent.
 96type EventRecord interface {
 97	Type() string
 98	fmt.Stringer
 99}
100
101// FocusEventType is the event type for a FocusEventRecord (see
102// https://docs.microsoft.com/en-us/windows/console/input-record-str).
103const FocusEventType EventType = 0x0010
104
105// FocusEventRecord represent the FOCUS_EVENT_RECORD structure from the Windows
106// console API (see
107// https://docs.microsoft.com/en-us/windows/console/focus-event-record-str).
108// These events are used internally by the Windows console API and should be
109// ignored.
110type FocusEventRecord struct {
111	// SetFocus is reserved and should not be used.
112	SetFocus bool
113}
114
115// Ensure that FocusEventRecord satisfies EventRecord interface.
116var _ EventRecord = FocusEventRecord{}
117
118// Type ensures that FocusEventRecord satisfies EventRecord interface.
119func (e FocusEventRecord) Type() string { return "FocusEvent" }
120
121// String ensures that FocusEventRecord satisfies EventRecord and fmt.Stringer
122// interfaces.
123func (e FocusEventRecord) String() string { return fmt.Sprintf("%s[%v]", e.Type(), e.SetFocus) }
124
125// KeyEventType is the event type for a KeyEventRecord (see
126// https://docs.microsoft.com/en-us/windows/console/input-record-str).
127const KeyEventType EventType = 0x0001
128
129// KeyEventRecord represent the KEY_EVENT_RECORD structure from the Windows
130// console API (see
131// https://docs.microsoft.com/en-us/windows/console/key-event-record-str).
132type KeyEventRecord struct {
133	// KeyDown specified whether the key is pressed or released.
134	KeyDown bool
135
136	//  RepeatCount indicates that a key is being held down. For example, when a
137	//  key is held down, five events with RepeatCount equal to 1 may be
138	//  generated, one event with RepeatCount equal to 5, or multiple events
139	//  with RepeatCount greater than or equal to 1.
140	RepeatCount uint16
141
142	// VirtualKeyCode identifies the given key in a device-independent manner
143	// (see
144	// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes).
145	VirtualKeyCode VirtualKeyCode
146
147	//  VirtualScanCode represents the device-dependent value generated by the
148	//  keyboard hardware.
149	VirtualScanCode VirtualKeyCode
150
151	// Char is the character that corresponds to the pressed key. Char can be
152	// zero for some keys.
153	Char rune
154
155	//ControlKeyState holds the state of the control keys.
156	ControlKeyState ControlKeyState
157}
158
159// Ensure that KeyEventRecord satisfies EventRecord interface.
160var _ EventRecord = KeyEventRecord{}
161
162// Type ensures that KeyEventRecord satisfies EventRecord interface.
163func (e KeyEventRecord) Type() string { return "KeyEvent" }
164
165// String ensures that KeyEventRecord satisfies EventRecord and fmt.Stringer
166// interfaces.
167func (e KeyEventRecord) String() string {
168	infos := []string{}
169
170	repeat := ""
171	if e.RepeatCount > 1 {
172		repeat = "x" + strconv.Itoa(int(e.RepeatCount))
173	}
174
175	infos = append(infos, fmt.Sprintf("%q%s", e.Char, repeat))
176
177	direction := "up"
178	if e.KeyDown {
179		direction = "down"
180	}
181
182	infos = append(infos, direction)
183
184	if e.ControlKeyState != NO_CONTROL_KEY {
185		infos = append(infos, e.ControlKeyState.String())
186	}
187
188	infos = append(infos, fmt.Sprintf("KeyCode: %d", e.VirtualKeyCode))
189	infos = append(infos, fmt.Sprintf("ScanCode: %d", e.VirtualScanCode))
190
191	return fmt.Sprintf("%s[%s]", e.Type(), strings.Join(infos, ", "))
192}
193
194// MenuEventType is the event type for a MenuEventRecord (see
195// https://docs.microsoft.com/en-us/windows/console/input-record-str).
196const MenuEventType EventType = 0x0008
197
198// MenuEventRecord represent the MENU_EVENT_RECORD structure from the Windows
199// console API (see
200// https://docs.microsoft.com/en-us/windows/console/menu-event-record-str).
201// These events are deprecated by the Windows console API and should be ignored.
202type MenuEventRecord struct {
203	CommandID uint32
204}
205
206// Ensure that MenuEventRecord satisfies EventRecord interface.
207var _ EventRecord = MenuEventRecord{}
208
209// Type ensures that MenuEventRecord satisfies EventRecord interface.
210func (e MenuEventRecord) Type() string { return "MenuEvent" }
211
212// String ensures that MenuEventRecord satisfies EventRecord and fmt.Stringer
213// interfaces.
214func (e MenuEventRecord) String() string { return fmt.Sprintf("MenuEvent[%d]", e.CommandID) }
215
216// MouseEventType is the event type for a MouseEventRecord (see
217// https://docs.microsoft.com/en-us/windows/console/input-record-str).
218const MouseEventType EventType = 0x0002
219
220// MouseEventRecord represent the MOUSE_EVENT_RECORD structure from the Windows
221// console API (see
222// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str).
223type MouseEventRecord struct {
224	// MousePosition contains the location of the cursor, in terms of the
225	// console screen buffer's character-cell coordinates.
226	MousePositon Coord
227
228	// ButtonState holds the status of the mouse buttons.
229	ButtonState ButtonState
230
231	// ControlKeyState holds the state of the control keys.
232	ControlKeyState ControlKeyState
233
234	// EventFlags specify tge type of mouse event.
235	EventFlags EventFlags
236
237	// WheelDirection specified the direction in which the mouse wheel is
238	// spinning when EventFlags contains MOUSE_HWHEELED or MOUSE_WHEELED. When
239	// the event flags specify MOUSE_WHEELED it is 1 if the wheel rotated
240	// forward (away from the user) or -1 when it rotates backwards. When
241	// MOUSE_HWHEELED is specified it is 1 when the wheel rotates right and -1
242	// when it rotates left. When the EventFlags do not indicate a mouse wheel
243	// event it is 0.
244	WheelDirection int
245}
246
247// Ensure that MouseEventRecord satisfies EventRecord interface.
248var _ EventRecord = MouseEventRecord{}
249
250func (e MouseEventRecord) WheelDirectionName() string {
251	if e.EventFlags&MOUSE_WHEELED > 0 {
252		if e.WheelDirection > 0 {
253			return "Forward"
254		}
255
256		return "Backward"
257	} else if e.EventFlags&MOUSE_HWHEELED > 0 {
258		if e.WheelDirection > 0 {
259			return "Right"
260		}
261
262		return "Left"
263	}
264
265	return ""
266}
267
268// Type ensures that MouseEventRecord satisfies EventRecord interface.
269func (e MouseEventRecord) Type() string { return "MouseEvent" }
270
271// String ensures that MouseEventRecord satisfies EventRecord and fmt.Stringer
272// interfaces.
273func (e MouseEventRecord) String() string {
274	infos := []string{e.MousePositon.String()}
275
276	if e.ButtonState&0xFF != 0 {
277		infos = append(infos, e.ButtonState.String())
278	}
279
280	eventDescription := e.EventFlags.String()
281
282	wheelDirection := e.WheelDirectionName()
283	if wheelDirection != "" {
284		eventDescription += "(" + wheelDirection + ")"
285	}
286
287	infos = append(infos, eventDescription)
288
289	if e.ControlKeyState != NO_CONTROL_KEY {
290		infos = append(infos, e.ControlKeyState.String())
291	}
292
293	return fmt.Sprintf("%s[%s]", e.Type(), strings.Join(infos, ", "))
294}
295
296// WindowBufferSizeEventType is the event type for a WindowBufferSizeEventRecord
297// (see https://docs.microsoft.com/en-us/windows/console/input-record-str).
298const WindowBufferSizeEventType EventType = 0x0004
299
300// WindowBufferSizeEventRecord represent the WINDOW_BUFFER_SIZE_RECORD structure
301// from the Windows console API (see
302// https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str).
303type WindowBufferSizeEventRecord struct {
304	// Size contains the size of the console screen buffer, in character cell columns and rows.
305	Size Coord
306}
307
308// Ensure that WindowBufferSizeEventRecord satisfies EventRecord interface.
309var _ EventRecord = WindowBufferSizeEventRecord{}
310
311// Type ensures that WindowBufferSizeEventRecord satisfies EventRecord interface.
312func (e WindowBufferSizeEventRecord) Type() string { return "WindowBufferSizeEvent" }
313
314// String ensures that WindowBufferSizeEventRecord satisfies EventRecord and fmt.Stringer
315// interfaces.
316func (e WindowBufferSizeEventRecord) String() string {
317	return fmt.Sprintf("WindowBufferSizeEvent[%s]", e.Size)
318}
319
320// UnknownEvent is generated when the event type does not match one of the
321// following types: TypeFocusEventRecord, TypeKeyEventRecord,
322// TypeMouseEventRecord, TypeWindowBufferSizeEvent, TypeMenuEventRecord and
323// UnknownEvent.
324type UnknownEvent struct {
325	InputRecord
326}
327
328// Ensure that UnknownEvent satisfies EventRecord interface.
329var _ EventRecord = UnknownEvent{}
330
331// Type ensures that UnknownEvent satisfies EventRecord interface.
332func (e UnknownEvent) Type() string { return "UnknownEvent" }
333
334// String ensures that UnknownEvent satisfies EventRecord and fmt.Stringer
335// interfaces.
336func (e UnknownEvent) String() string {
337	return fmt.Sprintf("%s[Type: %d, Data: %v]", e.Type(), e.InputRecord.EventType, e.InputRecord.Event[:])
338}
339
340// Coord represent the COORD structure from the Windows
341// console API (see https://docs.microsoft.com/en-us/windows/console/coord-str).
342type Coord struct {
343	// X is the horizontal coordinate or column value. The units depend on the function call.
344	X uint16
345	// Y is the vertical coordinate or row value. The units depend on the function call.
346	Y uint16
347}
348
349// String ensures that Coord satisfies the fmt.Stringer interface.
350func (c Coord) String() string {
351	return fmt.Sprintf("(%d, %d)", c.X, c.Y)
352}
353
354// ButtonState holds the state of the mouse buttons (see
355// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str).
356type ButtonState uint32
357
358func (bs ButtonState) Contains(state ButtonState) bool {
359	return bs&state > 0
360}
361
362// String ensures that ButtonState satisfies the fmt.Stringer interface.
363func (bs ButtonState) String() string {
364	switch {
365	case bs&FROM_LEFT_1ST_BUTTON_PRESSED > 0:
366		return "Left"
367	case bs&FROM_LEFT_2ND_BUTTON_PRESSED > 0:
368		return "2"
369	case bs&FROM_LEFT_3RD_BUTTON_PRESSED > 0:
370		return "3"
371	case bs&FROM_LEFT_4TH_BUTTON_PRESSED > 0:
372		return "4"
373	case bs&RIGHTMOST_BUTTON_PRESSED > 0:
374		return "Right"
375	case bs&0xFF == 0:
376		return "No Button"
377	default:
378		return fmt.Sprintf("Unknown(%d)", bs)
379	}
380}
381
382func (bs ButtonState) IsReleased() bool {
383	return bs&0xff > 0
384}
385
386// Valid values for ButtonState.
387const (
388	FROM_LEFT_1ST_BUTTON_PRESSED ButtonState = 0x0001
389	RIGHTMOST_BUTTON_PRESSED     ButtonState = 0x0002
390	FROM_LEFT_2ND_BUTTON_PRESSED ButtonState = 0x0004
391	FROM_LEFT_3RD_BUTTON_PRESSED ButtonState = 0x0008
392	FROM_LEFT_4TH_BUTTON_PRESSED ButtonState = 0x0010
393)
394
395// ControlKeyState holds the state of the control keys for key and mouse events
396// (see https://docs.microsoft.com/en-us/windows/console/key-event-record-str
397// and https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str).
398type ControlKeyState uint32
399
400func (cks ControlKeyState) Contains(state ControlKeyState) bool {
401	return cks&state > 0
402}
403
404// Valid values for ControlKeyState.
405const (
406	CAPSLOCK_ON        ControlKeyState = 0x0080
407	ENHANCED_KEY       ControlKeyState = 0x0100
408	LEFT_ALT_PRESSED   ControlKeyState = 0x0002
409	LEFT_CTRL_PRESSED  ControlKeyState = 0x0008
410	NUMLOCK_ON         ControlKeyState = 0x0020
411	RIGHT_ALT_PRESSED  ControlKeyState = 0x0001
412	RIGHT_CTRL_PRESSED ControlKeyState = 0x0004
413	SCROLLLOCK_ON      ControlKeyState = 0x0040
414	SHIFT_PRESSED      ControlKeyState = 0x0010
415	NO_CONTROL_KEY     ControlKeyState = 0x0000
416)
417
418// String ensures that ControlKeyState satisfies the fmt.Stringer interface.
419func (cks ControlKeyState) String() string {
420	controlKeys := []string{}
421
422	switch {
423	case cks&CAPSLOCK_ON > 0:
424		controlKeys = append(controlKeys, "CapsLock")
425	case cks&ENHANCED_KEY > 0:
426		controlKeys = append(controlKeys, "Enhanced")
427	case cks&LEFT_ALT_PRESSED > 0:
428		controlKeys = append(controlKeys, "Alt")
429	case cks&LEFT_CTRL_PRESSED > 0:
430		controlKeys = append(controlKeys, "CTRL")
431	case cks&NUMLOCK_ON > 0:
432		controlKeys = append(controlKeys, "NumLock")
433	case cks&RIGHT_ALT_PRESSED > 0:
434		controlKeys = append(controlKeys, "RightAlt")
435	case cks&RIGHT_CTRL_PRESSED > 0:
436		controlKeys = append(controlKeys, "RightCTRL")
437	case cks&SCROLLLOCK_ON > 0:
438		controlKeys = append(controlKeys, "ScrollLock")
439	case cks&SHIFT_PRESSED > 0:
440		controlKeys = append(controlKeys, "Shift")
441	case cks == NO_CONTROL_KEY:
442	default:
443		return fmt.Sprintf("Unknown(%d)", cks)
444	}
445
446	return strings.Join(controlKeys, ",")
447}
448
449// EventFlags specifies the type of a mouse event (see
450// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str).
451type EventFlags uint32
452
453// String ensures that EventFlags satisfies the fmt.Stringer interface.
454func (ef EventFlags) String() string {
455	switch {
456	case ef&DOUBLE_CLICK > 0:
457		return "DoubleClick"
458	case ef&MOUSE_WHEELED > 0:
459		return "Wheeled"
460	case ef&MOUSE_MOVED > 0:
461		return "Moved"
462	case ef&MOUSE_HWHEELED > 0:
463		return "HWheeld"
464	case ef == CLICK:
465		return "Click"
466	default:
467		return fmt.Sprintf("Unknown(%d)", ef)
468	}
469}
470
471func (ef EventFlags) Contains(flag EventFlags) bool {
472	return ef&flag > 0
473}
474
475// Valid values for EventFlags.
476const (
477	CLICK          EventFlags = 0x0000
478	MOUSE_MOVED    EventFlags = 0x0001
479	DOUBLE_CLICK   EventFlags = 0x0002
480	MOUSE_WHEELED  EventFlags = 0x0004
481	MOUSE_HWHEELED EventFlags = 0x0008
482)
483
484func highWord(data uint32) uint16 {
485	return uint16((data & 0xFFFF0000) >> 16)
486}