github.com/uriddle/docker@v0.0.0-20210926094723-4072e6aeb013/pkg/term/term.go (about)

     1  // +build !windows
     2  
     3  // Package term provides provides structures and helper functions to work with
     4  // terminal (state, sizes).
     5  package term
     6  
     7  import (
     8  	"errors"
     9  	"io"
    10  	"os"
    11  	"os/signal"
    12  	"syscall"
    13  	"unsafe"
    14  )
    15  
    16  var (
    17  	// ErrInvalidState is returned if the state of the terminal is invalid.
    18  	ErrInvalidState = errors.New("Invalid terminal state")
    19  )
    20  
    21  // State represents the state of the terminal.
    22  type State struct {
    23  	termios Termios
    24  }
    25  
    26  // Winsize represents the size of the terminal window.
    27  type Winsize struct {
    28  	Height uint16
    29  	Width  uint16
    30  	x      uint16
    31  	y      uint16
    32  }
    33  
    34  // StdStreams returns the standard streams (stdin, stdout, stedrr).
    35  func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
    36  	return os.Stdin, os.Stdout, os.Stderr
    37  }
    38  
    39  // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
    40  func GetFdInfo(in interface{}) (uintptr, bool) {
    41  	var inFd uintptr
    42  	var isTerminalIn bool
    43  	if file, ok := in.(*os.File); ok {
    44  		inFd = file.Fd()
    45  		isTerminalIn = IsTerminal(inFd)
    46  	}
    47  	return inFd, isTerminalIn
    48  }
    49  
    50  // GetWinsize returns the window size based on the specified file descriptor.
    51  func GetWinsize(fd uintptr) (*Winsize, error) {
    52  	ws := &Winsize{}
    53  	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
    54  	// Skip errno = 0
    55  	if err == 0 {
    56  		return ws, nil
    57  	}
    58  	return ws, err
    59  }
    60  
    61  // SetWinsize tries to set the specified window size for the specified file descriptor.
    62  func SetWinsize(fd uintptr, ws *Winsize) error {
    63  	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
    64  	// Skip errno = 0
    65  	if err == 0 {
    66  		return nil
    67  	}
    68  	return err
    69  }
    70  
    71  // IsTerminal returns true if the given file descriptor is a terminal.
    72  func IsTerminal(fd uintptr) bool {
    73  	var termios Termios
    74  	return tcget(fd, &termios) == 0
    75  }
    76  
    77  // RestoreTerminal restores the terminal connected to the given file descriptor
    78  // to a previous state.
    79  func RestoreTerminal(fd uintptr, state *State) error {
    80  	if state == nil {
    81  		return ErrInvalidState
    82  	}
    83  	if err := tcset(fd, &state.termios); err != 0 {
    84  		return err
    85  	}
    86  	return nil
    87  }
    88  
    89  // SaveState saves the state of the terminal connected to the given file descriptor.
    90  func SaveState(fd uintptr) (*State, error) {
    91  	var oldState State
    92  	if err := tcget(fd, &oldState.termios); err != 0 {
    93  		return nil, err
    94  	}
    95  
    96  	return &oldState, nil
    97  }
    98  
    99  // DisableEcho applies the specified state to the terminal connected to the file
   100  // descriptor, with echo disabled.
   101  func DisableEcho(fd uintptr, state *State) error {
   102  	newState := state.termios
   103  	newState.Lflag &^= syscall.ECHO
   104  
   105  	if err := tcset(fd, &newState); err != 0 {
   106  		return err
   107  	}
   108  	handleInterrupt(fd, state)
   109  	return nil
   110  }
   111  
   112  // SetRawTerminal puts the terminal connected to the given file descriptor into
   113  // raw mode and returns the previous state.
   114  func SetRawTerminal(fd uintptr) (*State, error) {
   115  	oldState, err := MakeRaw(fd)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	handleInterrupt(fd, oldState)
   120  	return oldState, err
   121  }
   122  
   123  func handleInterrupt(fd uintptr, state *State) {
   124  	sigchan := make(chan os.Signal, 1)
   125  	signal.Notify(sigchan, os.Interrupt)
   126  
   127  	go func() {
   128  		_ = <-sigchan
   129  		RestoreTerminal(fd, state)
   130  		os.Exit(0)
   131  	}()
   132  }