detect.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package icat
  3. import (
  4. "errors"
  5. "fmt"
  6. "os"
  7. "time"
  8. "kitty/tools/tui/graphics"
  9. "kitty/tools/tui/loop"
  10. "kitty/tools/utils"
  11. "kitty/tools/utils/images"
  12. "kitty/tools/utils/shm"
  13. )
  14. var _ = fmt.Print
  15. func DetectSupport(timeout time.Duration) (memory, files, direct bool, err error) {
  16. temp_files_to_delete := make([]string, 0, 8)
  17. shm_files_to_delete := make([]shm.MMap, 0, 8)
  18. var direct_query_id, file_query_id, memory_query_id uint32
  19. lp, e := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
  20. if e != nil {
  21. err = e
  22. return
  23. }
  24. print_error := func(format string, args ...any) {
  25. lp.Println(fmt.Sprintf(format, args...))
  26. }
  27. defer func() {
  28. if len(temp_files_to_delete) > 0 && transfer_by_file != supported {
  29. for _, name := range temp_files_to_delete {
  30. os.Remove(name)
  31. }
  32. }
  33. if len(shm_files_to_delete) > 0 && transfer_by_memory != supported {
  34. for _, name := range shm_files_to_delete {
  35. _ = name.Unlink()
  36. }
  37. }
  38. }()
  39. lp.OnInitialize = func() (string, error) {
  40. var iid uint32
  41. _, _ = lp.AddTimer(timeout, false, func(loop.IdType) error {
  42. return fmt.Errorf("Timed out waiting for a response from the terminal: %w", os.ErrDeadlineExceeded)
  43. })
  44. g := func(t graphics.GRT_t, payload string) uint32 {
  45. iid += 1
  46. g1 := &graphics.GraphicsCommand{}
  47. g1.SetTransmission(t).SetAction(graphics.GRT_action_query).SetImageId(iid).SetDataWidth(1).SetDataHeight(1).SetFormat(
  48. graphics.GRT_format_rgb).SetDataSize(uint64(len(payload)))
  49. _ = g1.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(payload))
  50. return iid
  51. }
  52. direct_query_id = g(graphics.GRT_transmission_direct, "123")
  53. tf, err := images.CreateTempInRAM()
  54. if err == nil {
  55. file_query_id = g(graphics.GRT_transmission_tempfile, tf.Name())
  56. temp_files_to_delete = append(temp_files_to_delete, tf.Name())
  57. if _, err = tf.Write([]byte{1, 2, 3}); err != nil {
  58. print_error("Failed to write to temporary file for data transfer, file based transfer is disabled. Error: %v", err)
  59. }
  60. tf.Close()
  61. } else {
  62. print_error("Failed to create temporary file for data transfer, file based transfer is disabled. Error: %v", err)
  63. }
  64. sf, err := shm.CreateTemp("icat-", 3)
  65. if err == nil {
  66. memory_query_id = g(graphics.GRT_transmission_sharedmem, sf.Name())
  67. shm_files_to_delete = append(shm_files_to_delete, sf)
  68. copy(sf.Slice(), []byte{1, 2, 3})
  69. sf.Close()
  70. } else {
  71. var ens *shm.ErrNotSupported
  72. if !errors.As(err, &ens) {
  73. print_error("Failed to create SHM for data transfer, memory based transfer is disabled. Error: %v", err)
  74. }
  75. }
  76. lp.QueueWriteString("\x1b[c")
  77. return "", nil
  78. }
  79. lp.OnEscapeCode = func(etype loop.EscapeCodeType, payload []byte) (err error) {
  80. switch etype {
  81. case loop.CSI:
  82. if len(payload) > 3 && payload[0] == '?' && payload[len(payload)-1] == 'c' {
  83. lp.Quit(0)
  84. return nil
  85. }
  86. case loop.APC:
  87. g := graphics.GraphicsCommandFromAPC(payload)
  88. if g != nil {
  89. if g.ResponseMessage() == "OK" {
  90. switch g.ImageId() {
  91. case direct_query_id:
  92. direct = true
  93. case file_query_id:
  94. files = true
  95. case memory_query_id:
  96. memory = true
  97. }
  98. }
  99. return
  100. }
  101. }
  102. return
  103. }
  104. lp.OnKeyEvent = func(event *loop.KeyEvent) error {
  105. if event.MatchesPressOrRepeat("ctrl+c") {
  106. event.Handled = true
  107. print_error("Waiting for response from terminal, aborting now could lead to corruption")
  108. }
  109. if event.MatchesPressOrRepeat("ctrl+z") {
  110. event.Handled = true
  111. }
  112. return nil
  113. }
  114. err = lp.Run()
  115. if err != nil {
  116. return
  117. }
  118. ds := lp.DeathSignalName()
  119. if ds != "" {
  120. fmt.Println("Killed by signal: ", ds)
  121. lp.KillIfSignalled()
  122. return
  123. }
  124. return
  125. }