term_unix.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // Copyright 2010 Jonas mg
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. // +build !plan9,!windows
  7. package term
  8. import (
  9. "io"
  10. "os"
  11. "syscall"
  12. "github.com/kless/term/sys"
  13. )
  14. // Default values for input and output.
  15. var (
  16. InputFD int = syscall.Stdin
  17. Input io.Reader = os.Stdin
  18. Output io.Writer = os.Stdout
  19. )
  20. // A Terminal represents a general terminal interface.
  21. type Terminal struct {
  22. mode modeType
  23. // Contain the state of a terminal, enabling to restore the original settings
  24. oldState, lastState sys.Termios
  25. // Window size
  26. size sys.Winsize
  27. fd int // File descriptor
  28. }
  29. // New creates a new terminal interface in the file descriptor InputFD.
  30. func New() (*Terminal, error) {
  31. var t Terminal
  32. // Get the actual state
  33. if err := sys.Getattr(InputFD, &t.lastState); err != nil {
  34. return nil, os.NewSyscallError("sys.Getattr", err)
  35. }
  36. t.oldState = t.lastState // the actual state is copied to another one
  37. t.fd = InputFD
  38. return &t, nil
  39. }
  40. // == Restore
  41. //
  42. type State struct {
  43. wrap sys.Termios
  44. }
  45. // OriginalState returns the terminal's original state.
  46. func (t *Terminal) OriginalState() State {
  47. return State{t.oldState}
  48. }
  49. // Restore restores the original settings for the term.
  50. func (t *Terminal) Restore() error {
  51. if t.mode != 0 {
  52. if err := sys.Setattr(t.fd, sys.TCSANOW, &t.oldState); err != nil {
  53. return os.NewSyscallError("sys.Setattr", err)
  54. }
  55. t.lastState = t.oldState
  56. t.mode = 0
  57. }
  58. return nil
  59. }
  60. // Restore restores the settings from State.
  61. func Restore(fd int, st State) error {
  62. if err := sys.Setattr(fd, sys.TCSANOW, &st.wrap); err != nil {
  63. return os.NewSyscallError("sys.Setattr", err)
  64. }
  65. return nil
  66. }
  67. // == Modes
  68. //
  69. // RawMode sets the terminal to something like the "raw" mode. Input is available
  70. // character by character, echoing is disabled, and all special processing of
  71. // terminal input and output characters is disabled.
  72. //
  73. // NOTE: in tty "raw mode", CR+LF is used for output and CR is used for input.
  74. func (t *Terminal) RawMode() error {
  75. // Input modes - no break, no CR to NL, no NL to CR, no carriage return,
  76. // no strip char, no start/stop output control, no parity check.
  77. t.lastState.Iflag &^= (sys.BRKINT | sys.IGNBRK | sys.ICRNL | sys.INLCR |
  78. sys.IGNCR | sys.ISTRIP | sys.IXON | sys.PARMRK)
  79. // Output modes - disable post processing.
  80. t.lastState.Oflag &^= sys.OPOST
  81. // Local modes - echoing off, canonical off, no extended functions,
  82. // no signal chars (^Z,^C).
  83. t.lastState.Lflag &^= (sys.ECHO | sys.ECHONL | sys.ICANON | sys.IEXTEN | sys.ISIG)
  84. // Control modes - set 8 bit chars.
  85. t.lastState.Cflag &^= (sys.CSIZE | sys.PARENB)
  86. t.lastState.Cflag |= sys.CS8
  87. // Control chars - set return condition: min number of bytes and timer.
  88. // We want read to return every single byte, without timeout.
  89. t.lastState.Cc[sys.VMIN] = 1 // Read returns when one char is available.
  90. t.lastState.Cc[sys.VTIME] = 0
  91. // Put the terminal in raw mode after flushing
  92. if err := sys.Setattr(t.fd, sys.TCSAFLUSH, &t.lastState); err != nil {
  93. return os.NewSyscallError("sys.Setattr", err)
  94. }
  95. t.mode |= RawMode
  96. return nil
  97. }
  98. // EchoMode turns the echo mode.
  99. func (t *Terminal) EchoMode(echo bool) error {
  100. if !echo {
  101. //t.lastState.Lflag &^= (sys.ECHO | sys.ECHOE | sys.ECHOK | sys.ECHONL)
  102. t.lastState.Lflag &^= sys.ECHO
  103. } else {
  104. //t.lastState.Lflag |= (sys.ECHO | sys.ECHOE | sys.ECHOK | sys.ECHONL)
  105. t.lastState.Lflag |= sys.ECHO
  106. }
  107. if err := sys.Setattr(t.fd, sys.TCSANOW, &t.lastState); err != nil {
  108. return os.NewSyscallError("sys.Setattr", err)
  109. }
  110. if echo {
  111. t.mode |= EchoMode
  112. } else {
  113. t.mode &^= EchoMode
  114. }
  115. return nil
  116. }
  117. // CharMode sets the terminal to single-character mode.
  118. func (t *Terminal) CharMode() error {
  119. // Disable canonical mode, and set buffer size to 1 byte.
  120. t.lastState.Lflag &^= sys.ICANON
  121. t.lastState.Cc[sys.VTIME] = 0
  122. t.lastState.Cc[sys.VMIN] = 1
  123. if err := sys.Setattr(t.fd, sys.TCSANOW, &t.lastState); err != nil {
  124. return os.NewSyscallError("sys.Setattr", err)
  125. }
  126. t.mode |= CharMode
  127. return nil
  128. }
  129. // SetMode sets the terminal attributes given by state.
  130. // Warning: The use of this function is not cross-system.
  131. func (t *Terminal) SetMode(state sys.Termios) error {
  132. if err := sys.Setattr(t.fd, sys.TCSANOW, &state); err != nil {
  133. return os.NewSyscallError("sys.Setattr", err)
  134. }
  135. t.lastState = state
  136. t.mode |= OtherMode
  137. return nil
  138. }
  139. // == Utility
  140. //
  141. // Fd returns the file descriptor referencing the term.
  142. func (t *Terminal) Fd() int {
  143. return t.fd
  144. }
  145. // GetSize returns the size of the term.
  146. func (t *Terminal) GetSize() (row, column int, err error) {
  147. if err = sys.GetWinsize(syscall.Stdout, &t.size); err != nil {
  148. return
  149. }
  150. return int(t.size.Row), int(t.size.Col), nil
  151. }