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