diff --git a/docs/changelog.rst b/docs/changelog.rst index 041207024..bbfe554be 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -40,6 +40,8 @@ Detailed list of changes - Text rendering: Use sRGB correct linear gamma blending for nicer font rendering and better color accuracy with transparent windows. See the option :opt:`text_composition_strategy` for details. The obsolete :opt:`macos_thicken_font` will make the font too thick and needs to be removed manually if it is configured. (:pull:`5969`) +- icat kitten: Support display of images inside tmux (:pull:`5664`) + - Graphics protocol: Add support for displaying images inside programs that do not support the protocol such as vim and tmux (:pull:`5664`) - Fix a regression in 0.27.0 that broke ``kitty @ set-font-size 0`` (:iss:`5992`) diff --git a/tools/cmd/icat/main.go b/tools/cmd/icat/main.go index 3a4aa566f..301d3a49b 100644 --- a/tools/cmd/icat/main.go +++ b/tools/cmd/icat/main.go @@ -190,7 +190,17 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) { } } - if opts.TransferMode == "detect" || opts.DetectSupport { + passthrough_mode := no_passthrough + switch opts.Passthrough { + case "tmux": + passthrough_mode = tmux_passthrough + case "detect": + if tui.TmuxSocketAddress() != "" { + passthrough_mode = tmux_passthrough + } + } + + if passthrough_mode == no_passthrough && (opts.TransferMode == "detect" || opts.DetectSupport) { memory, files, direct, err := DetectSupport(time.Duration(opts.DetectionTimeout * float64(time.Second))) if err != nil { return 1, err @@ -210,6 +220,12 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) { transfer_by_file = unsupported } } + if passthrough_mode != no_passthrough { + // tmux doesnt allow responses from the terminal so we cant detect if memory or file based transferring is supported + transfer_by_memory = unsupported + transfer_by_file = unsupported + transfer_by_stream = supported + } if opts.DetectSupport { if transfer_by_memory == supported { print_error("memory") @@ -221,9 +237,13 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) { return 0, nil } use_unicode_placeholder := opts.UnicodePlaceholder + if passthrough_mode != no_passthrough { + use_unicode_placeholder = true + } for num_of_items > 0 { imgd := <-output_channel imgd.use_unicode_placeholder = use_unicode_placeholder + imgd.passthrough_mode = passthrough_mode num_of_items-- if imgd.err != nil { print_error("Failed to process \x1b[31m%s\x1b[39m: %s\r\n", imgd.source_name, imgd.err) diff --git a/tools/cmd/icat/process_images.go b/tools/cmd/icat/process_images.go index af7323fa0..553ae470f 100644 --- a/tools/cmd/icat/process_images.go +++ b/tools/cmd/icat/process_images.go @@ -187,6 +187,7 @@ type image_data struct { move_to struct{ x, y int } width_cells, height_cells int use_unicode_placeholder bool + passthrough_mode passthrough_type // for error reporting err error diff --git a/tools/cmd/icat/transmit.go b/tools/cmd/icat/transmit.go index 38f04d3a6..a7ea13e32 100644 --- a/tools/cmd/icat/transmit.go +++ b/tools/cmd/icat/transmit.go @@ -16,6 +16,7 @@ import ( "path/filepath" "strings" + "kitty/tools/tui" "kitty/tools/tui/graphics" "kitty/tools/tui/loop" "kitty/tools/utils" @@ -25,8 +26,26 @@ import ( var _ = fmt.Print -func gc_for_image(imgd *image_data, frame_num int, frame *image_frame) *graphics.GraphicsCommand { +type passthrough_type int + +const ( + no_passthrough passthrough_type = iota + tmux_passthrough +) + +func new_graphics_command(imgd *image_data) *graphics.GraphicsCommand { gc := graphics.GraphicsCommand{} + switch imgd.passthrough_mode { + case tmux_passthrough: + gc.WrapPrefix = "\033Ptmux;" + gc.WrapSuffix = "\033\\" + gc.EncodeSerializedDataFunc = func(x string) string { return strings.ReplaceAll(x, "\033", "\033\033") } + } + return &gc +} + +func gc_for_image(imgd *image_data, frame_num int, frame *image_frame) *graphics.GraphicsCommand { + gc := new_graphics_command(imgd) gc.SetDataWidth(uint64(frame.width)).SetDataHeight(uint64(frame.height)) gc.SetQuiet(graphics.GRT_quiet_silent) gc.SetFormat(frame.transmission_format) @@ -63,7 +82,7 @@ func gc_for_image(imgd *image_data, frame_num int, frame *image_frame) *graphics } gc.SetLeftEdge(uint64(frame.left)).SetTopEdge(uint64(frame.top)) } - return &gc + return gc } func transmit_shm(imgd *image_data, frame_num int, frame *image_frame) (err error) { @@ -315,6 +334,13 @@ func transmit_image(imgd *image_data) { imgd.err = fmt.Errorf("Image too large to be displayed using Unicode placeholders. Maximum size is %dx%d cells", len(images.NumberToDiacritic), len(images.NumberToDiacritic)) return } + switch imgd.passthrough_mode { + case tmux_passthrough: + imgd.err = tui.TmuxAllowPassthrough() + if imgd.err != nil { + return + } + } fmt.Print("\r") if !imgd.use_unicode_placeholder { if imgd.move_x_by > 0 { @@ -324,7 +350,7 @@ func transmit_image(imgd *image_data) { fmt.Printf(loop.MoveCursorToTemplate, imgd.move_to.y, imgd.move_to.x) } } - frame_control_cmd := graphics.GraphicsCommand{} + frame_control_cmd := new_graphics_command(imgd) frame_control_cmd.SetAction(graphics.GRT_action_animate) if imgd.image_id != 0 { frame_control_cmd.SetImageId(imgd.image_id) diff --git a/tools/tui/graphics/command.go b/tools/tui/graphics/command.go index 6fa9f1efb..8c4473402 100644 --- a/tools/tui/graphics/command.go +++ b/tools/tui/graphics/command.go @@ -467,6 +467,9 @@ type GraphicsCommand struct { z int32 + WrapPrefix, WrapSuffix string + EncodeSerializedDataFunc func(string) string + response_message string } @@ -515,24 +518,39 @@ func (self GraphicsCommand) String() string { return "GraphicsCommand(" + strings.Join(self.serialize_non_default_fields(), ", ") + ")" } -func (self *GraphicsCommand) WriteMetadata(o io.StringWriter) (err error) { - items := self.serialize_non_default_fields() - _, err = o.WriteString(strings.Join(items, ",")) - return -} - func (self *GraphicsCommand) serialize_to(buf io.StringWriter, chunk string) (err error) { - ws := func(s string) { - _, err = buf.WriteString(s) + var ws func(string) + if self.EncodeSerializedDataFunc == nil { + ws = func(s string) { + _, err = buf.WriteString(s) + } + } else { + ws = func(s string) { + _, err = buf.WriteString(self.EncodeSerializedDataFunc(s)) + } + } + if self.WrapPrefix != "" { + _, err = buf.WriteString(self.WrapPrefix) + if err != nil { + return err + } + if self.WrapSuffix != "" { + defer func() { + if err == nil { + _, err = buf.WriteString(self.WrapSuffix) + } + }() + } } ws("\033_G") if err == nil { - err = self.WriteMetadata(buf) + items := self.serialize_non_default_fields() + ws(strings.Join(items, ",")) if err == nil { if len(chunk) > 0 { ws(";") if err == nil { - _, err = buf.WriteString(chunk) + ws(chunk) } } if err == nil { @@ -593,7 +611,9 @@ func (self *GraphicsCommand) WriteWithPayloadTo(o io.StringWriter, payload []byt if err != nil { return err } - gc = GraphicsCommand{q: self.q, a: self.a} + gc = GraphicsCommand{ + q: self.q, a: self.a, WrapPrefix: self.WrapPrefix, WrapSuffix: self.WrapSuffix, + EncodeSerializedDataFunc: self.EncodeSerializedDataFunc} } return }