read.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package clipboard
  3. import (
  4. "bytes"
  5. "encoding/base64"
  6. "fmt"
  7. "image"
  8. "io"
  9. "os"
  10. "path/filepath"
  11. "slices"
  12. "strings"
  13. "sync"
  14. "kitty/tools/tty"
  15. "kitty/tools/tui/loop"
  16. "kitty/tools/utils"
  17. "kitty/tools/utils/images"
  18. )
  19. var _ = fmt.Print
  20. var cwd string
  21. const OSC_NUMBER = "5522"
  22. type Output struct {
  23. arg string
  24. ext string
  25. arg_is_stream bool
  26. mime_type string
  27. remote_mime_type string
  28. image_needs_conversion bool
  29. is_stream bool
  30. dest_is_tty bool
  31. dest *os.File
  32. err error
  33. started bool
  34. all_data_received bool
  35. }
  36. func (self *Output) cleanup() {
  37. if self.dest != nil {
  38. self.dest.Close()
  39. if !self.is_stream {
  40. os.Remove(self.dest.Name())
  41. }
  42. self.dest = nil
  43. }
  44. }
  45. func (self *Output) add_data(data []byte) {
  46. if self.err != nil {
  47. return
  48. }
  49. if self.dest == nil {
  50. if !self.image_needs_conversion && self.arg_is_stream {
  51. self.is_stream = true
  52. self.dest = os.Stdout
  53. if self.arg == "/dev/stderr" {
  54. self.dest = os.Stderr
  55. }
  56. self.dest_is_tty = tty.IsTerminal(self.dest.Fd())
  57. } else {
  58. d := cwd
  59. if strings.ContainsRune(self.arg, os.PathSeparator) && !self.arg_is_stream {
  60. d = filepath.Dir(self.arg)
  61. }
  62. f, err := os.CreateTemp(d, "."+filepath.Base(self.arg))
  63. if err != nil {
  64. self.err = err
  65. return
  66. }
  67. self.dest = f
  68. }
  69. self.started = true
  70. }
  71. if self.dest_is_tty {
  72. data = bytes.ReplaceAll(data, utils.UnsafeStringToBytes("\n"), utils.UnsafeStringToBytes("\r\n"))
  73. }
  74. _, self.err = self.dest.Write(data)
  75. }
  76. func (self *Output) write_image(img image.Image) (err error) {
  77. var output *os.File
  78. if self.arg_is_stream {
  79. output = os.Stdout
  80. if self.arg == "/dev/stderr" {
  81. output = os.Stderr
  82. }
  83. } else {
  84. output, err = os.Create(self.arg)
  85. if err != nil {
  86. return err
  87. }
  88. }
  89. defer func() {
  90. output.Close()
  91. if err != nil && !self.arg_is_stream {
  92. os.Remove(output.Name())
  93. }
  94. }()
  95. return images.Encode(output, img, self.mime_type)
  96. }
  97. func (self *Output) commit() {
  98. if self.err != nil {
  99. return
  100. }
  101. if self.image_needs_conversion {
  102. self.dest.Seek(0, io.SeekStart)
  103. img, _, err := image.Decode(self.dest)
  104. self.dest.Close()
  105. os.Remove(self.dest.Name())
  106. if err == nil {
  107. err = self.write_image(img)
  108. }
  109. if err != nil {
  110. self.err = fmt.Errorf("Failed to encode image data to %s with error: %w", self.mime_type, err)
  111. }
  112. } else {
  113. self.dest.Close()
  114. if !self.is_stream {
  115. f, err := os.OpenFile(self.arg, os.O_CREATE|os.O_RDONLY, 0666)
  116. if err == nil {
  117. fi, err := f.Stat()
  118. if err == nil {
  119. self.dest.Chmod(fi.Mode().Perm())
  120. }
  121. f.Close()
  122. os.Remove(f.Name())
  123. }
  124. self.err = os.Rename(self.dest.Name(), self.arg)
  125. if self.err != nil {
  126. os.Remove(self.dest.Name())
  127. self.err = fmt.Errorf("Failed to rename temporary file used for downloading to destination: %s with error: %w", self.arg, self.err)
  128. }
  129. }
  130. }
  131. self.dest = nil
  132. }
  133. func (self *Output) assign_mime_type(available_mimes []string, aliases map[string][]string) (err error) {
  134. if self.mime_type == "." {
  135. self.remote_mime_type = "."
  136. return
  137. }
  138. if slices.Contains(available_mimes, self.mime_type) {
  139. self.remote_mime_type = self.mime_type
  140. return
  141. }
  142. if len(aliases[self.mime_type]) > 0 {
  143. for _, alias := range aliases[self.mime_type] {
  144. if slices.Contains(available_mimes, alias) {
  145. self.remote_mime_type = alias
  146. return
  147. }
  148. }
  149. }
  150. for _, mt := range available_mimes {
  151. if matched, _ := filepath.Match(self.mime_type, mt); matched {
  152. self.remote_mime_type = mt
  153. return
  154. }
  155. }
  156. if images.EncodableImageTypes[self.mime_type] {
  157. for _, mt := range available_mimes {
  158. if images.DecodableImageTypes[mt] {
  159. self.remote_mime_type = mt
  160. self.image_needs_conversion = true
  161. return
  162. }
  163. }
  164. }
  165. if is_textual_mime(self.mime_type) {
  166. for _, mt := range available_mimes {
  167. if mt == "text/plain" {
  168. self.remote_mime_type = mt
  169. return
  170. }
  171. }
  172. }
  173. return fmt.Errorf("The MIME type %s for %s not available on the clipboard", self.mime_type, self.arg)
  174. }
  175. func escape_metadata_value(k, x string) (ans string) {
  176. if k == "mime" {
  177. x = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(x))
  178. }
  179. return x
  180. }
  181. func unescape_metadata_value(k, x string) (ans string) {
  182. if k == "mime" {
  183. b, err := base64.StdEncoding.DecodeString(x)
  184. if err == nil {
  185. x = string(b)
  186. }
  187. }
  188. return x
  189. }
  190. func encode_bytes(metadata map[string]string, payload []byte) string {
  191. ans := strings.Builder{}
  192. enc_payload := ""
  193. if len(payload) > 0 {
  194. enc_payload = base64.StdEncoding.EncodeToString(payload)
  195. }
  196. ans.Grow(2048 + len(enc_payload))
  197. ans.WriteString("\x1b]")
  198. ans.WriteString(OSC_NUMBER)
  199. ans.WriteString(";")
  200. for k, v := range metadata {
  201. if !strings.HasSuffix(ans.String(), ";") {
  202. ans.WriteString(":")
  203. }
  204. ans.WriteString(k)
  205. ans.WriteString("=")
  206. ans.WriteString(escape_metadata_value(k, v))
  207. }
  208. if len(payload) > 0 {
  209. ans.WriteString(";")
  210. ans.WriteString(enc_payload)
  211. }
  212. ans.WriteString("\x1b\\")
  213. return ans.String()
  214. }
  215. func encode(metadata map[string]string, payload string) string {
  216. return encode_bytes(metadata, utils.UnsafeStringToBytes(payload))
  217. }
  218. func error_from_status(status string) error {
  219. switch status {
  220. case "ENOSYS":
  221. return fmt.Errorf("no primary selection available on this system")
  222. case "EPERM":
  223. return fmt.Errorf("permission denied")
  224. case "EBUSY":
  225. return fmt.Errorf("a temporary error occurred, try again later.")
  226. default:
  227. return fmt.Errorf("%s", status)
  228. }
  229. }
  230. func parse_escape_code(etype loop.EscapeCodeType, data []byte) (metadata map[string]string, payload []byte, err error) {
  231. if etype != loop.OSC || !bytes.HasPrefix(data, utils.UnsafeStringToBytes(OSC_NUMBER+";")) {
  232. return
  233. }
  234. parts := bytes.SplitN(data, utils.UnsafeStringToBytes(";"), 3)
  235. metadata = make(map[string]string)
  236. if len(parts) > 2 && len(parts[2]) > 0 {
  237. payload, err = base64.StdEncoding.DecodeString(utils.UnsafeBytesToString(parts[2]))
  238. if err != nil {
  239. err = fmt.Errorf("Received OSC %s packet from terminal with invalid base64 encoded payload", OSC_NUMBER)
  240. return
  241. }
  242. }
  243. if len(parts) > 1 {
  244. for _, record := range bytes.Split(parts[1], utils.UnsafeStringToBytes(":")) {
  245. rp := bytes.SplitN(record, utils.UnsafeStringToBytes("="), 2)
  246. v := ""
  247. if len(rp) == 2 {
  248. v = string(rp[1])
  249. }
  250. k := string(rp[0])
  251. metadata[k] = unescape_metadata_value(k, v)
  252. }
  253. }
  254. return
  255. }
  256. func parse_aliases(raw []string) (map[string][]string, error) {
  257. ans := make(map[string][]string, len(raw))
  258. for _, x := range raw {
  259. k, v, found := strings.Cut(x, "=")
  260. if !found {
  261. return nil, fmt.Errorf("%s is not valid MIME alias specification", x)
  262. }
  263. ans[k] = append(ans[k], v)
  264. ans[v] = append(ans[v], k)
  265. }
  266. return ans, nil
  267. }
  268. func run_get_loop(opts *Options, args []string) (err error) {
  269. lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications)
  270. if err != nil {
  271. return err
  272. }
  273. var available_mimes []string
  274. var wg sync.WaitGroup
  275. var getting_data_for string
  276. requested_mimes := make(map[string]*Output)
  277. reading_available_mimes := true
  278. outputs := make([]*Output, len(args))
  279. aliases, merr := parse_aliases(opts.Alias)
  280. if merr != nil {
  281. return merr
  282. }
  283. for i, arg := range args {
  284. outputs[i] = &Output{arg: arg, arg_is_stream: arg == "/dev/stdout" || arg == "/dev/stderr", ext: filepath.Ext(arg)}
  285. if len(opts.Mime) > i {
  286. outputs[i].mime_type = opts.Mime[i]
  287. } else {
  288. if outputs[i].arg_is_stream {
  289. outputs[i].mime_type = "text/plain"
  290. } else {
  291. outputs[i].mime_type = utils.GuessMimeType(outputs[i].arg)
  292. }
  293. }
  294. if outputs[i].mime_type == "" {
  295. return fmt.Errorf("Could not detect the MIME type for: %s use --mime to specify it manually", arg)
  296. }
  297. }
  298. defer func() {
  299. for _, o := range outputs {
  300. if o.dest != nil {
  301. o.cleanup()
  302. }
  303. }
  304. }()
  305. basic_metadata := map[string]string{"type": "read"}
  306. if opts.UsePrimary {
  307. basic_metadata["loc"] = "primary"
  308. }
  309. lp.OnInitialize = func() (string, error) {
  310. lp.QueueWriteString(encode(basic_metadata, "."))
  311. return "", nil
  312. }
  313. lp.OnEscapeCode = func(etype loop.EscapeCodeType, data []byte) (err error) {
  314. metadata, payload, err := parse_escape_code(etype, data)
  315. if err != nil {
  316. return err
  317. }
  318. if metadata == nil {
  319. return nil
  320. }
  321. if reading_available_mimes {
  322. switch metadata["status"] {
  323. case "DATA":
  324. available_mimes = utils.Map(strings.TrimSpace, strings.Split(utils.UnsafeBytesToString(payload), " "))
  325. case "OK":
  326. case "DONE":
  327. reading_available_mimes = false
  328. if len(available_mimes) == 0 {
  329. return fmt.Errorf("The clipboard is empty")
  330. }
  331. for _, o := range outputs {
  332. err = o.assign_mime_type(available_mimes, aliases)
  333. if err != nil {
  334. return err
  335. }
  336. if o.remote_mime_type == "." {
  337. o.started = true
  338. o.add_data(utils.UnsafeStringToBytes(strings.Join(available_mimes, "\n")))
  339. o.all_data_received = true
  340. } else {
  341. requested_mimes[o.remote_mime_type] = o
  342. }
  343. }
  344. if len(requested_mimes) > 0 {
  345. lp.QueueWriteString(encode(basic_metadata, strings.Join(utils.Keys(requested_mimes), " ")))
  346. } else {
  347. lp.Quit(0)
  348. }
  349. default:
  350. return fmt.Errorf("Failed to read list of available data types in the clipboard with error: %w", error_from_status(metadata["status"]))
  351. }
  352. } else {
  353. switch metadata["status"] {
  354. case "DATA":
  355. current_mime := metadata["mime"]
  356. o := requested_mimes[current_mime]
  357. if o != nil {
  358. if getting_data_for != current_mime {
  359. if prev := requested_mimes[getting_data_for]; prev != nil && !prev.all_data_received {
  360. prev.all_data_received = true
  361. wg.Add(1)
  362. go func() {
  363. prev.commit()
  364. wg.Done()
  365. }()
  366. }
  367. getting_data_for = current_mime
  368. }
  369. if !o.all_data_received {
  370. o.add_data(payload)
  371. }
  372. }
  373. case "OK":
  374. case "DONE":
  375. if prev := requested_mimes[getting_data_for]; getting_data_for != "" && prev != nil && !prev.all_data_received {
  376. prev.all_data_received = true
  377. wg.Add(1)
  378. go func() {
  379. prev.commit()
  380. wg.Done()
  381. }()
  382. getting_data_for = ""
  383. }
  384. lp.Quit(0)
  385. default:
  386. return fmt.Errorf("Failed to read data from the clipboard with error: %w", error_from_status(metadata["status"]))
  387. }
  388. }
  389. return
  390. }
  391. esc_count := 0
  392. lp.OnKeyEvent = func(event *loop.KeyEvent) error {
  393. if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") {
  394. event.Handled = true
  395. esc_count++
  396. if esc_count < 2 {
  397. key := "Esc"
  398. if event.MatchesPressOrRepeat("ctrl+c") {
  399. key = "Ctrl+C"
  400. }
  401. lp.QueueWriteString(fmt.Sprintf("Waiting for response from terminal, press %s again to abort. This could cause garbage to be spewed to the screen.\r\n", key))
  402. } else {
  403. return fmt.Errorf("Aborted by user!")
  404. }
  405. }
  406. return nil
  407. }
  408. err = lp.Run()
  409. wg.Wait()
  410. if err != nil {
  411. return
  412. }
  413. ds := lp.DeathSignalName()
  414. if ds != "" {
  415. fmt.Println("Killed by signal: ", ds)
  416. lp.KillIfSignalled()
  417. return
  418. }
  419. for _, o := range outputs {
  420. if o.err != nil {
  421. err = fmt.Errorf("Failed to get %s with error: %w", o.arg, o.err)
  422. return
  423. }
  424. if !o.started {
  425. err = fmt.Errorf("No data for %s with MIME type: %s", o.arg, o.mime_type)
  426. return
  427. }
  428. }
  429. return
  430. }