1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build windows
6
7// Package terminal provides support functions for dealing with terminals, as
8// commonly found on UNIX systems.
9//
10// Putting a terminal into raw mode is the most common requirement:
11//
12// 	oldState, err := terminal.MakeRaw(0)
13// 	if err != nil {
14// 	        panic(err)
15// 	}
16// 	defer terminal.Restore(0, oldState)
17package readline
18
19import (
20	"io"
21	"syscall"
22	"unsafe"
23)
24
25const (
26	enableLineInput       = 2
27	enableEchoInput       = 4
28	enableProcessedInput  = 1
29	enableWindowInput     = 8
30	enableMouseInput      = 16
31	enableInsertMode      = 32
32	enableQuickEditMode   = 64
33	enableExtendedFlags   = 128
34	enableAutoPosition    = 256
35	enableProcessedOutput = 1
36	enableWrapAtEolOutput = 2
37)
38
39var kernel32 = syscall.NewLazyDLL("kernel32.dll")
40
41var (
42	procGetConsoleMode             = kernel32.NewProc("GetConsoleMode")
43	procSetConsoleMode             = kernel32.NewProc("SetConsoleMode")
44	procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
45)
46
47type (
48	coord struct {
49		x short
50		y short
51	}
52	smallRect struct {
53		left   short
54		top    short
55		right  short
56		bottom short
57	}
58	consoleScreenBufferInfo struct {
59		size              coord
60		cursorPosition    coord
61		attributes        word
62		window            smallRect
63		maximumWindowSize coord
64	}
65)
66
67type State struct {
68	mode uint32
69}
70
71// IsTerminal returns true if the given file descriptor is a terminal.
72func IsTerminal(fd int) bool {
73	var st uint32
74	r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
75	return r != 0 && e == 0
76}
77
78// MakeRaw put the terminal connected to the given file descriptor into raw
79// mode and returns the previous state of the terminal so that it can be
80// restored.
81func MakeRaw(fd int) (*State, error) {
82	var st uint32
83	_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
84	if e != 0 {
85		return nil, error(e)
86	}
87	raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
88	_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0)
89	if e != 0 {
90		return nil, error(e)
91	}
92	return &State{st}, nil
93}
94
95// GetState returns the current state of a terminal which may be useful to
96// restore the terminal after a signal.
97func GetState(fd int) (*State, error) {
98	var st uint32
99	_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
100	if e != 0 {
101		return nil, error(e)
102	}
103	return &State{st}, nil
104}
105
106// Restore restores the terminal connected to the given file descriptor to a
107// previous state.
108func restoreTerm(fd int, state *State) error {
109	_, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0)
110	return err
111}
112
113// GetSize returns the dimensions of the given terminal.
114func GetSize(fd int) (width, height int, err error) {
115	var info consoleScreenBufferInfo
116	_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0)
117	if e != 0 {
118		return 0, 0, error(e)
119	}
120	return int(info.size.x), int(info.size.y), nil
121}
122
123// ReadPassword reads a line of input from a terminal without local echo.  This
124// is commonly used for inputting passwords and other sensitive data. The slice
125// returned does not include the \n.
126func ReadPassword(fd int) ([]byte, error) {
127	var st uint32
128	_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
129	if e != 0 {
130		return nil, error(e)
131	}
132	old := st
133
134	st &^= (enableEchoInput)
135	st |= (enableProcessedInput | enableLineInput | enableProcessedOutput)
136	_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0)
137	if e != 0 {
138		return nil, error(e)
139	}
140
141	defer func() {
142		syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0)
143	}()
144
145	var buf [16]byte
146	var ret []byte
147	for {
148		n, err := syscall.Read(syscall.Handle(fd), buf[:])
149		if err != nil {
150			return nil, err
151		}
152		if n == 0 {
153			if len(ret) == 0 {
154				return nil, io.EOF
155			}
156			break
157		}
158		if buf[n-1] == '\n' {
159			n--
160		}
161		if n > 0 && buf[n-1] == '\r' {
162			n--
163		}
164		ret = append(ret, buf[:n]...)
165		if n < len(buf) {
166			break
167		}
168	}
169
170	return ret, nil
171}
172