termpix 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95d3e15e1b1d9a862d73b8613665fd576c2f246e5c26949c18e1b926404bae4b
4
- data.tar.gz: d58c2201aae6c8642f587f48a74dd77695673481e3d875ae2eacd60eaa56e122
3
+ metadata.gz: 02dd8d6eb36d91f8db048b0302303bd720fbe246d0934bae5f31a4062a11f00a
4
+ data.tar.gz: 55745158a3f0657c3457c422ce8fd1bf33b7201132c921274c18ede7c49d9830
5
5
  SHA512:
6
- metadata.gz: 65a7dd630c64a04cb6982077175380e3d33d4bf81727ca623cf5d576cd4d6db2feec6106445dd47bc50b070cea5e4cff3ce40f0c57d80d9039ee6e801b98e6ff
7
- data.tar.gz: ff46fb6a49b7a3ed9429eeb41e491a36235e21216827bf638e81965d1225d772a851e6a06f6ee670786d6576548fa93188bac1faea57922213f9279524e7dca1
6
+ metadata.gz: 4871c317ab480d3df29ef922958f44fce76fddb54cd6f37d2539ce940ea3229fa754c49479fd2b0865f4b255b559a6663454a244d118b638add4e3e7341144c9
7
+ data.tar.gz: ce9f36b37d5f3e8c56c1aa5c6027da0a6b3849dc65a457465547b160f0038dc5d22268d5aea2c4cd245b2d2be390ba7d8c55b89a985655f2ee443be52cd6fe12
@@ -7,7 +7,7 @@ module Termpix
7
7
  # Uses Unicode placeholder mode (U=1) for curses/TUI compatibility
8
8
  # Images are tied to placeholder characters that curses manages as text
9
9
  module Kitty
10
- @current_image_id = nil
10
+ @active_image_ids = []
11
11
  @image_cache = {} # path -> image_id mapping
12
12
 
13
13
  def self.display(image_path, x:, y:, max_width:, max_height:)
@@ -32,8 +32,6 @@ module Termpix
32
32
  image_id = 1 if image_id == 0
33
33
  end
34
34
 
35
- old_image_id = @current_image_id
36
-
37
35
  unless @image_cache[cache_key]
38
36
  # Transmit the image with scaling
39
37
  escaped = Shellwords.escape(image_path)
@@ -50,11 +48,8 @@ module Termpix
50
48
  chunks.each_with_index do |chunk, idx|
51
49
  more = idx < chunks.length - 1 ? 1 : 0
52
50
  if idx == 0
53
- # First chunk: specify format (f=100 for PNG), action (a=t for transmit)
54
- # i=image_id, m=more_chunks
55
51
  print "\e_Ga=t,f=100,i=#{image_id},m=#{more};#{chunk}\e\\"
56
52
  else
57
- # Continuation chunks
58
53
  print "\e_Gm=#{more};#{chunk}\e\\"
59
54
  end
60
55
  end
@@ -63,30 +58,21 @@ module Termpix
63
58
  @image_cache[cache_key] = image_id
64
59
  end
65
60
 
66
- # Position cursor and display the NEW image FIRST
67
- # a=p (place), i=image_id, C=1 (don't move cursor)
68
- # Don't specify c/r - let kitty use image's native size (already scaled by ImageMagick)
69
- print "\e[#{y};#{x}H" # Move cursor to position
61
+ # Position cursor and place the image (keep existing images)
62
+ print "\e[#{y};#{x}H"
70
63
  print "\e_Ga=p,i=#{image_id},C=1\e\\"
71
64
  $stdout.flush
72
65
 
73
- # NOW delete old placement (after new one is visible) - atomic swap
74
- if old_image_id && old_image_id != image_id
75
- print "\e_Ga=d,d=i,i=#{old_image_id}\e\\"
76
- $stdout.flush
77
- end
78
-
79
- @current_image_id = image_id
66
+ @active_image_ids << image_id unless @active_image_ids.include?(image_id)
80
67
  true
81
68
  end
82
69
 
83
70
  def self.clear
84
- # Actually clear the image
85
- if @current_image_id
86
- print "\e_Ga=d,d=i,i=#{@current_image_id}\e\\"
87
- $stdout.flush
88
- @current_image_id = nil
71
+ @active_image_ids.each do |id|
72
+ print "\e_Ga=d,d=i,i=#{id}\e\\"
89
73
  end
74
+ $stdout.flush unless @active_image_ids.empty?
75
+ @active_image_ids.clear
90
76
  true
91
77
  end
92
78
 
