File: runereader_windows.go

package info (click to toggle)
golang-github-alecaivazis-survey 2.3.6%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bookworm-backports
  • size: 636 kB
  • sloc: makefile: 12
file content (142 lines) | stat: -rw-r--r-- 3,496 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package terminal

import (
	"bytes"
	"syscall"
	"unsafe"
)

var (
	dll              = syscall.NewLazyDLL("kernel32.dll")
	setConsoleMode   = dll.NewProc("SetConsoleMode")
	getConsoleMode   = dll.NewProc("GetConsoleMode")
	readConsoleInput = dll.NewProc("ReadConsoleInputW")
)

const (
	EVENT_KEY = 0x0001

	// key codes for arrow keys
	// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
	VK_DELETE = 0x2E
	VK_END    = 0x23
	VK_HOME   = 0x24
	VK_LEFT   = 0x25
	VK_UP     = 0x26
	VK_RIGHT  = 0x27
	VK_DOWN   = 0x28

	RIGHT_CTRL_PRESSED = 0x0004
	LEFT_CTRL_PRESSED  = 0x0008

	ENABLE_ECHO_INPUT      uint32 = 0x0004
	ENABLE_LINE_INPUT      uint32 = 0x0002
	ENABLE_PROCESSED_INPUT uint32 = 0x0001
)

type inputRecord struct {
	eventType uint16
	padding   uint16
	event     [16]byte
}

type keyEventRecord struct {
	bKeyDown          int32
	wRepeatCount      uint16
	wVirtualKeyCode   uint16
	wVirtualScanCode  uint16
	unicodeChar       uint16
	wdControlKeyState uint32
}

type runeReaderState struct {
	term uint32
}

func newRuneReaderState(input FileReader) runeReaderState {
	return runeReaderState{}
}

func (rr *RuneReader) Buffer() *bytes.Buffer {
	return nil
}

func (rr *RuneReader) SetTermMode() error {
	r, _, err := getConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(unsafe.Pointer(&rr.state.term)))
	// windows return 0 on error
	if r == 0 {
		return err
	}

	newState := rr.state.term
	newState &^= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT
	r, _, err = setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(newState))
	// windows return 0 on error
	if r == 0 {
		return err
	}
	return nil
}

func (rr *RuneReader) RestoreTermMode() error {
	r, _, err := setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(rr.state.term))
	// windows return 0 on error
	if r == 0 {
		return err
	}
	return nil
}

func (rr *RuneReader) ReadRune() (rune, int, error) {
	ir := &inputRecord{}
	bytesRead := 0
	for {
		rv, _, e := readConsoleInput.Call(rr.stdio.In.Fd(), uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&bytesRead)))
		// windows returns non-zero to indicate success
		if rv == 0 && e != nil {
			return 0, 0, e
		}

		if ir.eventType != EVENT_KEY {
			continue
		}

		// the event data is really a c struct union, so here we have to do an usafe
		// cast to put the data into the keyEventRecord (since we have already verified
		// above that this event does correspond to a key event
		key := (*keyEventRecord)(unsafe.Pointer(&ir.event[0]))
		// we only care about key down events
		if key.bKeyDown == 0 {
			continue
		}
		if key.wdControlKeyState&(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED) != 0 && key.unicodeChar == 'C' {
			return KeyInterrupt, bytesRead, nil
		}
		// not a normal character so look up the input sequence from the
		// virtual key code mappings (VK_*)
		if key.unicodeChar == 0 {
			switch key.wVirtualKeyCode {
			case VK_DOWN:
				return KeyArrowDown, bytesRead, nil
			case VK_LEFT:
				return KeyArrowLeft, bytesRead, nil
			case VK_RIGHT:
				return KeyArrowRight, bytesRead, nil
			case VK_UP:
				return KeyArrowUp, bytesRead, nil
			case VK_DELETE:
				return SpecialKeyDelete, bytesRead, nil
			case VK_HOME:
				return SpecialKeyHome, bytesRead, nil
			case VK_END:
				return SpecialKeyEnd, bytesRead, nil
			default:
				// not a virtual key that we care about so just continue on to
				// the next input key
				continue
			}
		}
		r := rune(key.unicodeChar)
		return r, bytesRead, nil
	}
}