tty.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package tty
  3. import (
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "os"
  9. "strconv"
  10. "sync"
  11. "time"
  12. "golang.org/x/sys/unix"
  13. "kitty/tools/utils"
  14. )
  15. const (
  16. TCSANOW = 0
  17. TCSADRAIN = 1
  18. TCSAFLUSH = 2
  19. )
  20. type Term struct {
  21. os_file *os.File
  22. states []unix.Termios
  23. }
  24. func eintr_retry_noret(f func() error) error {
  25. for {
  26. qerr := f()
  27. if qerr == unix.EINTR {
  28. continue
  29. }
  30. return qerr
  31. }
  32. }
  33. func eintr_retry_intret(f func() (int, error)) (int, error) {
  34. for {
  35. q, qerr := f()
  36. if qerr == unix.EINTR {
  37. continue
  38. }
  39. return q, qerr
  40. }
  41. }
  42. func IsTerminal(fd uintptr) bool {
  43. var t unix.Termios
  44. err := eintr_retry_noret(func() error { return Tcgetattr(int(fd), &t) })
  45. return err == nil
  46. }
  47. type TermiosOperation func(t *unix.Termios)
  48. func get_vmin_and_vtime(d time.Duration) (uint8, uint8) {
  49. if d > 0 {
  50. // VTIME is expressed in terms of deciseconds
  51. vtimeDeci := d.Milliseconds() / 100
  52. // ensure valid range
  53. vtime := uint8(clamp(vtimeDeci, 1, 0xff))
  54. return 0, vtime
  55. }
  56. // block indefinitely until we receive at least 1 byte
  57. return 1, 0
  58. }
  59. func SetReadTimeout(d time.Duration) TermiosOperation {
  60. vmin, vtime := get_vmin_and_vtime(d)
  61. return func(t *unix.Termios) {
  62. t.Cc[unix.VMIN] = vmin
  63. t.Cc[unix.VTIME] = vtime
  64. }
  65. }
  66. var SetBlockingRead TermiosOperation = SetReadTimeout(0)
  67. var SetRaw TermiosOperation = func(t *unix.Termios) {
  68. // This attempts to replicate the behaviour documented for cfmakeraw in
  69. // the termios(3) manpage, as Go doesn't wrap cfmakeraw probably because its not in POSIX
  70. t.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
  71. t.Oflag &^= unix.OPOST
  72. t.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
  73. t.Cflag &^= unix.CSIZE | unix.PARENB
  74. t.Cflag |= unix.CS8
  75. t.Cc[unix.VMIN] = 1
  76. t.Cc[unix.VTIME] = 0
  77. }
  78. var SetNoEcho TermiosOperation = func(t *unix.Termios) {
  79. t.Lflag &^= unix.ECHO
  80. }
  81. var SetReadPassword TermiosOperation = func(t *unix.Termios) {
  82. t.Lflag &^= unix.ECHO
  83. t.Lflag |= unix.ISIG
  84. t.Lflag &^= unix.ICANON
  85. t.Iflag |= unix.ICRNL
  86. t.Cc[unix.VMIN] = 1
  87. t.Cc[unix.VTIME] = 0
  88. }
  89. func WrapTerm(fd int, name string, operations ...TermiosOperation) (self *Term, err error) {
  90. if name == "" {
  91. name = fmt.Sprintf("<fd: %d>", fd)
  92. }
  93. os_file := os.NewFile(uintptr(fd), name)
  94. if os_file == nil {
  95. return nil, os.ErrInvalid
  96. }
  97. self = &Term{os_file: os_file}
  98. err = self.ApplyOperations(TCSANOW, operations...)
  99. if err != nil {
  100. self.Close()
  101. self = nil
  102. }
  103. return
  104. }
  105. func OpenTerm(name string, operations ...TermiosOperation) (self *Term, err error) {
  106. fd, err := eintr_retry_intret(func() (int, error) {
  107. return unix.Open(name, unix.O_NOCTTY|unix.O_CLOEXEC|unix.O_NDELAY|unix.O_RDWR, 0666)
  108. })
  109. if err != nil {
  110. return nil, &os.PathError{Op: "open", Path: name, Err: err}
  111. }
  112. self, err = WrapTerm(fd, name, operations...)
  113. return
  114. }
  115. func OpenControllingTerm(operations ...TermiosOperation) (self *Term, err error) {
  116. return OpenTerm(Ctermid(), operations...)
  117. }
  118. func (self *Term) Fd() int {
  119. if self.os_file == nil {
  120. return -1
  121. }
  122. return int(self.os_file.Fd())
  123. }
  124. func (self *Term) Close() error {
  125. if self.os_file == nil {
  126. return nil
  127. }
  128. err := eintr_retry_noret(func() error { return self.os_file.Close() })
  129. self.os_file = nil
  130. return err
  131. }
  132. func (self *Term) WasEchoOnOriginally() bool {
  133. if len(self.states) > 0 {
  134. return self.states[0].Lflag&unix.ECHO != 0
  135. }
  136. return false
  137. }
  138. func (self *Term) Tcgetattr(ans *unix.Termios) error {
  139. return eintr_retry_noret(func() error { return Tcgetattr(self.Fd(), ans) })
  140. }
  141. func (self *Term) Tcsetattr(when uintptr, ans *unix.Termios) error {
  142. return eintr_retry_noret(func() error { return Tcsetattr(self.Fd(), when, ans) })
  143. }
  144. func (self *Term) set_termios_attrs(when uintptr, modify func(*unix.Termios)) (err error) {
  145. var state unix.Termios
  146. if err = self.Tcgetattr(&state); err != nil {
  147. return
  148. }
  149. new_state := state
  150. modify(&new_state)
  151. if err = self.Tcsetattr(when, &new_state); err == nil {
  152. self.states = append(self.states, state)
  153. }
  154. return
  155. }
  156. func (self *Term) ApplyOperations(when uintptr, operations ...TermiosOperation) (err error) {
  157. if len(operations) == 0 {
  158. return
  159. }
  160. return self.set_termios_attrs(when, func(t *unix.Termios) {
  161. for _, op := range operations {
  162. op(t)
  163. }
  164. })
  165. }
  166. func (self *Term) PopStateWhen(when uintptr) (err error) {
  167. if len(self.states) == 0 {
  168. return nil
  169. }
  170. idx := len(self.states) - 1
  171. if err = self.Tcsetattr(when, &self.states[idx]); err == nil {
  172. self.states = self.states[:idx]
  173. }
  174. return
  175. }
  176. func (self *Term) PopState() error {
  177. return self.PopStateWhen(TCSAFLUSH)
  178. }
  179. func (self *Term) RestoreWhen(when uintptr) (err error) {
  180. if len(self.states) == 0 {
  181. return nil
  182. }
  183. self.states = self.states[:1]
  184. return self.PopStateWhen(when)
  185. }
  186. func (self *Term) Restore() error {
  187. return self.RestoreWhen(TCSAFLUSH)
  188. }
  189. func (self *Term) RestoreAndClose() error {
  190. _ = self.Restore()
  191. return self.Close()
  192. }
  193. func (self *Term) Suspend() (resume func() error, err error) {
  194. var state unix.Termios
  195. err = self.Tcgetattr(&state)
  196. if err != nil {
  197. return nil, err
  198. }
  199. if len(self.states) > 0 {
  200. err := self.Tcsetattr(TCSANOW, &self.states[0])
  201. if err != nil {
  202. return nil, err
  203. }
  204. }
  205. return func() error { return self.Tcsetattr(TCSANOW, &state) }, nil
  206. }
  207. func (self *Term) SuspendAndRun(callback func() error) error {
  208. resume, err := self.Suspend()
  209. if err != nil {
  210. return err
  211. }
  212. err = callback()
  213. if rerr := resume(); rerr != nil {
  214. err = rerr
  215. }
  216. return err
  217. }
  218. func clamp(v, lo, hi int64) int64 {
  219. if v < lo {
  220. return lo
  221. }
  222. if v > hi {
  223. return hi
  224. }
  225. return v
  226. }
  227. func (self *Term) ReadWithTimeout(b []byte, d time.Duration) (n int, err error) {
  228. var read, write, in_err unix.FdSet
  229. pselect := func() (int, error) {
  230. read.Zero()
  231. write.Zero()
  232. in_err.Zero()
  233. read.Set(self.Fd())
  234. return utils.Select(self.Fd()+1, &read, &write, &in_err, d)
  235. }
  236. num_ready, err := pselect()
  237. if err != nil {
  238. return 0, err
  239. }
  240. if num_ready == 0 {
  241. err = os.ErrDeadlineExceeded
  242. return 0, err
  243. }
  244. for {
  245. n, err = self.Read(b)
  246. if errors.Is(err, unix.EINTR) {
  247. continue
  248. }
  249. return n, err
  250. }
  251. }
  252. func is_temporary_read_error(err error) bool {
  253. return errors.Is(err, unix.EINTR) || errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EWOULDBLOCK)
  254. }
  255. func (self *Term) Read(b []byte) (n int, err error) {
  256. for {
  257. n, err = self.os_file.Read(b)
  258. // On macOS we get EAGAIN if another thread is writing to the tty at the same time
  259. if err != nil && is_temporary_read_error(err) && n <= 0 {
  260. continue
  261. }
  262. return
  263. }
  264. }
  265. func (self *Term) Write(b []byte) (int, error) {
  266. return self.os_file.Write(b)
  267. }
  268. func is_temporary_error(err error) bool {
  269. return errors.Is(err, unix.EINTR) || errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EWOULDBLOCK) || errors.Is(err, io.ErrShortWrite)
  270. }
  271. func (self *Term) WriteAll(b []byte) error {
  272. for len(b) > 0 {
  273. n, err := self.os_file.Write(b)
  274. if err != nil && !is_temporary_error(err) {
  275. return err
  276. }
  277. b = b[n:]
  278. }
  279. return nil
  280. }
  281. func (self *Term) WriteAllString(s string) error {
  282. return self.WriteAll(utils.UnsafeStringToBytes(s))
  283. }
  284. func (self *Term) WriteString(b string) (int, error) {
  285. return self.os_file.WriteString(b)
  286. }
  287. func (self *Term) DebugPrintln(a ...any) {
  288. msg := []byte(fmt.Sprintln(a...))
  289. const limit = 2048
  290. encoded := make([]byte, limit*2)
  291. for i := 0; i < len(msg); i += limit {
  292. end := i + limit
  293. if end > len(msg) {
  294. end = len(msg)
  295. }
  296. chunk := msg[i:end]
  297. encoded = encoded[:cap(encoded)]
  298. base64.StdEncoding.Encode(encoded, chunk)
  299. _, _ = self.WriteString("\x1bP@kitty-print|")
  300. _, _ = self.Write(encoded)
  301. _, _ = self.WriteString("\x1b\\")
  302. }
  303. }
  304. func GetSize(fd int) (*unix.Winsize, error) {
  305. for {
  306. sz, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
  307. if err != unix.EINTR {
  308. return sz, err
  309. }
  310. }
  311. }
  312. func (self *Term) GetSize() (*unix.Winsize, error) {
  313. return GetSize(self.Fd())
  314. }
  315. // go doesn't have a wrapper for ctermid()
  316. func Ctermid() string { return "/dev/tty" }
  317. var KittyStdout = sync.OnceValue(func() *os.File {
  318. if fds := os.Getenv(`KITTY_STDIO_FORWARDED`); fds != "" {
  319. if fd, err := strconv.Atoi(fds); err == nil && fd > -1 {
  320. if f := os.NewFile(uintptr(fd), "<kitty_stdout>"); f != nil {
  321. return f
  322. }
  323. }
  324. }
  325. return nil
  326. })
  327. func DebugPrintln(a ...any) {
  328. if f := KittyStdout(); f != nil {
  329. fmt.Fprintln(f, a...)
  330. return
  331. }
  332. term, err := OpenControllingTerm()
  333. if err == nil {
  334. defer term.Close()
  335. term.DebugPrintln(a...)
  336. }
  337. }