@@ -105,7 +91,7 @@ module Termpix
105
91
  end
106
92
  # Fall back to common defaults (10x20 pixels per cell)
107
93
  [10, 20]
108
- rescue
94
+ rescue LoadError, IOError, StandardError
109
95
  [10, 20]
110
96
  end
111
97
  end
@@ -115,7 +101,7 @@ module Termpix
115
101
  def self.get_dimensions(image_path)
116
102
  escaped = Shellwords.escape(image_path)
117
103
  dimensions = `identify -format "%wx%h" #{escaped}[0] 2>/dev/null`.strip
118
- return nil if dimensions.empty?
104
+ return nil if dimensions.empty? || !dimensions.match?(/\A\d+x\d+\z/)
119
105
  dimensions.split('x').map(&:to_i)
120
106
  end
121
107
 
@@ -154,35 +140,6 @@ module Termpix
154
140
  end
155
141
  end
156
142
 
157
- # Überzug++ Protocol
158
- module Ueberzug
159
- def self.display(image_path, x:, y:, max_width:, max_height:)
160
- # Get terminal pixel dimensions
161
- terminfo = `xwininfo -id $(xdotool getactivewindow 2>/dev/null) 2>/dev/null`
162
- return unless terminfo && !terminfo.empty?
163
-
164
- term_w = terminfo.match(/Width: (\d+)/)[1].to_i
165
- term_h = terminfo.match(/Height: (\d+)/)[1].to_i
166
-
167
- # Calculate character dimensions
168
- char_w = term_w / `tput cols`.to_i
169
- char_h = term_h / `tput lines`.to_i
170
-
171
- # Convert character positions to pixels
172
- img_x = char_w * x
173
- img_y = char_h * y
174
- img_w = char_w * max_width
175
- img_h = char_h * max_height
176
-
177
- # TODO: Implement actual Überzug++ protocol
178
- # For now, placeholder
179
- end
180
-
181
- def self.clear
182
- system('clear')
183
- end
184
- end
185
-
186
143
  # w3mimgdisplay Protocol
187
144
  module W3m
188
145
  @imgdisplay = '/usr/lib/w3m/w3mimgdisplay'
@@ -192,12 +149,17 @@ module Termpix
192
149
  terminfo = `xwininfo -id $(xdotool getactivewindow 2>/dev/null) 2>/dev/null`
193
150
  return unless terminfo && !terminfo.empty?
194
151
 
195
- term_w = terminfo.match(/Width: (\d+)/)[1].to_i
196
- term_h = terminfo.match(/Height: (\d+)/)[1].to_i
152
+ w_match = terminfo.match(/Width: (\d+)/)
153
+ h_match = terminfo.match(/Height: (\d+)/)
154
+ return unless w_match && h_match
155
+ term_w = w_match[1].to_i
156
+ term_h = h_match[1].to_i
197
157
 
198
158
  # Calculate character dimensions
199
159
  cols = `tput cols`.to_i
160
+ cols = 80 if cols <= 0
200
161
  lines = `tput lines`.to_i
162
+ lines = 24 if lines <= 0
201
163
  char_w = term_w / cols
202
164
  char_h = term_h / lines
203
165
 
@@ -230,8 +192,8 @@ module Termpix
230
192
  display_path = image_path
231
193
  end
232
194
 
233
- dimensions = `identify -format "%wx%h" #{Shellwords.escape(display_path)} 2>/dev/null`.strip
234
- return if dimensions.empty?
195
+ dimensions = `identify -format "%wx%h" #{Shellwords.escape(display_path)}[0] 2>/dev/null`.strip
196
+ return if dimensions.empty? || !dimensions.match?(/\A\d+x\d+\z/)
235
197
 
236
198
  img_w, img_h = dimensions.split('x').map(&:to_i)
237
199
 
@@ -245,9 +207,8 @@ module Termpix
245
207
  end
246
208
 
247
209
  # Display using w3mimgdisplay protocol
248
- `echo '0;1;#{img_x};#{img_y};#{img_w};#{img_h};;;;;#{display_path}
249
- 4;
250
- 3;' | #{@imgdisplay} 2>/dev/null`
210
+ cmd = "0;1;#{img_x};#{img_y};#{img_w};#{img_h};;;;;#{display_path}\n4;\n3;\n"
211
+ IO.popen([@imgdisplay], 'w', err: '/dev/null') { |io| io.write(cmd) }
251
212
 
252
213
  # Don't delete temp file - keep it cached for performance
253
214
  end
@@ -257,8 +218,11 @@ module Termpix
257
218
  terminfo = `xwininfo -id $(xdotool getactivewindow 2>/dev/null) 2>/dev/null`
