File: termios.go

package info (click to toggle)
golang-github-google-goterm 0.0~git20200907.555d40f-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 184 kB
  • sloc: makefile: 4
file content (392 lines) | stat: -rw-r--r-- 12,868 bytes parent folder | download
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.


/*
Package term implements a subset of the C termios library to interface with Terminals.

This package allows the caller to get and set most Terminal capabilites
and sizes as well as create PTYs to enable writing things like script,
screen, tmux, and expect.

The Termios type is used for setting/getting Terminal capabilities while
the PTY type is used for handling virtual terminals.

Currently this part of this lib is Linux specific.

Also implements a simple version of readline in pure Go and some Stringers
for terminal colors and attributes.
*/
package term

import (
	"errors"
	"os"
	"strconv"
	"strings"
	"syscall"
	"unsafe"
)

// IOCTL terminal stuff.
const (
	TCGETS     = 0x5401     // TCGETS get terminal attributes
	TCSETS     = 0x5402     // TCSETS set terminal attributes
	TIOCGWINSZ = 0x5413     // TIOCGWINSZ used to get the terminal window size
	TIOCSWINSZ = 0x5414     // TIOCSWINSZ used to set the terminal window size
	TIOCGPTN   = 0x80045430 // TIOCGPTN IOCTL used to get the PTY number
	TIOCSPTLCK = 0x40045431 // TIOCSPTLCK IOCT used to lock/unlock PTY
	CBAUD      = 0010017    // CBAUD Serial speed settings
	CBAUDEX    = 0010000    // CBAUDX Serial speed settings
)

// INPUT handling terminal flags
// see 'man stty' for further info about most of the constants
const (
	IGNBRK  = 0000001 // IGNBRK ignore break characters
	BRKINT  = 0000002 // BRKINT Break genereates an interrupt signal
	IGNPAR  = 0000004 // IGNPAR Ignore characters with parity errors
	PARMRK  = 0000010 // PARMRK Mark parity errors byte{ff,0}
	INPCK   = 0000020 // INPCK enable parity checking
	ISTRIP  = 0000040 // ISTRIP Clear 8th bit of input characters
	INLCR   = 0000100 // INLCR Translate LF => CR
	IGNCR   = 0000200 // IGNCR Ignore Carriage Return
	ICRNL   = 0000400 // ICRNL Translate CR => NL
	IUCLC   = 0001000 // IUCLC Translate uppercase to lowercase
	IXON    = 0002000 // IXON Enable flow control
	IXANY   = 0004000 // IXANY let any char restart input
	IXOFF   = 0010000 // IXOFF start sending start/stop chars
	IMAXBEL = 0020000 // IMAXBEL Sound the bell and skip flushing input buffer
	IUTF8   = 0040000 // IUTF8 assume input being utf-8
)

// OUTPUT treatment terminal flags
const (
	OPOST  = 0000001 // OPOST post process output
	OLCUC  = 0000002 // OLCUC translate lower case to upper case
	ONLCR  = 0000004 // ONLCR Map NL -> CR-NL
	OCRNL  = 0000010 // OCRNL Map CR -> NL
	ONOCR  = 0000020 // ONOCR No CR at col 0
	ONLRET = 0000040 // ONLRET NL also do CR
	OFILL  = 0000100 // OFILL Fillchar for delay
	OFDEL  = 0000200 // OFDEL use delete instead of null
)

// TERM control modes.
const (
	CSIZE  = 0000060 // CSIZE used as mask when setting character size
	CS5    = 0000000 // CS5 char size 5bits
	CS6    = 0000020 // CS6 char size 6bits
	CS7    = 0000040 // CS7 char size 7bits
	CS8    = 0000060 // CS8 char size 8bits
	CSTOPB = 0000100 // CSTOPB two stop bits
	CREAD  = 0000200 // CREAD enable input
	PARENB = 0000400 // PARENB generate and expect parity bit
	PARODD = 0001000 // PARODD set odd parity
	HUPCL  = 0002000 // HUPCL send HUP when last process closes term
	CLOCAL = 0004000 // CLOCAL no modem control signals
)

// TERM modes
const (
	ISIG    = 0000001 // ISIG enable Interrupt,quit and suspend chars
	ICANON  = 0000002 // ICANON enable erase,kill ,werase and rprnt chars
	XCASE   = 0000004 // XCASE preceedes all uppercase chars with '\'
	ECHO    = 0000010 // ECHO echo input characters
	ECHOE   = 0000020 // ECHOE erase => BS - SPACE - BS
	ECHOK   = 0000040 // ECHOK add newline after kill char
	ECHONL  = 0000100 // ECHONL echo NL even without other characters
	NOFLSH  = 0000200 // NOFLSH no flush after interrupt and kill characters
	TOSTOP  = 0000400 // TOSTOP stop BG jobs trying to write to term
	ECHOCTL = 0001000 // ECHOCTL will echo control characters as ^c
	ECHOPRT = 0002000 // ECHOPRT will print erased characters between \ /
	ECHOKE  = 0004000 // ECHOKE kill all line considering ECHOPRT and ECHOE flags
	IEXTEN  = 0100000 // IEXTEN enable non POSIX special characters
)

