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