Module: Slk::Support::InlineImages

Included in:
Commands::Emoji, Commands::Later, Commands::Messages, Commands::Preset, Commands::Status
Defined in:
lib/slk/support/inline_images.rb

Overview

Shared module for inline image display in terminals supporting iTerm2 protocol (iTerm2/WezTerm/Mintty) or Kitty graphics protocol (Ghostty/Kitty) Includes special handling for tmux passthrough sequences

Instance Method Summary collapse

Instance Method Details

#convert_to_png(path) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/slk/support/inline_images.rb', line 88

def convert_to_png(path)
  # Use sips (macOS) to convert to PNG
  require 'tempfile'
  temp = Tempfile.new(['emoji', '.png'])
  temp.close

  system('sips', '-s', 'format', 'png', path, '--out', temp.path,
         out: File::NULL, err: File::NULL)

  return nil unless File.exist?(temp.path) && File.size(temp.path).positive?

  File.binread(temp.path)
ensure
  temp&.unlink
end

#in_tmux?Boolean

Check if running inside tmux

Returns:

  • (Boolean)


47
48
49
50
# File 'lib/slk/support/inline_images.rb', line 47

def in_tmux?
  # tmux sets TERM to screen-* or tmux-*
  ENV['TERM']&.include?('screen') || ENV['TERM']&.start_with?('tmux')
end

#inline_images_supported?Boolean

Check if terminal supports any inline image protocol

Returns:

  • (Boolean)


10
11
12
# File 'lib/slk/support/inline_images.rb', line 10

def inline_images_supported?
  iterm2_protocol_supported? || kitty_graphics_supported?
end

#iterm2_protocol_supported?Boolean

Check if terminal supports iTerm2 inline image protocol

Returns:

  • (Boolean)


15
16
17
18
19
20
21
22
# File 'lib/slk/support/inline_images.rb', line 15

def iterm2_protocol_supported?
  # iTerm2, WezTerm, Mintty support inline images
  # LC_TERMINAL persists through tmux/ssh
  ['iTerm.app', 'WezTerm'].include?(ENV.fetch('TERM_PROGRAM', nil)) ||
    ENV['LC_TERMINAL'] == 'iTerm2' ||
    ENV['LC_TERMINAL'] == 'WezTerm' ||
    ENV['TERM'] == 'mintty'
end

#kitty_graphics_supported?Boolean

Check if terminal supports Kitty graphics protocol (Ghostty, Kitty)

Returns:

  • (Boolean)


25
26
27
28
29
30
# File 'lib/slk/support/inline_images.rb', line 25

def kitty_graphics_supported?
  ENV['TERM_PROGRAM'] == 'ghostty' ||
    ENV['GHOSTTY_RESOURCES_DIR'] ||
    ENV['TERM']&.include?('kitty') ||
    tmux_client_is_kitty_compatible?
end

#png_data?(data) ⇒ Boolean

Returns:

  • (Boolean)


83
84
85
86
# File 'lib/slk/support/inline_images.rb', line 83

def png_data?(data)
  # PNG files start with magic bytes: 137 80 78 71 13 10 26 10
  data[0, 8]&.bytes == [137, 80, 78, 71, 13, 10, 26, 10]
end

Print an inline image using the appropriate protocol In tmux, uses passthrough sequence and cursor positioning



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/slk/support/inline_images.rb', line 54

def print_inline_image(path, height: 1)
  data = read_image_data_for_protocol(path)
  return unless data

  encoded = [data].pack('m0')

  if kitty_graphics_supported?
    in_tmux? ? print_tmux_kitty_image(encoded, height) : print_kitty_image(encoded, height)
  else
    in_tmux? ? print_tmux_iterm_image(encoded, height) : print_iterm_image(encoded, height)
  end
end

Print inline image with name on same line Handles tmux cursor positioning to keep image and text on same line



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/slk/support/inline_images.rb', line 137

def print_inline_image_with_text(path, text, height: 1) # rubocop:disable Naming/PredicateMethod
  return false unless inline_images_supported? && File.exist?(path)

  print_inline_image(path, height: height)

  if in_tmux? && !kitty_graphics_supported?
    # iTerm2 in tmux: image ends with \n + space, cursor on next line
    # Move up 1 line, right 3 cols (past image), then print text
    print "\e[1A\e[3C#{text}\n"
  else
    # Direct output or Unicode placeholders (regular text, just print after)
    puts " #{text}"
  end

  true
end


111
112
113
# File 'lib/slk/support/inline_images.rb', line 111

def print_iterm_image(encoded, height)
  printf "\e]1337;File=inline=1;height=%<height>d:%<data>s\a", height: height, data: encoded
end

Kitty graphics protocol methods (Ghostty, Kitty) a=T: transmit and display, q=1: suppress OK response, f=100: PNG, r=rows, m=0: no more chunks



117
118
119
# File 'lib/slk/support/inline_images.rb', line 117

def print_kitty_image(encoded, height)
  printf "\e_Ga=T,q=1,f=100,r=%<height>d,m=0;%<data>s\e\\", height: height, data: encoded
end

iTerm2 protocol methods



105
106
107
108
109
# File 'lib/slk/support/inline_images.rb', line 105

def print_tmux_iterm_image(encoded, height)
  fmt = "\ePtmux;\e\e]1337;File=inline=1;preserveAspectRatio=0;" \
        "size=%<size>d;height=%<height>d:%<data>s\a\e\\\n "
  printf fmt, size: encoded.length, height: height, data: encoded
end

Kitty graphics with Unicode placeholders for tmux (images clear/scroll with text) See: sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/slk/support/inline_images.rb', line 123

def print_tmux_kitty_image(encoded, _height)
  @kitty_image_id ||= 30
  @kitty_image_id = (@kitty_image_id % 255) + 1
  image_id = @kitty_image_id

  # tmux passthrough: transmit image with Unicode placeholder mode (U=1), 2 cols x 1 row
  $stdout.print "\ePtmux;\e\e_Ga=T,U=1,q=1,f=100,i=#{image_id},c=2,r=1,m=0;#{encoded}\e\e\\\e\\"
  # Output placeholder cells (U+10EEEE + row/col diacritics) with foreground color = image_id
  $stdout.print "\e[38;5;#{image_id}m\u{10EEEE}\u0305\u0305\u{10EEEE}\u0305\u030D\e[39m"
  $stdout.flush
end

#read_image_data_for_protocol(path) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/slk/support/inline_images.rb', line 67

def read_image_data_for_protocol(path)
  return nil unless File.exist?(path)

  data = File.binread(path)
  return nil unless data

  # Kitty protocol requires PNG format; convert GIF/JPEG if needed
  if kitty_graphics_supported? && !png_data?(data)
    convert_to_png(path)
  else
    data
  end
rescue IOError, SystemCallError
  nil
end

#tmux_client_is_kitty_compatible?Boolean

Check if tmux client terminal supports Kitty graphics

Returns:

  • (Boolean)


33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/slk/support/inline_images.rb', line 33

def tmux_client_is_kitty_compatible?
  return false unless in_tmux?

  @tmux_client_is_kitty_compatible ||= begin
    output = begin
      `tmux display-message -p '\#{client_termname}'`.chomp
    rescue StandardError
      ''
    end
    output.include?('ghostty') || output.include?('kitty')
  end
end