forked from tredoe/term
-
Notifications
You must be signed in to change notification settings - Fork 0
/
term_unix.go
185 lines (151 loc) · 4.75 KB
/
term_unix.go
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// Copyright 2010 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// +build !plan9,!windows
package term
import (
"io"
"os"
"syscall"
"github.com/kless/term/sys"
)
// Default values for input and output.
var (
InputFD int = syscall.Stdin
Input io.Reader = os.Stdin
Output io.Writer = os.Stdout
)
// A Terminal represents a general terminal interface.
type Terminal struct {
mode modeType
// Contain the state of a terminal, enabling to restore the original settings
oldState, lastState sys.Termios
// Window size
size sys.Winsize
fd int // File descriptor
}
// New creates a new terminal interface in the file descriptor InputFD.
func New() (*Terminal, error) {
var t Terminal
// Get the actual state
if err := sys.Getattr(InputFD, &t.lastState); err != nil {
return nil, os.NewSyscallError("sys.Getattr", err)
}
t.oldState = t.lastState // the actual state is copied to another one
t.fd = InputFD
return &t, nil
}
// == Restore
//
type State struct {
wrap sys.Termios
}
// OriginalState returns the terminal's original state.
func (t *Terminal) OriginalState() State {
return State{t.oldState}
}
// Restore restores the original settings for the term.
func (t *Terminal) Restore() error {
if t.mode != 0 {
if err := sys.Setattr(t.fd, sys.TCSANOW, &t.oldState); err != nil {
return os.NewSyscallError("sys.Setattr", err)
}
t.lastState = t.oldState
t.mode = 0
}
return nil
}
// Restore restores the settings from State.
func Restore(fd int, st State) error {
if err := sys.Setattr(fd, sys.TCSANOW, &st.wrap); err != nil {
return os.NewSyscallError("sys.Setattr", err)
}
return nil
}
// == Modes
//
// RawMode sets the terminal to something like the "raw" mode. Input is available
// character by character, echoing is disabled, and all special processing of
// terminal input and output characters is disabled.
//
// NOTE: in tty "raw mode", CR+LF is used for output and CR is used for input.
func (t *Terminal) RawMode() error {
// Input modes - no break, no CR to NL, no NL to CR, no carriage return,
// no strip char, no start/stop output control, no parity check.
t.lastState.Iflag &^= (sys.BRKINT | sys.IGNBRK | sys.ICRNL | sys.INLCR |
sys.IGNCR | sys.ISTRIP | sys.IXON | sys.PARMRK)
// Output modes - disable post processing.
t.lastState.Oflag &^= sys.OPOST
// Local modes - echoing off, canonical off, no extended functions,
// no signal chars (^Z,^C).
t.lastState.Lflag &^= (sys.ECHO | sys.ECHONL | sys.ICANON | sys.IEXTEN | sys.ISIG)
// Control modes - set 8 bit chars.
t.lastState.Cflag &^= (sys.CSIZE | sys.PARENB)
t.lastState.Cflag |= sys.CS8
// Control chars - set return condition: min number of bytes and timer.
// We want read to return every single byte, without timeout.
t.lastState.Cc[sys.VMIN] = 1 // Read returns when one char is available.
t.lastState.Cc[sys.VTIME] = 0
// Put the terminal in raw mode after flushing
if err := sys.Setattr(t.fd, sys.TCSAFLUSH, &t.lastState); err != nil {
return os.NewSyscallError("sys.Setattr", err)
}
t.mode |= RawMode
return nil
}
// EchoMode turns the echo mode.
func (t *Terminal) EchoMode(echo bool) error {
if !echo {
//t.lastState.Lflag &^= (sys.ECHO | sys.ECHOE | sys.ECHOK | sys.ECHONL)
t.lastState.Lflag &^= sys.ECHO
} else {
//t.lastState.Lflag |= (sys.ECHO | sys.ECHOE | sys.ECHOK | sys.ECHONL)
t.lastState.Lflag |= sys.ECHO
}
if err := sys.Setattr(t.fd, sys.TCSANOW, &t.lastState); err != nil {
return os.NewSyscallError("sys.Setattr", err)
}
if echo {
t.mode |= EchoMode
} else {
t.mode &^= EchoMode
}
return nil
}
// CharMode sets the terminal to single-character mode.
func (t *Terminal) CharMode() error {
// Disable canonical mode, and set buffer size to 1 byte.
t.lastState.Lflag &^= sys.ICANON
t.lastState.Cc[sys.VTIME] = 0
t.lastState.Cc[sys.VMIN] = 1
if err := sys.Setattr(t.fd, sys.TCSANOW, &t.lastState); err != nil {
return os.NewSyscallError("sys.Setattr", err)
}
t.mode |= CharMode
return nil
}
// SetMode sets the terminal attributes given by state.
// Warning: The use of this function is not cross-system.
func (t *Terminal) SetMode(state sys.Termios) error {
if err := sys.Setattr(t.fd, sys.TCSANOW, &state); err != nil {
return os.NewSyscallError("sys.Setattr", err)
}
t.lastState = state
t.mode |= OtherMode
return nil
}
// == Utility
//
// Fd returns the file descriptor referencing the term.
func (t *Terminal) Fd() int {
return t.fd
}
// GetSize returns the size of the term.
func (t *Terminal) GetSize() (row, column int, err error) {
if err = sys.GetWinsize(syscall.Stdout, &t.size); err != nil {
return
}
return int(t.size.Row), int(t.size.Col), nil
}