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  }