main
Raw Download raw file
  1//go:build windows
  2// +build windows
  3
  4package tea
  5
  6import (
  7	"context"
  8	"fmt"
  9	"io"
 10
 11	"github.com/erikgeiser/coninput"
 12	localereader "github.com/mattn/go-localereader"
 13	"github.com/muesli/cancelreader"
 14)
 15
 16func readInputs(ctx context.Context, msgs chan<- Msg, input io.Reader) error {
 17	if coninReader, ok := input.(*conInputReader); ok {
 18		return readConInputs(ctx, msgs, coninReader)
 19	}
 20
 21	return readAnsiInputs(ctx, msgs, localereader.NewReader(input))
 22}
 23
 24func readConInputs(ctx context.Context, msgsch chan<- Msg, con *conInputReader) error {
 25	var ps coninput.ButtonState                 // keep track of previous mouse state
 26	var ws coninput.WindowBufferSizeEventRecord // keep track of the last window size event
 27	for {
 28		events, err := coninput.ReadNConsoleInputs(con.conin, 16)
 29		if err != nil {
 30			if con.isCanceled() {
 31				return cancelreader.ErrCanceled
 32			}
 33			return fmt.Errorf("read coninput events: %w", err)
 34		}
 35
 36		for _, event := range events {
 37			var msgs []Msg
 38			switch e := event.Unwrap().(type) {
 39			case coninput.KeyEventRecord:
 40				if !e.KeyDown || e.VirtualKeyCode == coninput.VK_SHIFT {
 41					continue
 42				}
 43
 44				for i := 0; i < int(e.RepeatCount); i++ {
 45					eventKeyType := keyType(e)
 46					var runes []rune
 47
 48					// Add the character only if the key type is an actual character and not a control sequence.
 49					// This mimics the behavior in readAnsiInputs where the character is also removed.
 50					// We don't need to handle KeySpace here. See the comment in keyType().
 51					if eventKeyType == KeyRunes {
 52						runes = []rune{e.Char}
 53					}
 54
 55					msgs = append(msgs, KeyMsg{
 56						Type:  eventKeyType,
 57						Runes: runes,
 58						Alt:   e.ControlKeyState.Contains(coninput.LEFT_ALT_PRESSED | coninput.RIGHT_ALT_PRESSED),
 59					})
 60				}
 61			case coninput.WindowBufferSizeEventRecord:
 62				if e != ws {
 63					ws = e
 64					msgs = append(msgs, WindowSizeMsg{
 65						Width:  int(e.Size.X),
 66						Height: int(e.Size.Y),
 67					})
 68				}
 69			case coninput.MouseEventRecord:
 70				event := mouseEvent(ps, e)
 71				if event.Type != MouseUnknown {
 72					msgs = append(msgs, event)
 73				}
 74				ps = e.ButtonState
 75			case coninput.FocusEventRecord, coninput.MenuEventRecord:
 76				// ignore
 77			default: // unknown event
 78				continue
 79			}
 80
 81			// Send all messages to the channel
 82			for _, msg := range msgs {
 83				select {
 84				case msgsch <- msg:
 85				case <-ctx.Done():
 86					err := ctx.Err()
 87					if err != nil {
 88						return fmt.Errorf("coninput context error: %w", err)
 89					}
 90					return err
 91				}
 92			}
 93		}
 94	}
 95}
 96
 97func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, action MouseAction) {
 98	btn := p ^ s
 99	action = MouseActionPress
100	if btn&s == 0 {
101		action = MouseActionRelease
102	}
103
104	if btn == 0 {
105		switch {
106		case s&coninput.FROM_LEFT_1ST_BUTTON_PRESSED > 0:
107			button = MouseButtonLeft
108		case s&coninput.FROM_LEFT_2ND_BUTTON_PRESSED > 0:
109			button = MouseButtonMiddle
110		case s&coninput.RIGHTMOST_BUTTON_PRESSED > 0:
111			button = MouseButtonRight
112		case s&coninput.FROM_LEFT_3RD_BUTTON_PRESSED > 0:
113			button = MouseButtonBackward
114		case s&coninput.FROM_LEFT_4TH_BUTTON_PRESSED > 0:
115			button = MouseButtonForward
116		}
117		return
118	}
119
120	switch {
121	case btn == coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
122		button = MouseButtonLeft
123	case btn == coninput.RIGHTMOST_BUTTON_PRESSED: // right button
124		button = MouseButtonRight
125	case btn == coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
126		button = MouseButtonMiddle
127	case btn == coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
128		button = MouseButtonBackward
129	case btn == coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
130		button = MouseButtonForward
131	}
132
133	return button, action
134}
135
136func mouseEvent(p coninput.ButtonState, e coninput.MouseEventRecord) MouseMsg {
137	ev := MouseMsg{
138		X:     int(e.MousePositon.X),
139		Y:     int(e.MousePositon.Y),
140		Alt:   e.ControlKeyState.Contains(coninput.LEFT_ALT_PRESSED | coninput.RIGHT_ALT_PRESSED),
141		Ctrl:  e.ControlKeyState.Contains(coninput.LEFT_CTRL_PRESSED | coninput.RIGHT_CTRL_PRESSED),
142		Shift: e.ControlKeyState.Contains(coninput.SHIFT_PRESSED),
143	}
144	switch e.EventFlags {
145	case coninput.CLICK, coninput.DOUBLE_CLICK:
146		ev.Button, ev.Action = mouseEventButton(p, e.ButtonState)
147		if ev.Action == MouseActionRelease {
148			ev.Type = MouseRelease
149		}
150		switch ev.Button {
151		case MouseButtonLeft:
152			ev.Type = MouseLeft
153		case MouseButtonMiddle:
154			ev.Type = MouseMiddle
155		case MouseButtonRight:
156			ev.Type = MouseRight
157		case MouseButtonBackward:
158			ev.Type = MouseBackward
159		case MouseButtonForward:
160			ev.Type = MouseForward
161		}
162	case coninput.MOUSE_WHEELED:
163		if e.WheelDirection > 0 {
164			ev.Button = MouseButtonWheelUp
165			ev.Type = MouseWheelUp
166		} else {
167			ev.Button = MouseButtonWheelDown
168			ev.Type = MouseWheelDown
169		}
170	case coninput.MOUSE_HWHEELED:
171		if e.WheelDirection > 0 {
172			ev.Button = MouseButtonWheelRight
173			ev.Type = MouseWheelRight
174		} else {
175			ev.Button = MouseButtonWheelLeft
176			ev.Type = MouseWheelLeft
177		}
178	case coninput.MOUSE_MOVED:
179		ev.Button, _ = mouseEventButton(p, e.ButtonState)
180		ev.Action = MouseActionMotion
181		ev.Type = MouseMotion
182	}
183
184	return ev
185}
186
187func keyType(e coninput.KeyEventRecord) KeyType {
188	code := e.VirtualKeyCode
189
190	shiftPressed := e.ControlKeyState.Contains(coninput.SHIFT_PRESSED)
191	ctrlPressed := e.ControlKeyState.Contains(coninput.LEFT_CTRL_PRESSED | coninput.RIGHT_CTRL_PRESSED)
192
193	switch code {
194	case coninput.VK_RETURN:
195		return KeyEnter
196	case coninput.VK_BACK:
197		return KeyBackspace
198	case coninput.VK_TAB:
199		if shiftPressed {
200			return KeyShiftTab
201		}
202		return KeyTab
203	case coninput.VK_SPACE:
204		return KeyRunes // this could be KeySpace but on unix space also produces KeyRunes
205	case coninput.VK_ESCAPE:
206		return KeyEscape
207	case coninput.VK_UP:
208		switch {
209		case shiftPressed && ctrlPressed:
210			return KeyCtrlShiftUp
211		case shiftPressed:
212			return KeyShiftUp
213		case ctrlPressed:
214			return KeyCtrlUp
215		default:
216			return KeyUp
217		}
218	case coninput.VK_DOWN:
219		switch {
220		case shiftPressed && ctrlPressed:
221			return KeyCtrlShiftDown
222		case shiftPressed:
223			return KeyShiftDown
224		case ctrlPressed:
225			return KeyCtrlDown
226		default:
227			return KeyDown
228		}
229	case coninput.VK_RIGHT:
230		switch {
231		case shiftPressed && ctrlPressed:
232			return KeyCtrlShiftRight
233		case shiftPressed:
234			return KeyShiftRight
235		case ctrlPressed:
236			return KeyCtrlRight
237		default:
238			return KeyRight
239		}
240	case coninput.VK_LEFT:
241		switch {
242		case shiftPressed && ctrlPressed:
243			return KeyCtrlShiftLeft
244		case shiftPressed:
245			return KeyShiftLeft
246		case ctrlPressed:
247			return KeyCtrlLeft
248		default:
249			return KeyLeft
250		}
251	case coninput.VK_HOME:
252		switch {
253		case shiftPressed && ctrlPressed:
254			return KeyCtrlShiftHome
255		case shiftPressed:
256			return KeyShiftHome
257		case ctrlPressed:
258			return KeyCtrlHome
259		default:
260			return KeyHome
261		}
262	case coninput.VK_END:
263		switch {
264		case shiftPressed && ctrlPressed:
265			return KeyCtrlShiftEnd
266		case shiftPressed:
267			return KeyShiftEnd
268		case ctrlPressed:
269			return KeyCtrlEnd
270		default:
271			return KeyEnd
272		}
273	case coninput.VK_PRIOR:
274		return KeyPgUp
275	case coninput.VK_NEXT:
276		return KeyPgDown
277	case coninput.VK_DELETE:
278		return KeyDelete
279	default:
280		switch {
281		case e.ControlKeyState.Contains(coninput.LEFT_CTRL_PRESSED) && e.ControlKeyState.Contains(coninput.RIGHT_ALT_PRESSED):
282			// AltGr is pressed, then it's a rune.
283			fallthrough
284		case !e.ControlKeyState.Contains(coninput.LEFT_CTRL_PRESSED) && !e.ControlKeyState.Contains(coninput.RIGHT_CTRL_PRESSED):
285			return KeyRunes
286		}
287
288		switch e.Char {
289		case '@':
290			return KeyCtrlAt
291		case '\x01':
292			return KeyCtrlA
293		case '\x02':
294			return KeyCtrlB
295		case '\x03':
296			return KeyCtrlC
297		case '\x04':
298			return KeyCtrlD
299		case '\x05':
300			return KeyCtrlE
301		case '\x06':
302			return KeyCtrlF
303		case '\a':
304			return KeyCtrlG
305		case '\b':
306			return KeyCtrlH
307		case '\t':
308			return KeyCtrlI
309		case '\n':
310			return KeyCtrlJ
311		case '\v':
312			return KeyCtrlK
313		case '\f':
314			return KeyCtrlL
315		case '\r':
316			return KeyCtrlM
317		case '\x0e':
318			return KeyCtrlN
319		case '\x0f':
320			return KeyCtrlO
321		case '\x10':
322			return KeyCtrlP
323		case '\x11':
324			return KeyCtrlQ
325		case '\x12':
326			return KeyCtrlR
327		case '\x13':
328			return KeyCtrlS
329		case '\x14':
330			return KeyCtrlT
331		case '\x15':
332			return KeyCtrlU
333		case '\x16':
334			return KeyCtrlV
335		case '\x17':
336			return KeyCtrlW
337		case '\x18':
338			return KeyCtrlX
339		case '\x19':
340			return KeyCtrlY
341		case '\x1a':
342			return KeyCtrlZ
343		case '\x1b':
344			return KeyCtrlOpenBracket // KeyEscape
345		case '\x1c':
346			return KeyCtrlBackslash
347		case '\x1f':
348			return KeyCtrlUnderscore
349		}
350
351		switch code {
352		case coninput.VK_OEM_4:
353			return KeyCtrlOpenBracket
354		case coninput.VK_OEM_6:
355			return KeyCtrlCloseBracket
356		}
357
358		return KeyRunes
359	}
360}