Module: Kward::TerminalImageSupport
- Defined in:
- lib/kward/terminal_image_support.rb
Overview
Terminal capability detection for inline image protocols.
Constant Summary collapse
- KITTY =
:kitty- ITERM2 =
:iterm2- PROBE_TIMEOUT =
0.3- KITTY_PROBE =
"#{TerminalSequences.kitty_query}\e[c".freeze
- KITTY_OK_RESPONSE =
"\e_Gi=31;OK\e\\".freeze
- DA1_RESPONSE_PATTERN =
/\e\[\?[0-9;]*c/.freeze
Class Method Summary collapse
- .da1_response?(response) ⇒ Boolean
- .detect(env: ENV, input: nil, output: nil, timeout: PROBE_TIMEOUT, probe_result: :auto) ⇒ Object
- .iterm2_hint?(env) ⇒ Boolean
- .kitty_hint?(env) ⇒ Boolean
- .kitty_probe_success?(response) ⇒ Boolean
- .monotonic_now ⇒ Object
- .normalized_environment(env) ⇒ Object
- .probe_kitty(input, output, env: ENV, timeout: PROBE_TIMEOUT) ⇒ Object
- .read_probe_response(input, timeout: PROBE_TIMEOUT) ⇒ Object
- .static_protocol(env) ⇒ Object
- .tty?(io) ⇒ Boolean
Class Method Details
.da1_response?(response) ⇒ Boolean
78 79 80 |
# File 'lib/kward/terminal_image_support.rb', line 78 def da1_response?(response) response.to_s.match?(DA1_RESPONSE_PATTERN) end |
.detect(env: ENV, input: nil, output: nil, timeout: PROBE_TIMEOUT, probe_result: :auto) ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 |
# File 'lib/kward/terminal_image_support.rb', line 16 def detect(env: ENV, input: nil, output: nil, timeout: PROBE_TIMEOUT, probe_result: :auto) return static_protocol(env) if input.nil? && output.nil? && probe_result == :auto return nil unless tty?(input) && tty?(output) probe_result = probe_kitty(input, output, env: env, timeout: timeout) if probe_result == :auto return KITTY if probe_result == true fallback = static_protocol(env) return fallback if probe_result.nil? || fallback nil end |
.iterm2_hint?(env) ⇒ Boolean
91 92 93 94 95 96 97 98 |
# File 'lib/kward/terminal_image_support.rb', line 91 def iterm2_hint?(env) value = normalized_environment(env) program = value.fetch("TERM_PROGRAM", "") return true if program == "iterm.app" || program == "wezterm" return true if value.fetch("TERM_FEATURES", "").include?("f") value["WEZTERM_PANE"] && !value["WEZTERM_PANE"].empty? end |
.kitty_hint?(env) ⇒ Boolean
82 83 84 85 86 87 88 89 |
# File 'lib/kward/terminal_image_support.rb', line 82 def kitty_hint?(env) value = normalized_environment(env) return true if value["KITTY_WINDOW_ID"] && !value["KITTY_WINDOW_ID"].empty? program = value.fetch("TERM_PROGRAM", "") term = value.fetch("TERM", "") program == "kitty" || program == "ghostty" || term.include?("kitty") || term.include?("ghostty") end |
.kitty_probe_success?(response) ⇒ Boolean
74 75 76 |
# File 'lib/kward/terminal_image_support.rb', line 74 def kitty_probe_success?(response) da1_response?(response) && response.to_s.include?(KITTY_OK_RESPONSE) end |
.monotonic_now ⇒ Object
112 113 114 |
# File 'lib/kward/terminal_image_support.rb', line 112 def monotonic_now Process.clock_gettime(Process::CLOCK_MONOTONIC) end |
.normalized_environment(env) ⇒ Object
100 101 102 103 104 |
# File 'lib/kward/terminal_image_support.rb', line 100 def normalized_environment(env) env.to_h.each_with_object({}) do |(key, value), normalized| normalized[key.to_s] = value.to_s.strip.downcase end end |
.probe_kitty(input, output, env: ENV, timeout: PROBE_TIMEOUT) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/kward/terminal_image_support.rb', line 36 def probe_kitty(input, output, env: ENV, timeout: PROBE_TIMEOUT) return nil unless tty?(input) && tty?(output) return nil unless input.respond_to?(:raw) return nil unless output.respond_to?(:write) && output.respond_to?(:flush) query = env["TMUX"].to_s.empty? ? KITTY_PROBE : TerminalSequences.tmux_passthrough(KITTY_PROBE) input.raw do output.write(query) output.flush read_probe_response(input, timeout: timeout) end rescue StandardError nil end |
.read_probe_response(input, timeout: PROBE_TIMEOUT) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/kward/terminal_image_support.rb', line 51 def read_probe_response(input, timeout: PROBE_TIMEOUT) buffer = +"" deadline = monotonic_now + timeout.to_f loop do break if da1_response?(buffer) remaining = deadline - monotonic_now break if remaining <= 0 ready = IO.select([input], nil, nil, remaining) break unless ready chunk = input.read_nonblock(256, exception: false) break if chunk == :wait_readable || chunk.nil? || chunk.empty? buffer << chunk end kitty_probe_success?(buffer) rescue IOError, SystemCallError false end |
.static_protocol(env) ⇒ Object
29 30 31 32 33 34 |
# File 'lib/kward/terminal_image_support.rb', line 29 def static_protocol(env) return KITTY if kitty_hint?(env) return ITERM2 if iterm2_hint?(env) nil end |
.tty?(io) ⇒ Boolean
106 107 108 109 110 |
# File 'lib/kward/terminal_image_support.rb', line 106 def tty?(io) io.respond_to?(:tty?) && io.tty? rescue IOError, SystemCallError false end |