1//go:build windows
2// +build windows
3
4package uv
5
6import (
7 "context"
8 "errors"
9 "fmt"
10 "strings"
11 "unicode"
12 "unicode/utf16"
13 "unicode/utf8"
14
15 "github.com/charmbracelet/x/ansi"
16 xwindows "github.com/charmbracelet/x/windows"
17 "github.com/muesli/cancelreader"
18 "golang.org/x/sys/windows"
19)
20
21// ReceiveEvents reads input events from the terminal and sends them to the
22// given event channel.
23func (d *TerminalReader) ReceiveEvents(ctx context.Context, events chan<- Event) error {
24 for {
25 evs, err := d.handleConInput(readConsoleInput)
26 if errors.Is(err, errNotConInputReader) {
27 return d.receiveEvents(ctx, events)
28 }
29 if err != nil {
30 return fmt.Errorf("read coninput events: %w", err)
31 }
32 for _, ev := range evs {
33 select {
34 case <-ctx.Done():
35 return nil
36 case events <- ev:
37 }
38 }
39 }
40}
41
42var errNotConInputReader = fmt.Errorf("handleConInput: not a conInputReader")
43
44func (d *TerminalReader) handleConInput(
45 finput func(windows.Handle, []xwindows.InputRecord) (uint32, error),
46) ([]Event, error) {
47 cc, ok := d.rd.(*conInputReader)
48 if !ok {
49 return nil, errNotConInputReader
50 }
51
52 // read up to 256 events, this is to allow for sequences events reported as
53 // key events.
54 var events [256]xwindows.InputRecord
55 _, err := finput(cc.conin, events[:])
56 if err != nil {
57 if cc.isCanceled() {
58 return nil, cancelreader.ErrCanceled
59 }
60 return nil, fmt.Errorf("read coninput events: %w", err)
61 }
62
63 var evs []Event
64 for _, event := range events {
65 if e := d.SequenceParser.parseConInputEvent(event, &d.keyState, d.MouseMode, d.logger); e != nil {
66 if e == nil {
67 continue
68 }
69 if multi, ok := e.(MultiEvent); ok {
70 if d.logger != nil {
71 for _, ev := range multi {
72 d.logf("input: %T %v", ev, ev)
73 }
74 }
75 evs = append(evs, multi...)
76 } else {
77 d.logf("input: %T %v", e, e)
78 evs = append(evs, e)
79 }
80 }
81 }
82
83 return evs, nil
84}
85
86func (p *SequenceParser) parseConInputEvent(event xwindows.InputRecord, keyState *win32InputState, mouseMode *MouseMode, logger Logger) Event {
87 switch event.EventType {
88 case xwindows.KEY_EVENT:
89 kevent := event.KeyEvent()
90 return p.parseWin32InputKeyEvent(keyState, kevent.VirtualKeyCode, kevent.VirtualScanCode,
91 kevent.Char, kevent.KeyDown, kevent.ControlKeyState, kevent.RepeatCount, logger)
92
93 case xwindows.WINDOW_BUFFER_SIZE_EVENT:
94 wevent := event.WindowBufferSizeEvent()
95 if wevent.Size.X != keyState.lastWinsizeX || wevent.Size.Y != keyState.lastWinsizeY {
96 keyState.lastWinsizeX, keyState.lastWinsizeY = wevent.Size.X, wevent.Size.Y
97 return WindowSizeEvent{
98 Width: int(wevent.Size.X),
99 Height: int(wevent.Size.Y),
100 }
101 }
102 case xwindows.MOUSE_EVENT:
103 if mouseMode == nil || *mouseMode == 0 {
104 return nil
105 }
106 mevent := event.MouseEvent()
107 event := mouseEvent(keyState.lastMouseBtns, mevent)
108 // We emulate mouse mode levels on Windows. This is because Windows
109 // doesn't have a concept of different mouse modes. We use the mouse mode to determine
110 switch m := event.(type) {
111 case MouseMotionEvent:
112 if m.Button == MouseNone && (*mouseMode)&AllMouseMode == 0 {
113 return nil
114 }
115 if m.Button != MouseNone && (*mouseMode)&DragMouseMode == 0 {
116 return nil
117 }
118 }
119 keyState.lastMouseBtns = mevent.ButtonState
120 return event
121 case xwindows.FOCUS_EVENT:
122 fevent := event.FocusEvent()
123 if fevent.SetFocus {
124 return FocusEvent{}
125 }
126 return BlurEvent{}
127 case xwindows.MENU_EVENT:
128 // ignore
129 }
130 return nil
131}
132
133func mouseEventButton(p, s uint32) (MouseButton, bool) {
134 var isRelease bool
135 button := MouseNone
136 btn := p ^ s
137 if btn&s == 0 {
138 isRelease = true
139 }
140
141 if btn == 0 {
142 switch {
143 case s&xwindows.FROM_LEFT_1ST_BUTTON_PRESSED > 0:
144 button = MouseLeft
145 case s&xwindows.FROM_LEFT_2ND_BUTTON_PRESSED > 0:
146 button = MouseMiddle
147 case s&xwindows.RIGHTMOST_BUTTON_PRESSED > 0:
148 button = MouseRight
149 case s&xwindows.FROM_LEFT_3RD_BUTTON_PRESSED > 0:
150 button = MouseBackward
151 case s&xwindows.FROM_LEFT_4TH_BUTTON_PRESSED > 0:
152 button = MouseForward
153 }
154 return button, isRelease
155 }
156
157 switch btn {
158 case xwindows.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
159 button = MouseLeft
160 case xwindows.RIGHTMOST_BUTTON_PRESSED: // right button
161 button = MouseRight
162 case xwindows.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
163 button = MouseMiddle
164 case xwindows.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
165 button = MouseBackward
166 case xwindows.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
167 button = MouseForward
168 }
169
170 return button, isRelease
171}
172
173func mouseEvent(p uint32, e xwindows.MouseEventRecord) (ev Event) {
174 var mod KeyMod
175 var isRelease bool
176 if e.ControlKeyState&(xwindows.LEFT_ALT_PRESSED|xwindows.RIGHT_ALT_PRESSED) != 0 {
177 mod |= ModAlt
178 }
179 if e.ControlKeyState&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_CTRL_PRESSED) != 0 {
180 mod |= ModCtrl
181 }
182 if e.ControlKeyState&(xwindows.SHIFT_PRESSED) != 0 {
183 mod |= ModShift
184 }
185
186 m := Mouse{
187 X: int(e.MousePositon.X),
188 Y: int(e.MousePositon.Y),
189 Mod: mod,
190 }
191
192 wheelDirection := int16(highWord(e.ButtonState)) //nolint:gosec
193 switch e.EventFlags {
194 case 0, xwindows.DOUBLE_CLICK:
195 m.Button, isRelease = mouseEventButton(p, e.ButtonState)
196 case xwindows.MOUSE_WHEELED:
197 if wheelDirection > 0 {
198 m.Button = MouseWheelUp
199 } else {
200 m.Button = MouseWheelDown
201 }
202 case xwindows.MOUSE_HWHEELED:
203 if wheelDirection > 0 {
204 m.Button = MouseWheelRight
205 } else {
206 m.Button = MouseWheelLeft
207 }
208 case xwindows.MOUSE_MOVED:
209 m.Button, _ = mouseEventButton(p, e.ButtonState)
210 return MouseMotionEvent(m)
211 }
212
213 if isWheel(m.Button) {
214 return MouseWheelEvent(m)
215 } else if isRelease {
216 return MouseReleaseEvent(m)
217 }
218
219 return MouseClickEvent(m)
220}
221
222func highWord(data uint32) uint16 {
223 return uint16((data & 0xFFFF0000) >> 16) //nolint:gosec
224}
225
226func readConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
227 if len(inputRecords) == 0 {
228 return 0, fmt.Errorf("size of input record buffer cannot be zero")
229 }
230
231 var read uint32
232
233 err := xwindows.ReadConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec
234
235 return read, err //nolint:wrapcheck
236}
237
238//nolint:unused
239func peekConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
240 if len(inputRecords) == 0 {
241 return 0, fmt.Errorf("size of input record buffer cannot be zero")
242 }
243
244 var read uint32
245
246 err := xwindows.PeekConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec
247
248 return read, err //nolint:wrapcheck
249}
250
251// parseWin32InputKeyEvent parses a single key event from either the Windows
252// Console API or win32-input-mode events. When state is nil, it means this is
253// an event from win32-input-mode. Otherwise, it's a key event from the Windows
254// Console API and needs a state to decode ANSI escape sequences and utf16
255// runes.
256func (p *SequenceParser) parseWin32InputKeyEvent(state *win32InputState, vkc uint16, _ uint16, r rune, keyDown bool, cks uint32, repeatCount uint16, logger Logger) (event Event) {
257 defer func() {
258 // Respect the repeat count.
259 if repeatCount > 1 {
260 var multi MultiEvent
261 for i := 0; i < int(repeatCount); i++ {
262 multi = append(multi, event)
263 }
264 event = multi
265 }
266 }()
267 if state != nil {
268 defer func() {
269 state.lastCks = cks
270 }()
271 }
272
273 var utf8Buf [utf8.UTFMax]byte
274 var key Key
275 if state != nil && state.utf16Half {
276 state.utf16Half = false
277 state.utf16Buf[1] = r
278 codepoint := utf16.DecodeRune(state.utf16Buf[0], state.utf16Buf[1])
279 rw := utf8.EncodeRune(utf8Buf[:], codepoint)
280 r, _ = utf8.DecodeRune(utf8Buf[:rw])
281 key.Code = r
282 key.Text = string(r)
283 key.Mod = translateControlKeyState(cks)
284 key = ensureKeyCase(key, cks)
285 if keyDown {
286 return KeyPressEvent(key)
287 }
288 return KeyReleaseEvent(key)
289 }
290
291 var baseCode rune
292 switch {
293 case vkc == 0:
294 // Zero means this event is either an escape code or a unicode
295 // codepoint.
296 if state != nil && state.ansiIdx == 0 && r != ansi.ESC {
297 if logger != nil {
298 logger.Printf("input: received unicode codepoint instead of sequence %q", r)
299 }
300 // This is a unicode codepoint.
301 baseCode = r
302 break
303 }
304
305 if state != nil {
306 // Collect ANSI escape code.
307 state.ansiBuf[state.ansiIdx] = byte(r)
308 state.ansiIdx++
309 if state.ansiIdx <= 2 {
310 // We haven't received enough bytes to determine if this is an
311 // ANSI escape code.
312 return nil
313 }
314 if r == ansi.ESC {
315 // We're expecting a closing String Terminator [ansi.ST].
316 return nil
317 }
318
319 n, event := p.parseSequence(state.ansiBuf[:state.ansiIdx])
320 if n == 0 {
321 return nil
322 }
323 if _, ok := event.(UnknownEvent); ok {
324 return nil
325 }
326
327 if logger != nil {
328 logger.Printf("input: parsed sequence %q, %d bytes", state.ansiBuf[:n], n)
329 }
330
331 state.ansiIdx = 0
332 return event
333 }
334 case vkc == xwindows.VK_BACK:
335 baseCode = KeyBackspace
336 case vkc == xwindows.VK_TAB:
337 baseCode = KeyTab
338 case vkc == xwindows.VK_RETURN:
339 baseCode = KeyEnter
340 case vkc == xwindows.VK_SHIFT:
341 //nolint:nestif
342 if cks&xwindows.SHIFT_PRESSED != 0 {
343 if cks&xwindows.ENHANCED_KEY != 0 {
344 baseCode = KeyRightShift
345 } else {
346 baseCode = KeyLeftShift
347 }
348 } else if state != nil {
349 if state.lastCks&xwindows.SHIFT_PRESSED != 0 {
350 if state.lastCks&xwindows.ENHANCED_KEY != 0 {
351 baseCode = KeyRightShift
352 } else {
353 baseCode = KeyLeftShift
354 }
355 }
356 }
357 case vkc == xwindows.VK_CONTROL:
358 if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
359 baseCode = KeyLeftCtrl
360 } else if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
361 baseCode = KeyRightCtrl
362 } else if state != nil {
363 if state.lastCks&xwindows.LEFT_CTRL_PRESSED != 0 {
364 baseCode = KeyLeftCtrl
365 } else if state.lastCks&xwindows.RIGHT_CTRL_PRESSED != 0 {
366 baseCode = KeyRightCtrl
367 }
368 }
369 case vkc == xwindows.VK_MENU:
370 if cks&xwindows.LEFT_ALT_PRESSED != 0 {
371 baseCode = KeyLeftAlt
372 } else if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
373 baseCode = KeyRightAlt
374 } else if state != nil {
375 if state.lastCks&xwindows.LEFT_ALT_PRESSED != 0 {
376 baseCode = KeyLeftAlt
377 } else if state.lastCks&xwindows.RIGHT_ALT_PRESSED != 0 {
378 baseCode = KeyRightAlt
379 }
380 }
381 case vkc == xwindows.VK_PAUSE:
382 baseCode = KeyPause
383 case vkc == xwindows.VK_CAPITAL:
384 baseCode = KeyCapsLock
385 case vkc == xwindows.VK_ESCAPE:
386 baseCode = KeyEscape
387 case vkc == xwindows.VK_SPACE:
388 baseCode = KeySpace
389 case vkc == xwindows.VK_PRIOR:
390 baseCode = KeyPgUp
391 case vkc == xwindows.VK_NEXT:
392 baseCode = KeyPgDown
393 case vkc == xwindows.VK_END:
394 baseCode = KeyEnd
395 case vkc == xwindows.VK_HOME:
396 baseCode = KeyHome
397 case vkc == xwindows.VK_LEFT:
398 baseCode = KeyLeft
399 case vkc == xwindows.VK_UP:
400 baseCode = KeyUp
401 case vkc == xwindows.VK_RIGHT:
402 baseCode = KeyRight
403 case vkc == xwindows.VK_DOWN:
404 baseCode = KeyDown
405 case vkc == xwindows.VK_SELECT:
406 baseCode = KeySelect
407 case vkc == xwindows.VK_SNAPSHOT:
408 baseCode = KeyPrintScreen
409 case vkc == xwindows.VK_INSERT:
410 baseCode = KeyInsert
411 case vkc == xwindows.VK_DELETE:
412 baseCode = KeyDelete
413 case vkc >= '0' && vkc <= '9':
414 baseCode = rune(vkc)
415 case vkc >= 'A' && vkc <= 'Z':
416 // Convert to lowercase.
417 baseCode = rune(vkc) + 32
418 case vkc == xwindows.VK_LWIN:
419 baseCode = KeyLeftSuper
420 case vkc == xwindows.VK_RWIN:
421 baseCode = KeyRightSuper
422 case vkc == xwindows.VK_APPS:
423 baseCode = KeyMenu
424 case vkc >= xwindows.VK_NUMPAD0 && vkc <= xwindows.VK_NUMPAD9:
425 baseCode = rune(vkc-xwindows.VK_NUMPAD0) + KeyKp0
426 case vkc == xwindows.VK_MULTIPLY:
427 baseCode = KeyKpMultiply
428 case vkc == xwindows.VK_ADD:
429 baseCode = KeyKpPlus
430 case vkc == xwindows.VK_SEPARATOR:
431 baseCode = KeyKpComma
432 case vkc == xwindows.VK_SUBTRACT:
433 baseCode = KeyKpMinus
434 case vkc == xwindows.VK_DECIMAL:
435 baseCode = KeyKpDecimal
436 case vkc == xwindows.VK_DIVIDE:
437 baseCode = KeyKpDivide
438 case vkc >= xwindows.VK_F1 && vkc <= xwindows.VK_F24:
439 baseCode = rune(vkc-xwindows.VK_F1) + KeyF1
440 case vkc == xwindows.VK_NUMLOCK:
441 baseCode = KeyNumLock
442 case vkc == xwindows.VK_SCROLL:
443 baseCode = KeyScrollLock
444 case vkc == xwindows.VK_LSHIFT:
445 baseCode = KeyLeftShift
446 case vkc == xwindows.VK_RSHIFT:
447 baseCode = KeyRightShift
448 case vkc == xwindows.VK_LCONTROL:
449 baseCode = KeyLeftCtrl
450 case vkc == xwindows.VK_RCONTROL:
451 baseCode = KeyRightCtrl
452 case vkc == xwindows.VK_LMENU:
453 baseCode = KeyLeftAlt
454 case vkc == xwindows.VK_RMENU:
455 baseCode = KeyRightAlt
456 case vkc == xwindows.VK_VOLUME_MUTE:
457 baseCode = KeyMute
458 case vkc == xwindows.VK_VOLUME_DOWN:
459 baseCode = KeyLowerVol
460 case vkc == xwindows.VK_VOLUME_UP:
461 baseCode = KeyRaiseVol
462 case vkc == xwindows.VK_MEDIA_NEXT_TRACK:
463 baseCode = KeyMediaNext
464 case vkc == xwindows.VK_MEDIA_PREV_TRACK:
465 baseCode = KeyMediaPrev
466 case vkc == xwindows.VK_MEDIA_STOP:
467 baseCode = KeyMediaStop
468 case vkc == xwindows.VK_MEDIA_PLAY_PAUSE:
469 baseCode = KeyMediaPlayPause
470 case vkc == xwindows.VK_OEM_1:
471 baseCode = ';'
472 case vkc == xwindows.VK_OEM_PLUS:
473 baseCode = '+'
474 case vkc == xwindows.VK_OEM_COMMA:
475 baseCode = ','
476 case vkc == xwindows.VK_OEM_MINUS:
477 baseCode = '-'
478 case vkc == xwindows.VK_OEM_PERIOD:
479 baseCode = '.'
480 case vkc == xwindows.VK_OEM_2:
481 baseCode = '/'
482 case vkc == xwindows.VK_OEM_3:
483 baseCode = '`'
484 case vkc == xwindows.VK_OEM_4:
485 baseCode = '['
486 case vkc == xwindows.VK_OEM_5:
487 baseCode = '\\'
488 case vkc == xwindows.VK_OEM_6:
489 baseCode = ']'
490 case vkc == xwindows.VK_OEM_7:
491 baseCode = '\''
492 }
493
494 if utf16.IsSurrogate(r) {
495 if state != nil {
496 state.utf16Buf[0] = r
497 state.utf16Half = true
498 }
499 return nil
500 }
501
502 // AltGr is left ctrl + right alt. On non-US keyboards, this is used to type
503 // special characters and produce printable events.
504 // XXX: Should this be a KeyMod?
505 altGr := cks&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_ALT_PRESSED) == xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_ALT_PRESSED
506
507 var text string
508 keyCode := baseCode
509 if !unicode.IsControl(r) {
510 rw := utf8.EncodeRune(utf8Buf[:], r)
511 keyCode, _ = utf8.DecodeRune(utf8Buf[:rw])
512 if unicode.IsPrint(keyCode) && (cks == 0 ||
513 cks == xwindows.SHIFT_PRESSED ||
514 cks == xwindows.CAPSLOCK_ON ||
515 altGr) {
516 // If the control key state is 0, shift is pressed, or caps lock
517 // then the key event is a printable event i.e. [text] is not empty.
518 text = string(keyCode)
519 }
520 }
521
522 key.Code = keyCode
523 key.Text = text
524 key.Mod = translateControlKeyState(cks)
525 key.BaseCode = baseCode
526 key = ensureKeyCase(key, cks)
527 if keyDown {
528 return KeyPressEvent(key)
529 }
530
531 return KeyReleaseEvent(key)
532}
533
534// ensureKeyCase ensures that the key's text is in the correct case based on the
535// control key state.
536func ensureKeyCase(key Key, cks uint32) Key {
537 if len(key.Text) == 0 {
538 return key
539 }
540
541 hasShift := cks&xwindows.SHIFT_PRESSED != 0
542 hasCaps := cks&xwindows.CAPSLOCK_ON != 0
543 if hasShift || hasCaps {
544 if unicode.IsLower(key.Code) {
545 key.ShiftedCode = unicode.ToUpper(key.Code)
546 key.Text = string(key.ShiftedCode)
547 }
548 } else {
549 if unicode.IsUpper(key.Code) {
550 key.ShiftedCode = unicode.ToLower(key.Code)
551 key.Text = string(key.ShiftedCode)
552 }
553 }
554
555 return key
556}
557
558// translateControlKeyState translates the control key state from the Windows
559// Console API into a Mod bitmask.
560func translateControlKeyState(cks uint32) (m KeyMod) {
561 if cks&xwindows.LEFT_CTRL_PRESSED != 0 || cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
562 m |= ModCtrl
563 }
564 if cks&xwindows.LEFT_ALT_PRESSED != 0 || cks&xwindows.RIGHT_ALT_PRESSED != 0 {
565 m |= ModAlt
566 }
567 if cks&xwindows.SHIFT_PRESSED != 0 {
568 m |= ModShift
569 }
570 if cks&xwindows.CAPSLOCK_ON != 0 {
571 m |= ModCapsLock
572 }
573 if cks&xwindows.NUMLOCK_ON != 0 {
574 m |= ModNumLock
575 }
576 if cks&xwindows.SCROLLLOCK_ON != 0 {
577 m |= ModScrollLock
578 }
579 return
580}
581
582//nolint:unused
583func keyEventString(vkc, sc uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) string {
584 var s strings.Builder
585 s.WriteString("vkc: ")
586 s.WriteString(fmt.Sprintf("%d, 0x%02x", vkc, vkc))
587 s.WriteString(", sc: ")
588 s.WriteString(fmt.Sprintf("%d, 0x%02x", sc, sc))
589 s.WriteString(", r: ")
590 s.WriteString(fmt.Sprintf("%q", r))
591 s.WriteString(", down: ")
592 s.WriteString(fmt.Sprintf("%v", keyDown))
593 s.WriteString(", cks: [")
594 if cks&xwindows.LEFT_ALT_PRESSED != 0 {
595 s.WriteString("left alt, ")
596 }
597 if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
598 s.WriteString("right alt, ")
599 }
600 if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
601 s.WriteString("left ctrl, ")
602 }
603 if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
604 s.WriteString("right ctrl, ")
605 }
606 if cks&xwindows.SHIFT_PRESSED != 0 {
607 s.WriteString("shift, ")
608 }
609 if cks&xwindows.CAPSLOCK_ON != 0 {
610 s.WriteString("caps lock, ")
611 }
612 if cks&xwindows.NUMLOCK_ON != 0 {
613 s.WriteString("num lock, ")
614 }
615 if cks&xwindows.SCROLLLOCK_ON != 0 {
616 s.WriteString("scroll lock, ")
617 }
618 if cks&xwindows.ENHANCED_KEY != 0 {
619 s.WriteString("enhanced key, ")
620 }
621 s.WriteString("], repeat count: ")
622 s.WriteString(fmt.Sprintf("%d", repeatCount))
623 return s.String()
624}