main
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}