backend.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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. if k.to == nil {
  60. return fmt.Errorf("Trying to send data when to pipe is nil")
  61. }
  62. data, err := json.Marshal(v)
  63. if err != nil {
  64. return fmt.Errorf("Could not encode message to kitty with error: %w", err)
  65. }
  66. c := make(chan error)
  67. go func() {
  68. if _, err = k.to.Write(data); err != nil {
  69. c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err)
  70. return
  71. }
  72. if _, err = k.to.Write([]byte{'\n'}); err != nil {
  73. c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err)
  74. return
  75. }
  76. c <- nil
  77. }()
  78. select {
  79. case err := <-c:
  80. return err
  81. case <-time.After(k.timeout):
  82. return fmt.Errorf("Timed out waiting to write to kitty font backend after %v", k.timeout)
  83. case err := <-k.wait_for_exit:
  84. k.exited = true
  85. if err == nil {
  86. err = fmt.Errorf("kitty font backend exited with no error while waiting for a response from it")
  87. } else {
  88. k.failed = true
  89. }
  90. return err
  91. }
  92. }
  93. func (k *kitty_font_backend_type) query(action string, cmd map[string]any, result any) error {
  94. k.lock.Lock()
  95. defer k.lock.Unlock()
  96. if cmd == nil {
  97. cmd = make(map[string]any)
  98. }
  99. cmd["action"] = action
  100. if err := k.send(cmd); err != nil {
  101. return err
  102. }
  103. c := make(chan error)
  104. go func() {
  105. if err := k.json_decoder.Decode(result); err != nil {
  106. c <- fmt.Errorf("Failed to decode JSON from kitty with error: %w", err)
  107. }
  108. c <- nil
  109. }()
  110. select {
  111. case err := <-c:
  112. return err
  113. case <-time.After(k.timeout):
  114. return fmt.Errorf("Timed out waiting for response from kitty font backend after %v", k.timeout)
  115. case err := <-k.wait_for_exit:
  116. k.exited = true
  117. if err == nil {
  118. err = fmt.Errorf("kitty font backed exited with no error while waiting for a response from it")
  119. } else {
  120. k.failed = true
  121. }
  122. return err
  123. }
  124. }
  125. func (k *kitty_font_backend_type) release() (err error) {
  126. if k.r != nil {
  127. k.r.Close()
  128. k.r = nil
  129. }
  130. if k.to != nil {
  131. k.to.Close()
  132. k.to = nil
  133. }
  134. if k.w != nil {
  135. k.w.Close()
  136. k.w = nil
  137. }
  138. if k.from != nil {
  139. k.from.Close()
  140. k.from = nil
  141. }
  142. if k.started && !k.exited {
  143. timeout := 2 * time.Second
  144. select {
  145. case err = <-k.wait_for_exit:
  146. k.exited = true
  147. if err != nil {
  148. k.failed = true
  149. }
  150. case <-time.After(timeout):
  151. k.failed = true
  152. err = fmt.Errorf("Timed out waiting for kitty font backend to exit for %v", timeout)
  153. }
  154. }
  155. os.Stderr.WriteString(k.stderr.String())
  156. return
  157. }