123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- // +build !windows
- // Package term provides structures and helper functions to work with
- // terminal (state, sizes).
- package term
- import (
- "errors"
- "fmt"
- "io"
- "os"
- "os/signal"
- "golang.org/x/sys/unix"
- )
- var (
- // ErrInvalidState is returned if the state of the terminal is invalid.
- ErrInvalidState = errors.New("Invalid terminal state")
- )
- // State represents the state of the terminal.
- type State struct {
- termios Termios
- }
- // Winsize represents the size of the terminal window.
- type Winsize struct {
- Height uint16
- Width uint16
- x uint16
- y uint16
- }
- // StdStreams returns the standard streams (stdin, stdout, stderr).
- func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
- return os.Stdin, os.Stdout, os.Stderr
- }
- // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
- func GetFdInfo(in interface{}) (uintptr, bool) {
- var inFd uintptr
- var isTerminalIn bool
- if file, ok := in.(*os.File); ok {
- inFd = file.Fd()
- isTerminalIn = IsTerminal(inFd)
- }
- return inFd, isTerminalIn
- }
- // IsTerminal returns true if the given file descriptor is a terminal.
- func IsTerminal(fd uintptr) bool {
- var termios Termios
- return tcget(fd, &termios) == 0
- }
- // RestoreTerminal restores the terminal connected to the given file descriptor
- // to a previous state.
- func RestoreTerminal(fd uintptr, state *State) error {
- if state == nil {
- return ErrInvalidState
- }
- if err := tcset(fd, &state.termios); err != 0 {
- return err
- }
- return nil
- }
- // SaveState saves the state of the terminal connected to the given file descriptor.
- func SaveState(fd uintptr) (*State, error) {
- var oldState State
- if err := tcget(fd, &oldState.termios); err != 0 {
- return nil, err
- }
- return &oldState, nil
- }
- // DisableEcho applies the specified state to the terminal connected to the file
- // descriptor, with echo disabled.
- func DisableEcho(fd uintptr, state *State) error {
- newState := state.termios
- newState.Lflag &^= unix.ECHO
- if err := tcset(fd, &newState); err != 0 {
- return err
- }
- handleInterrupt(fd, state)
- return nil
- }
- // SetRawTerminal puts the terminal connected to the given file descriptor into
- // raw mode and returns the previous state. On UNIX, this puts both the input
- // and output into raw mode. On Windows, it only puts the input into raw mode.
- func SetRawTerminal(fd uintptr) (*State, error) {
- oldState, err := MakeRaw(fd)
- if err != nil {
- return nil, err
- }
- handleInterrupt(fd, oldState)
- return oldState, err
- }
- // SetRawTerminalOutput puts the output of terminal connected to the given file
- // descriptor into raw mode. On UNIX, this does nothing and returns nil for the
- // state. On Windows, it disables LF -> CRLF translation.
- func SetRawTerminalOutput(fd uintptr) (*State, error) {
- return nil, nil
- }
- func handleInterrupt(fd uintptr, state *State) {
- sigchan := make(chan os.Signal, 1)
- signal.Notify(sigchan, os.Interrupt)
- go func() {
- for range sigchan {
- // quit cleanly and the new terminal item is on a new line
- fmt.Println()
- signal.Stop(sigchan)
- close(sigchan)
- RestoreTerminal(fd, state)
- os.Exit(1)
- }
- }()
- }
|