258
219
  return true unless terminfo && !terminfo.empty?
259
220
 
260
- term_w = terminfo.match(/Width: (\d+)/)[1].to_i
261
- term_h = terminfo.match(/Height: (\d+)/)[1].to_i
221
+ w_match = terminfo.match(/Width: (\d+)/)
222
+ h_match = terminfo.match(/Height: (\d+)/)
223
+ return true unless w_match && h_match
224
+ term_w = w_match[1].to_i
225
+ term_h = h_match[1].to_i
262
226
 
263
227
  # Calculate character dimensions
264
228
  char_w = term_w / term_width
@@ -271,7 +235,8 @@ module Termpix
271
235
  img_max_h = char_h * height + 2
272
236
 
273
237
  # Use w3mimgdisplay command "6" to clear just the image area
274
- `echo "6;#{img_x};#{img_y};#{img_max_w};#{img_max_h};\n4;\n3;" | #{@imgdisplay} 2>/dev/null`
238
+ cmd = "6;#{img_x};#{img_y};#{img_max_w};#{img_max_h};\n4;\n3;\n"
239
+ IO.popen([@imgdisplay], 'w', err: '/dev/null') { |io| io.write(cmd) }
275
240
  true
276
241
  end
277
242
  end
@@ -1,3 +1,3 @@
1
1
  module Termpix
2
- VERSION = "0.3.2"
2
+ VERSION = "0.4.1"
3
3
  end
data/lib/termpix.rb CHANGED
@@ -25,19 +25,16 @@ module Termpix
25
25
  Protocols::Kitty.display(image_path, x: x, y: y, max_width: max_width, max_height: max_height)
26
26
  when :sixel
27
27
  Protocols::Sixel.display(image_path, x: x, y: y, max_width: max_width, max_height: max_height)
28
- when :ueberzug
29
- Protocols::Ueberzug.display(image_path, x: x, y: y, max_width: max_width, max_height: max_height)
30
28
  when :w3m
31
29
  Protocols::W3m.display(image_path, x: x, y: y, max_width: max_width, max_height: max_height)
32
30
  else
33
31
  return false
34
32
  end
35
33
 
36
- @current_image = image_path
37
34
  true
38
35
  end
39
36
 
40
- # Clear the currently displayed image
37
+ # Clear all displayed images
41
38
  def clear(x: 0, y: 0, width: 80, height: 24, term_width: 80, term_height: 24)
42
39
  return false unless @protocol
43
40
 
@@ -46,13 +43,10 @@ module Termpix
46
43
  Protocols::Kitty.clear
47
44
  when :sixel
48
45
  Protocols::Sixel.clear
49
- when :ueberzug
50
- Protocols::Ueberzug.clear
51
46
  when :w3m
52
47
  Protocols::W3m.clear(x: x, y: y, width: width, height: height, term_width: term_width, term_height: term_height)
53
48
  end
54
49
 
55
- @current_image = nil
56
50
  true
57
51
  end
58
52
 
@@ -95,14 +89,6 @@ module Termpix
95
89
  return :sixel if check_dependency('convert')
96
90
  end
97
91
 
98
- # Überzug++ - disabled for now (implementation incomplete)
99
- # TODO: Implement proper Überzug++ JSON-RPC communication
100
- # if command_exists?('ueberzug') || command_exists?('ueberzugpp')
101
- # if check_dependencies('xwininfo', 'xdotool', 'identify')
102
- # return :ueberzug
103
- # end
104
- # end
105
-
106
92
  # Fall back to w3m (works on urxvt, xterm, etc.)
107
93
  if command_exists?('/usr/lib/w3m/w3mimgdisplay')
108
94
  if check_dependencies('xwininfo', 'xdotool', 'identify')
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: termpix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-30 00:00:00.000000000 Z
11
+ date: 2026-03-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: 'Termpix v0.3.2: Fixed Kitty cache invalidation when files are overwritten.
14
- Cache key now includes file mtime. Uses Kitty graphics protocol for kitty, WezTerm,
15
- and Ghostty terminals. Provides clean API for displaying images in terminal using
16
- best available protocol (Kitty, Sixel, or w3m). Auto-detects terminal capabilities
17
- and falls back gracefully.'
13
+ description: 'Termpix v0.4.1: Kitty protocol now supports multiple simultaneous images.
14
+ Track array of active image IDs instead of single image. clear() removes all active
15
+ images at once.'
18
16
  email: g@isene.com
19
17
  executables: []
20
18
  extensions: []