// Control characters
const (
	VINTR    = 0  // VINTR 		char will send an interrupt signal
	VQUIT    = 1  // VQUIT 		char will send a quit signal
	VERASE   = 2  // VEREASE 	char will erase last typed char
	VKILL    = 3  // VKILL 		char will erase current line
	VEOF     = 4  // VEOF 		char will send EOF
	VTIME    = 5  // VTIME 		set read timeout in tenths of seconds
	VMIN     = 6  // VMIN 		set min characters for a complete read
	VSWTC    = 7  // VSWTC 		char will switch to a different shell layer
	VSTART   = 8  // VSTART 	char will restart output after stopping it
	VSTOP    = 9  // VSTOP 		char will stop output
	VSUSP    = 10 // VSUSP 		char will send a stop signal
	VEOL     = 11 // VEOL 		char will end the line
	VREPRINT = 12 // VREPRINT will redraw the current line
	VDISCARD = 13 // VDISCARD
	VWERASE  = 14 // VWERASE 	char will erase last word typed
	VLNEXT   = 15 // VLNEXT 	char will enter the next char quoted
	VEOL2    = 16 // VEOL2 		char alternate to end line
	tNCCS    = 32 // tNCCS    Termios CC size
)

// Termios merge of the C Terminal and Kernel termios structs.
type Termios struct {
	Iflag  uint32      // Iflag Handles the different Input modes
	Oflag  uint32      // Oflag For the different Output modes
	Cflag  uint32      // Cflag Control modes
	Lflag  uint32      // Lflag Local modes
	Line   byte        // Line
	Cc     [tNCCS]byte // Cc Control characters. How to handle special Characters eg. Backspace being ^H or ^? and so on
	Ispeed uint32      // Ispeed Hardly ever used speed of terminal
	Ospeed uint32      // Ospeed "
	Wz     Winsize     // Wz Terminal size information.
}

// Winsize handle the terminal window size.
type Winsize struct {
	WsRow    uint16 // WsRow 		Terminal number of rows
	WsCol    uint16 // WsCol 		Terminal number of columns
	WsXpixel uint16 // WsXpixel Terminal width in pixels
	WsYpixel uint16 // WsYpixel Terminal height in pixels
}

// PTY the PTY Master/Slave are always bundled together so makes sense to bundle here too.
//
// Slave  - implements the virtual terminal functionality and the place you connect client applications
// Master - Things written to the Master are forwarded to the Slave terminal and the other way around.
//					This gives reading from Master would give you nice line-by-line with no strange characters in
//					Cooked() Mode and every char in Raw() mode.
//
// Since Slave is a virtual terminal it depends on the terminal settings ( in this lib the Termios ) what
// and when data is forwarded through the terminal.
//
// See 'man pty' for further info
type PTY struct {
	Master *os.File // Master The Master part of the PTY
	Slave  *os.File // Slave The Slave part of the PTY
}

// Raw Sets terminal t to raw mode.
// This gives that the terminal will do the absolut minimal of processing, pretty much send everything through.
// This is normally what Shells and such want since they have their own readline and movement code.
func (t *Termios) Raw() {
	t.Iflag &^= IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON
	// t.Iflag &^= BRKINT | ISTRIP | ICRNL | IXON // Stevens RAW
	t.Oflag &^= OPOST
	t.Lflag &^= ECHO | ECHONL | ICANON | ISIG | IEXTEN
	t.Cflag &^= CSIZE | PARENB
	t.Cflag |= CS8
	t.Cc[VMIN] = 1
	t.Cc[VTIME] = 0
}

// Cook Set the Terminal to Cooked mode.
// In this mode the Terminal process the information before sending it on to the application.
func (t *Termios) Cook() {
	t.Iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON
	t.Oflag |= OPOST
	t.Lflag |= ISIG | ICANON
}

// Sane reset Term to sane values.
// Should be pretty much what the shell command "reset" does to the terminal.
func (t *Termios) Sane() {
	t.Iflag &^= IGNBRK | INLCR | IGNCR | IUTF8 | IXOFF | IUCLC | IXANY
	t.Iflag |= BRKINT | ICRNL | IMAXBEL
	t.Oflag |= OPOST | ONLCR
	t.Oflag &^= OLCUC | OCRNL | ONOCR | ONLRET
	t.Cflag |= CREAD
}

// Set Sets terminal t attributes on file.
func (t *Termios) Set(file *os.File) error {
	fd := file.Fd()
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(TCSETS), uintptr(unsafe.Pointer(t)))
	if errno != 0 {
		return errno
	}
	return nil
}

// Attr Gets (terminal related) attributes from file.
func Attr(file *os.File) (Termios, error) {
	var t Termios
	fd := file.Fd()
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(TCGETS), uintptr(unsafe.Pointer(&t)))
	if errno != 0 {
		return t, errno
	}
	t.Ispeed &= CBAUD | CBAUDEX
	t.Ospeed &= CBAUD | CBAUDEX
	return t, nil
}

