@@ -2,6 +2,7 @@ package image
@@ -13,6 +14,7 @@ import (
14 tea "charm.land/bubbletea/v2"
15 tea "charm.land/bubbletea/v2"
15 "github.com/charmbracelet/crush/internal/uiutil"
16 "github.com/charmbracelet/crush/internal/uiutil"
17+ uv "github.com/charmbracelet/ultraviolet"
16 "github.com/charmbracelet/x/ansi"
18 "github.com/charmbracelet/x/ansi"
17 "github.com/charmbracelet/x/ansi/kitty"
19 "github.com/charmbracelet/x/ansi/kitty"
18 "github.com/charmbracelet/x/mosaic"
20 "github.com/charmbracelet/x/mosaic"
@@ -33,6 +35,8 @@ type Capabilities struct {
33 // SupportsKittyGraphics indicates whether the terminal supports the Kitty
35 // SupportsKittyGraphics indicates whether the terminal supports the Kitty
34 // graphics protocol.
36 // graphics protocol.
35 SupportsKittyGraphics bool
37 SupportsKittyGraphics bool
38+ // Env is the terminal environment variables.
38// CellSize returns the size of a single terminal cell in pixels.
42// CellSize returns the size of a single terminal cell in pixels.
@@ -55,12 +59,15 @@ func CalculateCellSize(pixelWidth, pixelHeight, charWidth, charHeight int) CellS
56// RequestCapabilities is a [tea.Cmd] that requests the terminal to report
60// RequestCapabilities is a [tea.Cmd] that requests the terminal to report
57// its image related capabilities to the program.
61// its image related capabilities to the program.
58−func RequestCapabilities() tea.Cmd {
62+func RequestCapabilities(env uv.Environ) tea.Cmd {
60− ansi.WindowOp(14) + // Window size in pixels
63+ winOpReq := ansi.WindowOp(14) // Window size in pixels
61− // ID 31 is just a random ID used to detect Kitty graphics support.
64+ // ID 31 is just a random ID used to detect Kitty graphics support.
62− ansi.KittyGraphics([]byte("AAAA"), "i=31", "s=1", "v=1", "a=q", "t=d", "f=24"),
65+ kittyReq := ansi.KittyGraphics([]byte("AAAA"), "i=31", "s=1", "v=1", "a=q", "t=d", "f=24")
63− )
66+ if _, isTmux := env.LookupEnv("TMUX"); isTmux {
67+ kittyReq = ansi.TmuxPassthrough(kittyReq)
70+ return tea.Raw(winOpReq + kittyReq)
66// TransmittedMsg is a message indicating that an image has been transmitted to
73// TransmittedMsg is a message indicating that an image has been transmitted to
@@ -161,7 +168,7 @@ func HasTransmitted(id string, cols, rows int) bool {
162// Transmit transmits the image data to the terminal if needed. This is used to
169// Transmit transmits the image data to the terminal if needed. This is used to
163// cache the image on the terminal for later rendering.
170// cache the image on the terminal for later rendering.
164−func (e Encoding) Transmit(id string, img image.Image, cs CellSize, cols, rows int) tea.Cmd {
171+func (e Encoding) Transmit(id string, img image.Image, cs CellSize, cols, rows int, tmux bool) tea.Cmd {
165 if img == nil {
172 if img == nil {
166 return nil
173 return nil
@@ -188,13 +195,50 @@ func (e Encoding) Transmit(id string, img image.Image, cs CellSize, cols, rows i
190 var buf bytes.Buffer
197 var buf bytes.Buffer
201+ // Read single Kitty graphic chunks from the pipe and wrap them
202+ // for tmux if needed.
203+ var out bytes.Buffer
207+ n, err := rp.Read(p[:])
210+ if p[0] == ansi.ESC {
212+ } else if seenEsc && p[0] == '\\' {
213+ // End of Kitty graphics sequence
220+ if !errors.Is(err, io.EOF) {
221+ slog.Error("error reading from pipe", "err", err)
229+ seq = ansi.TmuxPassthrough(seq)
232+ buf.WriteString(seq)
191 img := fitImage(id, img, cs, cols, rows)
236 img := fitImage(id, img, cs, cols, rows)
192 bounds := img.Bounds()
237 bounds := img.Bounds()
193 imgWidth := bounds.Dx()
238 imgWidth := bounds.Dx()
194 imgHeight := bounds.Dy()
239 imgHeight := bounds.Dy()
196 imgID := int(key.Hash())
240 imgID := int(key.Hash())
197− if err := kitty.EncodeGraphics(&buf, img, &kitty.Options{
241+ if err := kitty.EncodeGraphics(wp, img, &kitty.Options{
198 ID: imgID,
242 ID: imgID,
199 Action: kitty.TransmitAndPut,
243 Action: kitty.TransmitAndPut,
200 Transmission: kitty.Direct,
244 Transmission: kitty.Direct,
@@ -205,6 +249,7 @@ func (e Encoding) Transmit(id string, img image.Image, cs CellSize, cols, rows i
205 Rows: rows,
249 Rows: rows,
206 VirtualPlacement: true,
250 VirtualPlacement: true,
207 Quite: 1,
251 Quite: 1,
208 }); err != nil {
253 }); err != nil {
209 slog.Error("failed to encode image for kitty graphics", "err", err)
254 slog.Error("failed to encode image for kitty graphics", "err", err)
210 return uiutil.ReportError(fmt.Errorf("failed to encode image"))
255 return uiutil.ReportError(fmt.Errorf("failed to encode image"))