api.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package loop
  3. import (
  4. "encoding/base64"
  5. "encoding/hex"
  6. "fmt"
  7. "os"
  8. "runtime"
  9. "strings"
  10. "time"
  11. "golang.org/x/sys/unix"
  12. "kitty/tools/tty"
  13. "kitty/tools/utils"
  14. "kitty/tools/utils/style"
  15. "kitty/tools/wcswidth"
  16. )
  17. type ScreenSize struct {
  18. WidthCells, HeightCells, WidthPx, HeightPx, CellWidth, CellHeight uint
  19. updated bool
  20. }
  21. type IdType uint64
  22. type TimerCallback func(timer_id IdType) error
  23. type EscapeCodeType int
  24. const (
  25. CSI EscapeCodeType = iota
  26. DCS
  27. OSC
  28. APC
  29. SOS
  30. PM
  31. )
  32. type Loop struct {
  33. controlling_term *tty.Term
  34. terminal_options TerminalStateOptions
  35. screen_size ScreenSize
  36. seen_inband_resize bool
  37. escape_code_parser wcswidth.EscapeCodeParser
  38. keep_going bool
  39. death_signal unix.Signal
  40. exit_code int
  41. timers, timers_temp []*timer
  42. timer_id_counter, write_msg_id_counter IdType
  43. wakeup_channel chan byte
  44. pending_writes []write_msg
  45. tty_write_channel chan write_msg
  46. pending_mouse_events *utils.RingBuffer[MouseEvent]
  47. on_SIGTSTP func() error
  48. style_cache map[string]func(...any) string
  49. style_ctx style.Context
  50. atomic_update_active bool
  51. pointer_shapes []PointerShape
  52. // Suspend the loop restoring terminal state, and run the provided function. When it returns terminal state is
  53. // put back to what it was before suspending unless the function returns an error or an error occurs saving/restoring state.
  54. SuspendAndRun func(func() error) error
  55. // Callbacks
  56. // Called when the terminal has been fully setup. Any string returned is sent to
  57. // the terminal on shutdown
  58. OnInitialize func() (string, error)
  59. // Called just before the loop shuts down. Any returned string is written to the terminal before
  60. // shutdown
  61. OnFinalize func() string
  62. // Called when a key event happens
  63. OnKeyEvent func(event *KeyEvent) error
  64. // Called when a mouse event happens
  65. OnMouseEvent func(event *MouseEvent) error
  66. // Called when text is received either from a key event or directly from the terminal
  67. // Called with an empty string when bracketed paste ends
  68. OnText func(text string, from_key_event bool, in_bracketed_paste bool) error
  69. // Called when the terminal is resized
  70. OnResize func(old_size ScreenSize, new_size ScreenSize) error
  71. // Called when writing is done
  72. OnWriteComplete func(msg_id IdType, has_pending_writes bool) error
  73. // Called when a response to an rc command is received
  74. OnRCResponse func(data []byte) error
  75. // Called when a response to a query command is received
  76. OnQueryResponse func(key, val string, valid bool) error
  77. // Called when any input from tty is received
  78. OnReceivedData func(data []byte) error
  79. // Called when an escape code is received that is not handled by any other handler
  80. OnEscapeCode func(EscapeCodeType, []byte) error
  81. // Called when resuming from a SIGTSTP or Ctrl-z
  82. OnResumeFromStop func() error
  83. // Called when main loop is woken up
  84. OnWakeup func() error
  85. // Called on SIGINT return true if you wish to handle it yourself
  86. OnSIGINT func() (bool, error)
  87. // Called on SIGTERM return true if you wish to handle it yourself
  88. OnSIGTERM func() (bool, error)
  89. }
  90. func New(options ...func(self *Loop)) (*Loop, error) {
  91. l := new_loop()
  92. for _, f := range options {
  93. f(l)
  94. }
  95. return l, nil
  96. }
  97. func (self *Loop) AddTimer(interval time.Duration, repeats bool, callback TimerCallback) (IdType, error) {
  98. return self.add_timer(interval, repeats, callback)
  99. }
  100. func (self *Loop) CallSoon(callback TimerCallback) (IdType, error) {
  101. return self.add_timer(0, false, callback)
  102. }
  103. func (self *Loop) RemoveTimer(id IdType) bool {
  104. return self.remove_timer(id)
  105. }
  106. func (self *Loop) NoAlternateScreen() *Loop {
  107. self.terminal_options.Alternate_screen = false
  108. return self
  109. }
  110. func NoAlternateScreen(self *Loop) {
  111. self.terminal_options.Alternate_screen = false
  112. }
  113. func (self *Loop) OnlyDisambiguateKeys() *Loop {
  114. self.terminal_options.kitty_keyboard_mode = DISAMBIGUATE_KEYS
  115. return self
  116. }
  117. func OnlyDisambiguateKeys(self *Loop) {
  118. self.terminal_options.kitty_keyboard_mode = DISAMBIGUATE_KEYS
  119. }
  120. func (self *Loop) NoKeyboardStateChange() *Loop {
  121. self.terminal_options.kitty_keyboard_mode = NO_KEYBOARD_STATE_CHANGE
  122. return self
  123. }
  124. func NoKeyboardStateChange(self *Loop) {
  125. self.terminal_options.kitty_keyboard_mode = NO_KEYBOARD_STATE_CHANGE
  126. }
  127. func (self *Loop) FullKeyboardProtocol() *Loop {
  128. self.terminal_options.kitty_keyboard_mode = FULL_KEYBOARD_PROTOCOL
  129. return self
  130. }
  131. func FullKeyboardProtocol(self *Loop) {
  132. self.terminal_options.kitty_keyboard_mode = FULL_KEYBOARD_PROTOCOL
  133. }
  134. func (self *Loop) MouseTrackingMode(mt MouseTracking) *Loop {
  135. self.terminal_options.mouse_tracking = mt
  136. return self
  137. }
  138. func MouseTrackingMode(self *Loop, mt MouseTracking) {
  139. self.terminal_options.mouse_tracking = mt
  140. }
  141. func NoMouseTracking(self *Loop) {
  142. self.terminal_options.mouse_tracking = NO_MOUSE_TRACKING
  143. }
  144. func (self *Loop) NoMouseTracking() *Loop {
  145. self.terminal_options.mouse_tracking = NO_MOUSE_TRACKING
  146. return self
  147. }
  148. func (self *Loop) NoRestoreColors() *Loop {
  149. self.terminal_options.restore_colors = false
  150. return self
  151. }
  152. func NoRestoreColors(self *Loop) {
  153. self.terminal_options.restore_colors = false
  154. }
  155. func NoInBandResizeNotifications(self *Loop) {
  156. self.terminal_options.in_band_resize_notification = false
  157. }
  158. func (self *Loop) DeathSignalName() string {
  159. if self.death_signal != SIGNULL {
  160. return self.death_signal.String()
  161. }
  162. return ""
  163. }
  164. func (self *Loop) ScreenSize() (ScreenSize, error) {
  165. if self.screen_size.updated {
  166. return self.screen_size, nil
  167. }
  168. err := self.update_screen_size()
  169. return self.screen_size, err
  170. }
  171. func (self *Loop) KillIfSignalled() {
  172. if self.death_signal != SIGNULL {
  173. kill_self(self.death_signal)
  174. }
  175. }
  176. func (self *Loop) Println(args ...any) {
  177. self.QueueWriteString(fmt.Sprintln(args...))
  178. self.QueueWriteString("\r")
  179. }
  180. func (self *Loop) style_region(style string, start_x, start_y, end_x, end_y int) string {
  181. sgr := self.SprintStyled(style, "|")
  182. if len(sgr) > 2 {
  183. sgr = sgr[2:strings.IndexByte(sgr, 'm')]
  184. return fmt.Sprintf("\x1b[%d;%d;%d;%d;%s$r", start_y+1, start_x+1, end_y+1, end_x+1, sgr)
  185. }
  186. return ""
  187. }
  188. // Apply the specified style to the specified region of the screen (0-based
  189. // indexing). The region is all cells from the start cell to the end cell. See
  190. // StyleRectangle to apply style to a rectangular area.
  191. func (self *Loop) StyleRegion(style string, start_x, start_y, end_x, end_y int) IdType {
  192. return self.QueueWriteString(self.style_region(style, start_x, start_y, end_x, end_y))
  193. }
  194. // Apply the specified style to the specified rectangle of the screen (0-based indexing).
  195. func (self *Loop) StyleRectangle(style string, start_x, start_y, end_x, end_y int) IdType {
  196. return self.QueueWriteString("\x1b[2*x" + self.style_region(style, start_x, start_y, end_x, end_y) + "\x1b[*x")
  197. }
  198. func (self *Loop) SprintStyled(style string, args ...any) string {
  199. f := self.style_cache[style]
  200. if f == nil {
  201. f = self.style_ctx.SprintFunc(style)
  202. self.style_cache[style] = f
  203. }
  204. return f(args...)
  205. }
  206. func (self *Loop) PrintStyled(style string, args ...any) {
  207. self.QueueWriteString(self.SprintStyled(style, args...))
  208. }
  209. func (self *Loop) SaveCursorPosition() {
  210. self.QueueWriteString("\x1b7")
  211. }
  212. func (self *Loop) RestoreCursorPosition() {
  213. self.QueueWriteString("\x1b8")
  214. }
  215. func (self *Loop) Printf(format string, args ...any) {
  216. format = strings.ReplaceAll(format, "\n", "\r\n")
  217. self.QueueWriteString(fmt.Sprintf(format, args...))
  218. }
  219. func (self *Loop) DebugPrintln(args ...any) {
  220. if self.controlling_term != nil {
  221. const limit = 2048
  222. msg := fmt.Sprintln(args...)
  223. for i := 0; i < len(msg); i += limit {
  224. end := i + limit
  225. if end > len(msg) {
  226. end = len(msg)
  227. }
  228. self.QueueWriteString("\x1bP@kitty-print|")
  229. self.QueueWriteString(base64.StdEncoding.EncodeToString([]byte(msg[i:end])))
  230. self.QueueWriteString("\x1b\\")
  231. }
  232. }
  233. }
  234. func (self *Loop) Run() (err error) {
  235. defer func() {
  236. if r := recover(); r != nil {
  237. pcs := make([]uintptr, 256)
  238. n := runtime.Callers(2, pcs)
  239. frames := runtime.CallersFrames(pcs[:n])
  240. err = fmt.Errorf("Panicked: %s", r)
  241. fmt.Fprintf(os.Stderr, "\r\nPanicked with error: %s\r\nStacktrace (most recent call first):\r\n", r)
  242. found_first_frame := false
  243. for frame, more := frames.Next(); more; frame, more = frames.Next() {
  244. if !found_first_frame {
  245. if strings.HasPrefix(frame.Function, "runtime.") {
  246. continue
  247. }
  248. found_first_frame = true
  249. }
  250. fmt.Fprintf(os.Stderr, "%s\r\n\t%s:%d\r\n", frame.Function, frame.File, frame.Line)
  251. }
  252. if self.terminal_options.Alternate_screen {
  253. term, err := tty.OpenControllingTerm(tty.SetRaw)
  254. if err == nil {
  255. defer term.RestoreAndClose()
  256. fmt.Println("Press any key to exit.\r")
  257. buf := make([]byte, 16)
  258. _, _ = term.Read(buf)
  259. }
  260. }
  261. }
  262. }()
  263. return self.run()
  264. }
  265. func (self *Loop) WakeupMainThread() bool {
  266. select {
  267. case self.wakeup_channel <- 1:
  268. return true
  269. default:
  270. return false
  271. }
  272. }
  273. func (self *Loop) QueueWriteString(data string) IdType {
  274. self.write_msg_id_counter++
  275. msg := write_msg{str: data, bytes: nil, id: self.write_msg_id_counter}
  276. self.add_write_to_pending_queue(msg)
  277. return msg.id
  278. }
  279. // This is dangerous as it is upto the calling code
  280. // to ensure the data in the underlying array does not change
  281. func (self *Loop) UnsafeQueueWriteBytes(data []byte) IdType {
  282. self.write_msg_id_counter++
  283. msg := write_msg{bytes: data, id: self.write_msg_id_counter}
  284. self.add_write_to_pending_queue(msg)
  285. return msg.id
  286. }
  287. func (self *Loop) QueueWriteBytesCopy(data []byte) IdType {
  288. d := make([]byte, len(data))
  289. copy(d, data)
  290. return self.UnsafeQueueWriteBytes(d)
  291. }
  292. func (self *Loop) ExitCode() int {
  293. return self.exit_code
  294. }
  295. func (self *Loop) Beep() {
  296. self.QueueWriteString("\a")
  297. }
  298. func (self *Loop) StartAtomicUpdate() {
  299. if self.atomic_update_active {
  300. self.EndAtomicUpdate()
  301. }
  302. self.QueueWriteString(PENDING_UPDATE.EscapeCodeToSet())
  303. self.atomic_update_active = true
  304. }
  305. func (self *Loop) IsAtomicUpdateActive() bool { return self.atomic_update_active }
  306. func (self *Loop) EndAtomicUpdate() {
  307. if self.atomic_update_active {
  308. self.QueueWriteString(PENDING_UPDATE.EscapeCodeToReset())
  309. self.atomic_update_active = false
  310. }
  311. }
  312. func (self *Loop) SetCursorShape(shape CursorShapes, blink bool) {
  313. self.QueueWriteString(CursorShape(shape, blink))
  314. }
  315. func (self *Loop) SetCursorVisible(visible bool) {
  316. if visible {
  317. self.QueueWriteString(DECTCEM.EscapeCodeToSet())
  318. } else {
  319. self.QueueWriteString(DECTCEM.EscapeCodeToReset())
  320. }
  321. }
  322. const MoveCursorToTemplate = "\x1b[%d;%dH"
  323. func (self *Loop) MoveCursorTo(x, y int) { // 1, 1 is top left
  324. if x > 0 && y > 0 {
  325. self.QueueWriteString(fmt.Sprintf(MoveCursorToTemplate, y, x))
  326. }
  327. }
  328. func (self *Loop) MoveCursorHorizontally(amt int) {
  329. if amt != 0 {
  330. suffix := "C"
  331. if amt < 0 {
  332. suffix = "D"
  333. amt *= -1
  334. }
  335. self.QueueWriteString(fmt.Sprintf("\x1b[%d%s", amt, suffix))
  336. }
  337. }
  338. func (self *Loop) MoveCursorVertically(amt int) {
  339. if amt != 0 {
  340. suffix := "B"
  341. if amt < 0 {
  342. suffix = "A"
  343. amt *= -1
  344. }
  345. self.QueueWriteString(fmt.Sprintf("\x1b[%d%s", amt, suffix))
  346. }
  347. }
  348. func (self *Loop) ClearToEndOfScreen() {
  349. self.QueueWriteString("\x1b[J")
  350. }
  351. func (self *Loop) ClearToEndOfLine() {
  352. self.QueueWriteString("\x1b[K")
  353. }
  354. func (self *Loop) StartBracketedPaste() {
  355. self.QueueWriteString(BRACKETED_PASTE.EscapeCodeToSet())
  356. }
  357. func (self *Loop) EndBracketedPaste() {
  358. self.QueueWriteString(BRACKETED_PASTE.EscapeCodeToReset())
  359. }
  360. func (self *Loop) AllowLineWrapping(allow bool) {
  361. if allow {
  362. self.QueueWriteString(DECAWM.EscapeCodeToSet())
  363. } else {
  364. self.QueueWriteString(DECAWM.EscapeCodeToReset())
  365. }
  366. }
  367. func EscapeCodeToSetWindowTitle(title string) string {
  368. title = wcswidth.StripEscapeCodes(title)
  369. return "\033]2;" + title + "\033\\"
  370. }
  371. func (self *Loop) SetWindowTitle(title string) {
  372. self.QueueWriteString(EscapeCodeToSetWindowTitle(title))
  373. }
  374. func (self *Loop) ClearScreen() {
  375. self.QueueWriteString("\x1b[H\x1b[2J")
  376. }
  377. func (self *Loop) ClearScreenButNotGraphics() {
  378. self.QueueWriteString("\x1b[H\x1b[J")
  379. }
  380. func (self *Loop) SendOverlayReady() {
  381. self.QueueWriteString("\x1bP@kitty-overlay-ready|\x1b\\")
  382. }
  383. func (self *Loop) Quit(exit_code int) {
  384. self.exit_code = exit_code
  385. self.keep_going = false
  386. }
  387. type DefaultColor int
  388. const (
  389. BACKGROUND DefaultColor = 11
  390. FOREGROUND DefaultColor = 10
  391. CURSOR DefaultColor = 12
  392. SELECTION_BG DefaultColor = 17
  393. SELECTION_FG DefaultColor = 19
  394. )
  395. func (self *Loop) SetDefaultColor(which DefaultColor, val style.RGBA) {
  396. self.QueueWriteString(fmt.Sprintf("\033]%d;%s\033\\", int(which), val.AsRGBSharp()))
  397. }
  398. func (self *Loop) copy_text_to(text, dest string) {
  399. self.QueueWriteString("\x1b]52;" + dest + ";")
  400. self.QueueWriteString(base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(text)))
  401. self.QueueWriteString("\x1b\\")
  402. }
  403. func (self *Loop) CopyTextToPrimarySelection(text string) {
  404. self.copy_text_to(text, "p")
  405. }
  406. func (self *Loop) CopyTextToClipboard(text string) {
  407. self.copy_text_to(text, "c")
  408. }
  409. func (self *Loop) QueryTerminal(fields ...string) IdType {
  410. if len(fields) == 0 {
  411. return 0
  412. }
  413. q := make([]string, len(fields))
  414. for i, x := range fields {
  415. q[i] = hex.EncodeToString(utils.UnsafeStringToBytes("kitty-query-" + x))
  416. }
  417. return self.QueueWriteString(fmt.Sprintf("\x1bP+q%s\a", strings.Join(q, ";")))
  418. }
  419. func (self *Loop) PushPointerShape(s PointerShape) {
  420. self.pointer_shapes = append(self.pointer_shapes, s)
  421. self.QueueWriteString("\x1b]22;" + s.String() + "\x1b\\")
  422. }
  423. func (self *Loop) PopPointerShape() {
  424. if len(self.pointer_shapes) > 0 {
  425. self.pointer_shapes = self.pointer_shapes[:len(self.pointer_shapes)-1]
  426. self.QueueWriteString("\x1b]22;<\x1b\\")
  427. }
  428. }
  429. // Remove all pointer shapes from the shape stack resetting to default pointer
  430. // shape. This is called automatically on loop termination.
  431. func (self *Loop) ClearPointerShapes() (ans []PointerShape) {
  432. ans = self.pointer_shapes
  433. for i := len(self.pointer_shapes) - 1; i >= 0; i-- {
  434. self.QueueWriteString("\x1b]22;<\x1b\\")
  435. }
  436. self.pointer_shapes = nil
  437. return ans
  438. }
  439. func (self *Loop) CurrentPointerShape() (ans PointerShape, has_shape bool) {
  440. if len(self.pointer_shapes) > 0 {
  441. has_shape = true
  442. ans = self.pointer_shapes[len(self.pointer_shapes)-1]
  443. }
  444. return
  445. }