1package liner
2
3import (
4	"bufio"
5	"os"
6	"syscall"
7	"unsafe"
8)
9
10var (
11	kernel32 = syscall.NewLazyDLL("kernel32.dll")
12
13	procGetStdHandle                  = kernel32.NewProc("GetStdHandle")
14	procReadConsoleInput              = kernel32.NewProc("ReadConsoleInputW")
15	procGetNumberOfConsoleInputEvents = kernel32.NewProc("GetNumberOfConsoleInputEvents")
16	procGetConsoleMode                = kernel32.NewProc("GetConsoleMode")
17	procSetConsoleMode                = kernel32.NewProc("SetConsoleMode")
18	procSetConsoleCursorPosition      = kernel32.NewProc("SetConsoleCursorPosition")
19	procGetConsoleScreenBufferInfo    = kernel32.NewProc("GetConsoleScreenBufferInfo")
20	procFillConsoleOutputCharacter    = kernel32.NewProc("FillConsoleOutputCharacterW")
21)
22
23// These names are from the Win32 api, so they use underscores (contrary to
24// what golint suggests)
25const (
26	std_input_handle     = uint32(-10 & 0xFFFFFFFF)
27	std_output_handle    = uint32(-11 & 0xFFFFFFFF)
28	std_error_handle     = uint32(-12 & 0xFFFFFFFF)
29	invalid_handle_value = ^uintptr(0)
30)
31
32type inputMode uint32
33
34// State represents an open terminal
35type State struct {
36	commonState
37	handle      syscall.Handle
38	hOut        syscall.Handle
39	origMode    inputMode
40	defaultMode inputMode
41	key         interface{}
42	repeat      uint16
43}
44
45const (
46	enableEchoInput      = 0x4
47	enableInsertMode     = 0x20
48	enableLineInput      = 0x2
49	enableMouseInput     = 0x10
50	enableProcessedInput = 0x1
51	enableQuickEditMode  = 0x40
52	enableWindowInput    = 0x8
53)
54
55// NewLiner initializes a new *State, and sets the terminal into raw mode. To
56// restore the terminal to its previous state, call State.Close().
57func NewLiner() *State {
58	var s State
59	hIn, _, _ := procGetStdHandle.Call(uintptr(std_input_handle))
60	s.handle = syscall.Handle(hIn)
61	hOut, _, _ := procGetStdHandle.Call(uintptr(std_output_handle))
62	s.hOut = syscall.Handle(hOut)
63
64	s.terminalSupported = true
65	if m, err := TerminalMode(); err == nil {
66		s.origMode = m.(inputMode)
67		mode := s.origMode
68		mode &^= enableEchoInput
69		mode &^= enableInsertMode
70		mode &^= enableLineInput
71		mode &^= enableMouseInput
72		mode |= enableWindowInput
73		mode.ApplyMode()
74	} else {
75		s.inputRedirected = true
76		s.r = bufio.NewReader(os.Stdin)
77	}
78
79	s.getColumns()
80	s.outputRedirected = s.columns <= 0
81
82	return &s
83}
84
85// These names are from the Win32 api, so they use underscores (contrary to
86// what golint suggests)
87const (
88	focus_event              = 0x0010
89	key_event                = 0x0001
90	menu_event               = 0x0008
91	mouse_event              = 0x0002
92	window_buffer_size_event = 0x0004
93)
94
95type input_record struct {
96	eventType uint16
97	pad       uint16
98	blob      [16]byte
99}
100
101type key_event_record struct {
102	KeyDown         int32
103	RepeatCount     uint16
104	VirtualKeyCode  uint16
105	VirtualScanCode uint16
106	Char            int16
107	ControlKeyState uint32
108}
109
110// These names are from the Win32 api, so they use underscores (contrary to
111// what golint suggests)
112const (
113	vk_tab    = 0x09
114	vk_prior  = 0x21
115	vk_next   = 0x22
116	vk_end    = 0x23
117	vk_home   = 0x24
118	vk_left   = 0x25
119	vk_up     = 0x26
120	vk_right  = 0x27
121	vk_down   = 0x28
122	vk_insert = 0x2d
123	vk_delete = 0x2e
124	vk_f1     = 0x70
125	vk_f2     = 0x71
126	vk_f3     = 0x72
127	vk_f4     = 0x73
128	vk_f5     = 0x74
129	vk_f6     = 0x75
130	vk_f7     = 0x76
131	vk_f8     = 0x77
132	vk_f9     = 0x78
133	vk_f10    = 0x79
134	vk_f11    = 0x7a
135	vk_f12    = 0x7b
136	bKey      = 0x42
137	fKey      = 0x46
138	yKey      = 0x59
139)
140
141const (
142	shiftPressed     = 0x0010
143	leftAltPressed   = 0x0002
144	leftCtrlPressed  = 0x0008
145	rightAltPressed  = 0x0001
146	rightCtrlPressed = 0x0004
147
148	modKeys = shiftPressed | leftAltPressed | rightAltPressed | leftCtrlPressed | rightCtrlPressed
149)
150
151// inputWaiting only returns true if the next call to readNext will return immediately.
152func (s *State) inputWaiting() bool {
153	var num uint32
154	ok, _, _ := procGetNumberOfConsoleInputEvents.Call(uintptr(s.handle), uintptr(unsafe.Pointer(&num)))
155	if ok == 0 {
156		// call failed, so we cannot guarantee a non-blocking readNext
157		return false
158	}
159
160	// during a "paste" input events are always an odd number, and
161	// the last one results in a blocking readNext, so return false
162	// when num is 1 or 0.
163	return num > 1
164}
165
166func (s *State) readNext() (interface{}, error) {
167	if s.repeat > 0 {
168		s.repeat--
169		return s.key, nil
170	}
171
172	var input input_record
173	pbuf := uintptr(unsafe.Pointer(&input))
174	var rv uint32
175	prv := uintptr(unsafe.Pointer(&rv))
176
177	for {
178		ok, _, err := procReadConsoleInput.Call(uintptr(s.handle), pbuf, 1, prv)
179
180		if ok == 0 {
181			return nil, err
182		}
183
184		if input.eventType == window_buffer_size_event {
185			xy := (*coord)(unsafe.Pointer(&input.blob[0]))
186			s.columns = int(xy.x)
187			if s.columns > 1 {
188				s.columns--
189			}
190			return winch, nil
191		}
192		if input.eventType != key_event {
193			continue
194		}
195		ke := (*key_event_record)(unsafe.Pointer(&input.blob[0]))
196		if ke.KeyDown == 0 {
197			continue
198		}
199
200		if ke.VirtualKeyCode == vk_tab && ke.ControlKeyState&modKeys == shiftPressed {
201			s.key = shiftTab
202		} else if ke.VirtualKeyCode == bKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
203			ke.ControlKeyState&modKeys == rightAltPressed) {
204			s.key = altB
205		} else if ke.VirtualKeyCode == fKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
206			ke.ControlKeyState&modKeys == rightAltPressed) {
207			s.key = altF
208		} else if ke.VirtualKeyCode == yKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
209			ke.ControlKeyState&modKeys == rightAltPressed) {
210			s.key = altY
211		} else if ke.Char > 0 {
212			s.key = rune(ke.Char)
213		} else {
214			switch ke.VirtualKeyCode {
215			case vk_prior:
216				s.key = pageUp
217			case vk_next:
218				s.key = pageDown
219			case vk_end:
220				s.key = end
221			case vk_home:
222				s.key = home
223			case vk_left:
224				s.key = left
225				if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 {
226					if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) {
227						s.key = wordLeft
228					}
229				}
230			case vk_right:
231				s.key = right
232				if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 {
233					if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) {
234						s.key = wordRight
235					}
236				}
237			case vk_up:
238				s.key = up
239			case vk_down:
240				s.key = down
241			case vk_insert:
242				s.key = insert
243			case vk_delete:
244				s.key = del
245			case vk_f1:
246				s.key = f1
247			case vk_f2:
248				s.key = f2
249			case vk_f3:
250				s.key = f3
251			case vk_f4:
252				s.key = f4
253			case vk_f5:
254				s.key = f5
255			case vk_f6:
256				s.key = f6
257			case vk_f7:
258				s.key = f7
259			case vk_f8:
260				s.key = f8
261			case vk_f9:
262				s.key = f9
263			case vk_f10:
264				s.key = f10
265			case vk_f11:
266				s.key = f11
267			case vk_f12:
268				s.key = f12
269			default:
270				// Eat modifier keys
271				// TODO: return Action(Unknown) if the key isn't a
272				// modifier.
273				continue
274			}
275		}
276
277		if ke.RepeatCount > 1 {
278			s.repeat = ke.RepeatCount - 1
279		}
280		return s.key, nil
281	}
282}
283
284// Close returns the terminal to its previous mode
285func (s *State) Close() error {
286	s.origMode.ApplyMode()
287	return nil
288}
289
290func (s *State) startPrompt() {
291	if m, err := TerminalMode(); err == nil {
292		s.defaultMode = m.(inputMode)
293		mode := s.defaultMode
294		mode &^= enableProcessedInput
295		mode.ApplyMode()
296	}
297}
298
299func (s *State) restartPrompt() {
300}
301
302func (s *State) stopPrompt() {
303	s.defaultMode.ApplyMode()
304}
305
306// TerminalSupported returns true because line editing is always
307// supported on Windows.
308func TerminalSupported() bool {
309	return true
310}
311
312func (mode inputMode) ApplyMode() error {
313	hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle))
314	if hIn == invalid_handle_value || hIn == 0 {
315		return err
316	}
317	ok, _, err := procSetConsoleMode.Call(hIn, uintptr(mode))
318	if ok != 0 {
319		err = nil
320	}
321	return err
322}
323
324// TerminalMode returns the current terminal input mode as an InputModeSetter.
325//
326// This function is provided for convenience, and should
327// not be necessary for most users of liner.
328func TerminalMode() (ModeApplier, error) {
329	var mode inputMode
330	hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle))
331	if hIn == invalid_handle_value || hIn == 0 {
332		return nil, err
333	}
334	ok, _, err := procGetConsoleMode.Call(hIn, uintptr(unsafe.Pointer(&mode)))
335	if ok != 0 {
336		err = nil
337	}
338	return mode, err
339}
340