backend.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package choose_fonts
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "strings"
  9. "sync"
  10. "time"
  11. "kitty/tools/utils"
  12. )
  13. var _ = fmt.Print
  14. type kitty_font_backend_type struct {
  15. from io.ReadCloser
  16. to io.WriteCloser
  17. json_decoder *json.Decoder
  18. cmd *exec.Cmd
  19. stderr strings.Builder
  20. lock sync.Mutex
  21. r io.ReadCloser
  22. w io.WriteCloser
  23. wait_for_exit chan error
  24. started, exited, failed bool
  25. timeout time.Duration
  26. }
  27. func (k *kitty_font_backend_type) start() (err error) {
  28. exe := utils.KittyExe()
  29. if exe == "" {
  30. exe = utils.Which("kitty")
  31. }
  32. if exe == "" {
  33. return fmt.Errorf("Failed to find the kitty executable, this kitten requires the kitty executable to be present. You can use the environment variable KITTY_PATH_TO_KITTY_EXE to specify the path to the kitty executable")
  34. }
  35. k.cmd = exec.Command(exe, "+runpy", "from kittens.choose_fonts.backend import main; main()")
  36. k.cmd.Stderr = &k.stderr
  37. if k.r, k.to, err = os.Pipe(); err != nil {
  38. return err
  39. }
  40. k.cmd.Stdin = k.r
  41. if k.from, k.w, err = os.Pipe(); err != nil {
  42. return err
  43. }
  44. k.cmd.Stdout = k.w
  45. k.json_decoder = json.NewDecoder(k.from)
  46. if err = k.cmd.Start(); err != nil {
  47. return err
  48. }
  49. k.started = true
  50. k.timeout = 60 * time.Second
  51. k.wait_for_exit = make(chan error)
  52. go func() {
  53. k.wait_for_exit <- k.cmd.Wait()
  54. }()
  55. return
  56. }
  57. var kitty_font_backend kitty_font_backend_type
  58. func (k *kitty_font_backend_type) send(v any) error {
  59. data, err := json.Marshal(v)
  60. if err != nil {
  61. return fmt.Errorf("Could not encode message to kitty with error: %w", err)
  62. }
  63. c := make(chan error)
  64. go func() {
  65. if _, err = k.to.Write(data); err != nil {
  66. c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err)
  67. return
  68. }
  69. if _, err = k.to.Write([]byte{'\n'}); err != nil {
  70. c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err)
  71. return
  72. }
  73. c <- nil
  74. }()
  75. select {
  76. case err := <-c:
  77. return err
  78. case <-time.After(k.timeout):
  79. return fmt.Errorf("Timed out waiting to write to kitty font backend after %v", k.timeout)
  80. case err := <-k.wait_for_exit:
  81. k.exited = true
  82. if err == nil {
  83. err = fmt.Errorf("kitty font backend exited with no error while waiting for a response from it")
  84. } else {
  85. k.failed = true
  86. }
  87. return err
  88. }
  89. }
  90. func (k *kitty_font_backend_type) query(action string, cmd map[string]any, result any) error {
  91. k.lock.Lock()
  92. defer k.lock.Unlock()
  93. if cmd == nil {
  94. cmd = make(map[string]any)
  95. }
  96. cmd["action"] = action
  97. if err := k.send(cmd); err != nil {
  98. return err
  99. }
  100. c := make(chan error)
  101. go func() {
  102. if err := k.json_decoder.Decode(result); err != nil {
  103. c <- fmt.Errorf("Failed to decode JSON from kitty with error: %w", err)
  104. }
  105. c <- nil
  106. }()
  107. select {
  108. case err := <-c:
  109. return err
  110. case <-time.After(k.timeout):
  111. return fmt.Errorf("Timed out waiting for response from kitty font backend after %v", k.timeout)
  112. case err := <-k.wait_for_exit:
  113. k.exited = true
  114. if err == nil {
  115. err = fmt.Errorf("kitty font backed exited with no error while waiting for a response from it")
  116. } else {
  117. k.failed = true
  118. }
  119. return err
  120. }
  121. }
  122. func (k *kitty_font_backend_type) release() (err error) {
  123. if k.r != nil {
  124. k.r.Close()
  125. k.r = nil
  126. }
  127. if k.to != nil {
  128. k.to.Close()
  129. k.to = nil
  130. }
  131. if k.w != nil {
  132. k.w.Close()
  133. k.w = nil
  134. }
  135. if k.from != nil {
  136. k.from.Close()
  137. k.from = nil
  138. }
  139. if k.started && !k.exited {
  140. timeout := 2 * time.Second
  141. select {
  142. case err = <-k.wait_for_exit:
  143. k.exited = true
  144. if err != nil {
  145. k.failed = true
  146. }
  147. case <-time.After(timeout):
  148. k.failed = true
  149. err = fmt.Errorf("Timed out waiting for kitty font backend to exit for %v", timeout)
  150. }
  151. }
  152. os.Stderr.WriteString(k.stderr.String())
  153. return
  154. }