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
- #convert_to_png(path) ⇒ Object
-
#in_tmux? ⇒ Boolean
Check if running inside tmux.
-
#inline_images_supported? ⇒ Boolean
Check if terminal supports any inline image protocol.
-
#iterm2_protocol_supported? ⇒ Boolean
Check if terminal supports iTerm2 inline image protocol.
-
#kitty_graphics_supported? ⇒ Boolean
Check if terminal supports Kitty graphics protocol (Ghostty, Kitty).
- #png_data?(data) ⇒ Boolean
-
#print_inline_image(path, height: 1) ⇒ Object
Print an inline image using the appropriate protocol In tmux, uses passthrough sequence and cursor positioning.
-
#print_inline_image_with_text(path, text, height: 1) ⇒ Object
Print inline image with name on same line Handles tmux cursor positioning to keep image and text on same line.
- #print_iterm_image(encoded, height) ⇒ Object
-
#print_kitty_image(encoded, height) ⇒ Object
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.
-
#print_tmux_iterm_image(encoded, height) ⇒ Object
iTerm2 protocol methods.
-
#print_tmux_kitty_image(encoded, _height) ⇒ Object
Kitty graphics with Unicode placeholders for tmux (images clear/scroll with text) See: sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders.
- #read_image_data_for_protocol(path) ⇒ Object
-
#tmux_client_is_kitty_compatible? ⇒ Boolean
Check if tmux client terminal supports Kitty graphics.
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
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
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
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)
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
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_inline_image(path, height: 1) ⇒ Object
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_text(path, text, height: 1) ⇒ Object
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 |
#print_iterm_image(encoded, height) ⇒ Object
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 |
#print_kitty_image(encoded, height) ⇒ Object
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 |
#print_tmux_iterm_image(encoded, height) ⇒ Object
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 |
#print_tmux_kitty_image(encoded, _height) ⇒ Object
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
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 |