// Isatty returns true if file is a tty.
func Isatty(file *os.File) bool {
	_, err := Attr(file)
	return err == nil
}

// GetPass reads password from a TTY with no echo.
func GetPass(prompt string, f *os.File, pbuf []byte) ([]byte, error) {
	t, err := Attr(f)
	if err != nil {
		return nil, err
	}
	defer t.Set(f)
	noecho := t
	noecho.Lflag = noecho.Lflag &^ ECHO
	if err := noecho.Set(f); err != nil {
		return nil, err
	}
	b := make([]byte, 1, 1)
	i := 0
	if _, err := f.Write([]byte(prompt)); err != nil {
		return nil, err
	}
	for ; i < len(pbuf); i++ {
		if _, err := f.Read(b); err != nil {
			b[0] = 0
			clearbuf(pbuf[:i+1])
		}
		if b[0] == '\n' || b[0] == '\r' {
			return pbuf[:i], nil
		}
		pbuf[i] = b[0]
		b[0] = 0
	}
	clearbuf(pbuf[:i+1])
	return nil, errors.New("ran out of bufferspace")
}

// clearbuf clears out the buffer incase we couldn't read the full password.
func clearbuf(b []byte) {
	for i := range b {
		b[i] = 0
	}
}

// GetChar reads a single byte.
func GetChar(f *os.File) (b byte, err error) {
	bs := make([]byte, 1, 1)
	if _, err = f.Read(bs); err != nil {
		return 0, err
	}
	return bs[0], err
}

// PTSName return the name of the pty.
func (p *PTY) PTSName() (string, error) {
	n, err := p.PTSNumber()
	if err != nil {
		return "", err
	}
	return "/dev/pts/" + strconv.Itoa(int(n)), nil
}

// PTSNumber return the pty number.
func (p *PTY) PTSNumber() (uint, error) {
	var ptyno uint32
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(p.Master.Fd()), uintptr(TIOCGPTN), uintptr(unsafe.Pointer(&ptyno)))
	if errno != 0 {
		return 0, errno
	}
	return uint(ptyno), nil
}

// Winsz Fetches the current terminal windowsize.
// example handling changing window sizes with PTYs:
//
// import "os"
// import "os/signal"
//
// var sig = make(chan os.Signal,2) 		// Channel to listen for UNIX SIGNALS on
// signal.Notify(sig, syscall.SIGWINCH) // That'd be the window changing
//
// for {
//	<-sig
// 	term.Winsz(os.Stdin)			// We got signaled our terminal changed size so we read in the new value
//  term.Setwinsz(pty.Slave) // Copy it to our virtual Terminal
// }
func (t *Termios) Winsz(file *os.File) error {
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(file.Fd()), uintptr(TIOCGWINSZ), uintptr(unsafe.Pointer(&t.Wz)))
	if errno != 0 {
		return errno
	}
	return nil
}

// Setwinsz Sets the terminal window size.
func (t *Termios) Setwinsz(file *os.File) error {
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(file.Fd()), uintptr(TIOCSWINSZ), uintptr(unsafe.Pointer(&t.Wz)))
	if errno != 0 {
		return errno
	}
	return nil
}

// OpenPTY Creates a new Master/Slave PTY pair.
func OpenPTY() (*PTY, error) {
	// Opening ptmx gives you the FD of a brand new PTY
	master, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
	if err != nil {
		return nil, err
	}

	// unlock pty slave
	var unlock int32 // 0 => Unlock
	if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(master.Fd()), uintptr(TIOCSPTLCK), uintptr(unsafe.Pointer(&unlock))); errno != 0 {
		master.Close()
		return nil, errno
	}

	// get path of pts slave
	pty := &PTY{Master: master}
	slaveStr, err := pty.PTSName()
	if err != nil {
		master.Close()
		return nil, err
	}

	// open pty slave
	pty.Slave, err = os.OpenFile(slaveStr, os.O_RDWR|syscall.O_NOCTTY, 0)
	if err != nil {
		master.Close()
		return nil, err
	}

	return pty, nil
}

// Close closes the PTYs that OpenPTY created.
func (p *PTY) Close() error {
	slaveErr := errors.New("Slave FD nil")
	if p.Slave != nil {
		slaveErr = p.Slave.Close()
	}
	masterErr := errors.New("Master FD nil")
	if p.Master != nil {
		masterErr = p.Master.Close()
	}
	if slaveErr != nil || masterErr != nil {
		var errs []string
		if slaveErr != nil {
			errs = append(errs, "Slave: "+slaveErr.Error())
		}
		if masterErr != nil {
			errs = append(errs, "Master: "+masterErr.Error())
		}
		return errors.New(strings.Join(errs, " "))
	}
	return nil
}

// ReadByte implements the io.ByteReader interface to read single char from the PTY.
func (p *PTY) ReadByte() (byte, error) {
	bs := make([]byte, 1, 1)
	_, err := p.Master.Read(bs)
	return bs[0], err
}

// GetChar fine old getchar() for a PTY.
func (p *PTY) GetChar() (byte, error) {
	return p.ReadByte()
}