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()
}
|