term.go 3.1 KB

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