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 darwin dragonfly freebsd linux,!appengine netbsd openbsd 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 terminal 18 19import ( 20 "io" 21 "syscall" 22 "unsafe" 23) 24 25// State contains the state of a terminal. 26type State struct { 27 termios syscall.Termios 28} 29 30// IsTerminal returns true if the given file descriptor is a terminal. 31func IsTerminal(fd int) bool { 32 var termios syscall.Termios 33 _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) 34 return err == 0 35} 36 37// MakeRaw put the terminal connected to the given file descriptor into raw 38// mode and returns the previous state of the terminal so that it can be 39// restored. 40func MakeRaw(fd int) (*State, error) { 41 var oldState State 42 if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { 43 return nil, err 44 } 45 46 newState := oldState.termios 47 newState.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF 48 newState.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG 49 if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { 50 return nil, err 51 } 52 53 return &oldState, nil 54} 55 56// GetState returns the current state of a terminal which may be useful to 57// restore the terminal after a signal. 58func GetState(fd int) (*State, error) { 59 var oldState State 60 if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { 61 return nil, err 62 } 63 64 return &oldState, nil 65} 66 67// Restore restores the terminal connected to the given file descriptor to a 68// previous state. 69func Restore(fd int, state *State) error { 70 _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) 71 return err 72} 73 74// GetSize returns the dimensions of the given terminal. 75func GetSize(fd int) (width, height int, err error) { 76 var dimensions [4]uint16 77 78 if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 { 79 return -1, -1, err 80 } 81 return int(dimensions[1]), int(dimensions[0]), nil 82} 83 84// ReadPassword reads a line of input from a terminal without local echo. This 85// is commonly used for inputting passwords and other sensitive data. The slice 86// returned does not include the \n. 87func ReadPassword(fd int) ([]byte, error) { 88 var oldState syscall.Termios 89 if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 { 90 return nil, err 91 } 92 93 newState := oldState 94 newState.Lflag &^= syscall.ECHO 95 newState.Lflag |= syscall.ICANON | syscall.ISIG 96 newState.Iflag |= syscall.ICRNL 97 if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { 98 return nil, err 99 } 100 101 defer func() { 102 syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) 103 }() 104 105 var buf [16]byte 106 var ret []byte 107 for { 108 n, err := syscall.Read(fd, buf[:]) 109 if err != nil { 110 return nil, err 111 } 112 if n == 0 { 113 if len(ret) == 0 { 114 return nil, io.EOF 115 } 116 break 117 } 118 if buf[n-1] == '\n' { 119 n-- 120 } 121 ret = append(ret, buf[:n]...) 122 if n < len(buf) { 123 break 124 } 125 } 126 127 return ret, nil 128} 129