github.com/ttys3/engine@v17.12.1-ce-rc2+incompatible/pkg/term/term.go (about) 1 // +build !windows 2 3 // Package term provides structures and helper functions to work with 4 // terminal (state, sizes). 5 package term 6 7 import ( 8 "errors" 9 "fmt" 10 "io" 11 "os" 12 "os/signal" 13 14 "golang.org/x/sys/unix" 15 ) 16 17 var ( 18 // ErrInvalidState is returned if the state of the terminal is invalid. 19 ErrInvalidState = errors.New("Invalid terminal state") 20 ) 21 22 // State represents the state of the terminal. 23 type State struct { 24 termios Termios 25 } 26 27 // Winsize represents the size of the terminal window. 28 type Winsize struct { 29 Height uint16 30 Width uint16 31 x uint16 32 y uint16 33 } 34 35 // StdStreams returns the standard streams (stdin, stdout, stderr). 36 func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { 37 return os.Stdin, os.Stdout, os.Stderr 38 } 39 40 // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. 41 func GetFdInfo(in interface{}) (uintptr, bool) { 42 var inFd uintptr 43 var isTerminalIn bool 44 if file, ok := in.(*os.File); ok { 45 inFd = file.Fd() 46 isTerminalIn = IsTerminal(inFd) 47 } 48 return inFd, isTerminalIn 49 } 50 51 // IsTerminal returns true if the given file descriptor is a terminal. 52 func IsTerminal(fd uintptr) bool { 53 var termios Termios 54 return tcget(fd, &termios) == 0 55 } 56 57 // RestoreTerminal restores the terminal connected to the given file descriptor 58 // to a previous state. 59 func RestoreTerminal(fd uintptr, state *State) error { 60 if state == nil { 61 return ErrInvalidState 62 } 63 if err := tcset(fd, &state.termios); err != 0 { 64 return err 65 } 66 return nil 67 } 68 69 // SaveState saves the state of the terminal connected to the given file descriptor. 70 func SaveState(fd uintptr) (*State, error) { 71 var oldState State 72 if err := tcget(fd, &oldState.termios); err != 0 { 73 return nil, err 74 } 75 76 return &oldState, nil 77 } 78 79 // DisableEcho applies the specified state to the terminal connected to the file 80 // descriptor, with echo disabled. 81 func DisableEcho(fd uintptr, state *State) error { 82 newState := state.termios 83 newState.Lflag &^= unix.ECHO 84 85 if err := tcset(fd, &newState); err != 0 { 86 return err 87 } 88 handleInterrupt(fd, state) 89 return nil 90 } 91 92 // SetRawTerminal puts the terminal connected to the given file descriptor into 93 // raw mode and returns the previous state. On UNIX, this puts both the input 94 // and output into raw mode. On Windows, it only puts the input into raw mode. 95 func SetRawTerminal(fd uintptr) (*State, error) { 96 oldState, err := MakeRaw(fd) 97 if err != nil { 98 return nil, err 99 } 100 handleInterrupt(fd, oldState) 101 return oldState, err 102 } 103 104 // SetRawTerminalOutput puts the output of terminal connected to the given file 105 // descriptor into raw mode. On UNIX, this does nothing and returns nil for the 106 // state. On Windows, it disables LF -> CRLF translation. 107 func SetRawTerminalOutput(fd uintptr) (*State, error) { 108 return nil, nil 109 } 110 111 func handleInterrupt(fd uintptr, state *State) { 112 sigchan := make(chan os.Signal, 1) 113 signal.Notify(sigchan, os.Interrupt) 114 go func() { 115 for range sigchan { 116 // quit cleanly and the new terminal item is on a new line 117 fmt.Println() 118 signal.Stop(sigchan) 119 close(sigchan) 120 RestoreTerminal(fd, state) 121 os.Exit(1) 122 } 123 }() 124 }