okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Kieran Klukaschore: satisfy the newer gofumpt6d3a341

upstream
9.6 KiB336 linesraw
1//go:build windows
2// +build windows
3
4package uv
5
6import (
7	"bytes"
8	"context"
9	"fmt"
10	"strings"
11	"time"
12	"unicode/utf16"
13
14	"github.com/charmbracelet/x/ansi"
15	xwindows "github.com/charmbracelet/x/windows"
16	"github.com/muesli/cancelreader"
17	"golang.org/x/sys/windows"
18)
19
20// streamData sends data from the input stream to the event channel.
21func (d *TerminalReader) streamData(ctx context.Context, readc chan []byte) error {
22	cc, ok := d.r.(*conInputReader)
23	if !ok {
24		d.logf("streamData: reader is not a conInputReader, falling back to default implementation")
25		return d.sendBytes(ctx, readc)
26	}
27
28	// Store the value of VT Input Mode for later use.
29	d.vtInput = cc.newMode&windows.ENABLE_VIRTUAL_TERMINAL_INPUT != 0
30
31	var buf bytes.Buffer
32	var records []xwindows.InputRecord
33	var err error
34	for {
35		for {
36			records, err = peekNConsoleInputs(cc.conin, readBufSize)
37			if cc.isCanceled() {
38				return cancelreader.ErrCanceled
39			}
40			if err != nil {
41				return err
42			}
43			if len(records) > 0 {
44				break
45			}
46
47			// Sleep for a bit to avoid busy waiting.
48			time.Sleep(10 * time.Millisecond)
49		}
50
51		records, err = readNConsoleInputs(cc.conin, uint32(len(records))) //nolint:gosec
52		if cc.isCanceled() {
53			return cancelreader.ErrCanceled
54		}
55		if err != nil {
56			return err
57		}
58
59		// We convert Windows Input Records to VT input sequences for easier
60		// processing especially when dealing with UTF-16 decoding and
61		// Win32-Input-Mode processing.
62		d.serializeWin32InputRecords(records, &buf)
63
64		select {
65		case <-ctx.Done():
66			return nil
67		case readc <- buf.Bytes():
68		}
69
70		buf.Reset()
71	}
72}
73
74// serializeWin32InputRecords serializes the Win32 input events converting them
75// to valid VT input sequences. It will also encode any UTF-16 pairs that might
76// be present in the input buffer. The resulting byte slice can be sent to the
77// terminal as input.
78func (d *TerminalReader) serializeWin32InputRecords(records []xwindows.InputRecord, buf *bytes.Buffer) {
79	for _, record := range records {
80		switch record.EventType {
81		case xwindows.KEY_EVENT:
82			kevent := record.KeyEvent()
83			// d.logf("key event: %s", keyEventString(kevent.VirtualKeyCode, kevent.VirtualScanCode, kevent.Char, kevent.KeyDown, kevent.ControlKeyState, kevent.RepeatCount))
84
85			var kd int
86			if kevent.KeyDown {
87				kd = 1
88			}
89			if d.vtInput { //nolint:nestif
90				// In VT Input Mode, we only capture the Unicode characters
91				// decoding them along the way.
92				// This is similar to [TerminalReader.storeGraphemeRune] except
93				// that we need to write the events directly to the buffer.
94				if d.utf16Half[kd] {
95					// We have a half pair that needs to be decoded.
96					d.utf16Half[kd] = false
97					d.utf16Buf[kd][1] = kevent.Char
98					r := utf16.DecodeRune(d.utf16Buf[kd][0], d.utf16Buf[kd][1])
99					buf.WriteRune(r)
100				} else if utf16.IsSurrogate(kevent.Char) {
101					// This is the first half of a UTF-16 surrogate pair.
102					d.utf16Half[kd] = true
103					d.utf16Buf[kd][0] = kevent.Char
104				} else if kevent.KeyDown {
105					// Just a regular key press character encoded in VT.
106					buf.WriteRune(kevent.Char)
107				}
108			} else {
109				// We encode the key to Win32 Input Mode if it is a known key.
110				if kevent.VirtualKeyCode == 0 {
111					d.eventScanner.storeGraphemeRune(kd, kevent.Char)
112				} else {
113					buf.Write(d.eventScanner.encodeGraphemeBufs())
114					fmt.Fprintf(buf,
115						"\x1b[%d;%d;%d;%d;%d;%d_",
116						kevent.VirtualKeyCode,
117						kevent.VirtualScanCode,
118						kevent.Char,
119						kd,
120						kevent.ControlKeyState,
121						kevent.RepeatCount)
122				}
123			}
124
125		case xwindows.MOUSE_EVENT:
126			if d.MouseMode == nil || *d.MouseMode == 0 {
127				continue
128			}
129			mouseMode := *d.MouseMode
130			mevent := record.MouseEvent()
131
132			var isRelease bool
133			var isMotion bool
134			var button MouseButton
135			alt := mevent.ControlKeyState&(xwindows.LEFT_ALT_PRESSED|xwindows.RIGHT_ALT_PRESSED) != 0
136			ctrl := mevent.ControlKeyState&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_CTRL_PRESSED) != 0
137			shift := mevent.ControlKeyState&xwindows.SHIFT_PRESSED != 0
138			wheelDirection := int16(highWord(mevent.ButtonState)) //nolint:gosec
139			switch mevent.EventFlags {
140			case 0, xwindows.DOUBLE_CLICK:
141				button, isRelease = mouseEventButton(d.lastMouseBtns, mevent.ButtonState)
142			case xwindows.MOUSE_WHEELED:
143				if wheelDirection > 0 {
144					button = MouseWheelUp
145				} else {
146					button = MouseWheelDown
147				}
148			case xwindows.MOUSE_HWHEELED:
149				if wheelDirection > 0 {
150					button = MouseWheelRight
151				} else {
152					button = MouseWheelLeft
153				}
154			case xwindows.MOUSE_MOVED:
155				button, _ = mouseEventButton(d.lastMouseBtns, mevent.ButtonState)
156				isMotion = true
157			}
158
159			// We emulate mouse mode levels on Windows. This is because Windows
160			// doesn't have a concept of different mouse modes. We use the mouse mode to determine
161			if button == MouseNone && mouseMode&MouseModeMotion == 0 ||
162				(button != MouseNone && mouseMode&MouseModeDrag == 0) {
163				continue
164			}
165
166			// Encode mouse events as SGR mouse sequences that can be read by [EventDecoder].
167			buf.WriteString(ansi.MouseSgr(
168				ansi.EncodeMouseButton(button, isMotion, shift, alt, ctrl),
169				int(mevent.MousePositon.X), int(mevent.MousePositon.Y), isRelease,
170			))
171
172			d.lastMouseBtns = mevent.ButtonState
173
174		case xwindows.WINDOW_BUFFER_SIZE_EVENT:
175			wevent := record.WindowBufferSizeEvent()
176			if wevent.Size.X != d.lastWinsizeX || wevent.Size.Y != d.lastWinsizeY {
177				d.lastWinsizeX, d.lastWinsizeY = wevent.Size.X, wevent.Size.Y
178				// We encode window resize events as CSI 4 ; height ; width t
179				// sequence which the [EventDecoder] understands.
180				buf.WriteString(
181					ansi.WindowOp(
182						8,                  // Terminal window size in cells
183						int(wevent.Size.Y), // height
184						int(wevent.Size.X), // width
185					),
186				)
187			}
188
189		case xwindows.FOCUS_EVENT:
190			fevent := record.FocusEvent()
191			if fevent.SetFocus {
192				buf.WriteString(ansi.Focus)
193			} else {
194				buf.WriteString(ansi.Blur)
195			}
196
197		case xwindows.MENU_EVENT:
198			// ignore
199		}
200	}
201
202	// Flush any remaining grapheme buffers.
203	buf.Write(d.eventScanner.encodeGraphemeBufs())
204}
205
206func mouseEventButton(p, s uint32) (MouseButton, bool) {
207	var isRelease bool
208	button := MouseNone
209	btn := p ^ s
210	if btn&s == 0 {
211		isRelease = true
212	}
213
214	if btn == 0 {
215		switch {
216		case s&xwindows.FROM_LEFT_1ST_BUTTON_PRESSED > 0:
217			button = MouseLeft
218		case s&xwindows.FROM_LEFT_2ND_BUTTON_PRESSED > 0:
219			button = MouseMiddle
220		case s&xwindows.RIGHTMOST_BUTTON_PRESSED > 0:
221			button = MouseRight
222		case s&xwindows.FROM_LEFT_3RD_BUTTON_PRESSED > 0:
223			button = MouseBackward
224		case s&xwindows.FROM_LEFT_4TH_BUTTON_PRESSED > 0:
225			button = MouseForward
226		}
227		return button, isRelease
228	}
229
230	switch btn {
231	case xwindows.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
232		button = MouseLeft
233	case xwindows.RIGHTMOST_BUTTON_PRESSED: // right button
234		button = MouseRight
235	case xwindows.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
236		button = MouseMiddle
237	case xwindows.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
238		button = MouseBackward
239	case xwindows.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
240		button = MouseForward
241	}
242
243	return button, isRelease
244}
245
246func highWord(data uint32) uint16 {
247	return uint16((data & 0xFFFF0000) >> 16) //nolint:gosec
248}
249
250func readNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) {
251	if maxEvents == 0 {
252		return nil, fmt.Errorf("maxEvents cannot be zero")
253	}
254
255	records := make([]xwindows.InputRecord, maxEvents)
256	n, err := readConsoleInput(console, records)
257	return records[:n], err
258}
259
260func readConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
261	if len(inputRecords) == 0 {
262		return 0, fmt.Errorf("size of input record buffer cannot be zero")
263	}
264
265	var read uint32
266
267	err := xwindows.ReadConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec
268
269	return read, err //nolint:wrapcheck
270}
271
272func peekConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) {
273	if len(inputRecords) == 0 {
274		return 0, fmt.Errorf("size of input record buffer cannot be zero")
275	}
276
277	var read uint32
278
279	err := xwindows.PeekConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec
280
281	return read, err //nolint:wrapcheck
282}
283
284func peekNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) {
285	if maxEvents == 0 {
286		return nil, fmt.Errorf("maxEvents cannot be zero")
287	}
288
289	records := make([]xwindows.InputRecord, maxEvents)
290	n, err := peekConsoleInput(console, records)
291	return records[:n], err
292}
293
294//nolint:unused
295func keyEventString(vkc, sc uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) string {
296	var s strings.Builder
297	s.WriteString("vkc: ")
298	fmt.Fprintf(&s, "%d, 0x%02x", vkc, vkc)
299	s.WriteString(", sc: ")
300	fmt.Fprintf(&s, "%d, 0x%02x", sc, sc)
301	s.WriteString(", r: ")
302	fmt.Fprintf(&s, "%q 0x%x", r, r)
303	s.WriteString(", down: ")
304	fmt.Fprintf(&s, "%v", keyDown)
305	s.WriteString(", cks: [")
306	if cks&xwindows.LEFT_ALT_PRESSED != 0 {
307		s.WriteString("left alt, ")
308	}
309	if cks&xwindows.RIGHT_ALT_PRESSED != 0 {
310		s.WriteString("right alt, ")
311	}
312	if cks&xwindows.LEFT_CTRL_PRESSED != 0 {
313		s.WriteString("left ctrl, ")
314	}
315	if cks&xwindows.RIGHT_CTRL_PRESSED != 0 {
316		s.WriteString("right ctrl, ")
317	}
318	if cks&xwindows.SHIFT_PRESSED != 0 {
319		s.WriteString("shift, ")
320	}
321	if cks&xwindows.CAPSLOCK_ON != 0 {
322		s.WriteString("caps lock, ")
323	}
324	if cks&xwindows.NUMLOCK_ON != 0 {
325		s.WriteString("num lock, ")
326	}
327	if cks&xwindows.SCROLLLOCK_ON != 0 {
328		s.WriteString("scroll lock, ")
329	}
330	if cks&xwindows.ENHANCED_KEY != 0 {
331		s.WriteString("enhanced key, ")
332	}
333	s.WriteString("], repeat count: ")
334	fmt.Fprintf(&s, "%d", repeatCount)
335	return s.String()
336}