Repository: go
Issue: proposal: x/sys/windows: console input event union member types
Labels: OS-Windows,Proposal

### Proposal Details

On Windows, reading console events can be achieved by either using [Console Virtual Terminal Sequences](https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences) similar to Unix terminals, or by using the [Windows Console API](https://learn.microsoft.com/en-us/windows/console/readconsoleinput) and [Buffer Events](https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events).

The former only supports basic key/mouse events (no release or unambiguous keys). Because Windows Consoles are not traditional TTYs, and they don't support `SIGWINCH` signals, we cannot listen to window resize events without polling the console.

Thus, it's more beneficial to work with the latter API when dealing with terminals and consoles on Windows. Using the Console API, we can listen for window resize events, and mouse/keyboard release and unambiguous events (such as <kbd>ctrl+i</kbd> vs <kbd>tab</kbd>).

The Windows Console API defines [input record](https://learn.microsoft.com/en-us/windows/console/input-record-str) events as a `union` type. And since Go doesn't support unions, we need a way to decode and access field members. The [proposed change](https://github.com/golang/sys/pull/228) ([CL 621496](https://go-review.googlesource.com/c/sys/+/621496)) is to use `encoding/binary` to decode the event into its respective type using member functions.

```c
typedef struct _INPUT_RECORD {
  WORD  EventType;
  union {
    KEY_EVENT_RECORD          KeyEvent;
    MOUSE_EVENT_RECORD        MouseEvent;
    WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
    MENU_EVENT_RECORD         MenuEvent;
    FOCUS_EVENT_RECORD        FocusEvent;
  } Event;
} INPUT_RECORD;
```

Becomes

```go

type InputRecord struct {
	EventType uint16
	_ [2]byte
	Event [16]byte
}

func (ir InputRecord) FocusEvent() FocusEventRecord {
	return FocusEventRecord{SetFocus: ir.Event[0] > 0}
}

func (ir InputRecord) KeyEvent() KeyEventRecord {
	return KeyEventRecord{
		KeyDown:         binary.LittleEndian.Uint32(ir.Event[0:4]) > 0,
		RepeatCount:     binary.LittleEndian.Uint16(ir.Event[4:6]),
		VirtualKeyCode:  binary.LittleEndian.Uint16(ir.Event[6:8]),
		VirtualScanCode: binary.LittleEndian.Uint16(ir.Event[8:10]),
		Char:            rune(binary.LittleEndian.Uint16(ir.Event[10:12])),
		ControlKeyState: binary.LittleEndian.Uint32(ir.Event[12:16]),
	}
}

func (ir InputRecord) MouseEvent() MouseEventRecord {
	return MouseEventRecord{
		MousePositon: Coord{
			X: int16(binary.LittleEndian.Uint16(ir.Event[0:2])),
			Y: int16(binary.LittleEndian.Uint16(ir.Event[2:4])),
		},
		ButtonState:     binary.LittleEndian.Uint32(ir.Event[4:8]),
		ControlKeyState: binary.LittleEndian.Uint32(ir.Event[8:12]),
		EventFlags:      binary.LittleEndian.Uint32(ir.Event[12:16]),
	}
}

func (ir InputRecord) WindowBufferSizeEvent() WindowBufferSizeRecord {
	return WindowBufferSizeRecord{
		Size: Coord{
			X: int16(binary.LittleEndian.Uint16(ir.Event[0:2])),
			Y: int16(binary.LittleEndian.Uint16(ir.Event[2:4])),
		},
	}
}

func (ir InputRecord) MenuEvent() MenuEventRecord {
	return MenuEventRecord{
		CommandID: binary.LittleEndian.Uint32(ir.Event[0:4]),
	}
}
```

Discussed in https://github.com/golang/sys/pull/196
Related https://github.com/golang/sys/pull/227
Related https://github.com/golang/sys/pull/228