astropanel 4.0.0 → 4.1.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -0
  3. data/bin/astropanel +84 -65
  4. metadata +22 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 721a794f0a0c6c84cafa4dff6c93ad6741a247ece7710809735b631a3c9a782c
4
- data.tar.gz: 1ddffb11f1a0f61b2764295e846dfd80ca74497b5025ab4fdc716d632496f067
3
+ metadata.gz: d9868bc13e6a6ee12c977d549646731414134fa1a3efcc369011f4ab905ac19e
4
+ data.tar.gz: eeecdb2b16dafa1d5fb7c7eed003786ade001843be356051ccaa3d9093e19c4d
5
5
  SHA512:
6
- metadata.gz: 0ae5ab39b7eea65c3338d73e4041926a9850eb9c7b8c18241a75f530b83d7e61592a188cf0f7af029744dd53f929f23fb684a7b5c62398eef8cdc40d2055fbbf
7
- data.tar.gz: '08227fd9689b5d494518a8f465c0835c63bb082e3cd615f0f7f6c5272eb2fb117de618457ac31a48055274e6f51c0f0396fd350f5c12a45d1008781f10b103cb'
6
+ metadata.gz: f63cb7786331c386cb05cf098fb70f6e22722e2ec56c020d84fac57840769420342d16f36cd4335e199044bb1aa70d3f8880e97c3a9496509659c28d0b56fed0
7
+ data.tar.gz: 3363eab644b0fde17f2dbf0c4f5b9d2982e48527edb9355510ae3fc5c2566d90cedbec9fd76fb84719cdc733bd11a73d8042ad46e81b7758d52242720defd94e
data/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Astropanel
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/astropanel.svg)](https://badge.fury.io/rb/astropanel)
4
+ [![Ruby](https://img.shields.io/badge/Ruby-CC342D?style=flat&logo=ruby&logoColor=white)](https://www.ruby-lang.org/)
5
+ [![License](https://img.shields.io/badge/License-Public%20Domain-brightgreen.svg)](https://unlicense.org/)
6
+ [![GitHub stars](https://img.shields.io/github/stars/isene/astropanel.svg)](https://github.com/isene/astropanel/stargazers)
7
+ [![Stay Amazing](https://img.shields.io/badge/Stay-Amazing-blue.svg)](https://isene.org)
8
+
3
9
  <img src="logo.jpg" align="left" width="150" height="150">Terminal program for
4
10
  amateur astronomers with weather forecast, ephemeris, astronomical events and
5
11
  more. It's what you need to decide wether to take your telescope out for a
data/bin/astropanel CHANGED
@@ -14,7 +14,7 @@
14
14
  # for any damages resulting from its use. Further, I am under no
15
15
  # obligation to maintain or extend this software. It is provided
16
16
  # on an 'as is' basis without any expressed or implied warranty.
17
- # Version: 4.0.0: Breaking change - requires rcurses 6.0.0+ with explicit initialization
17
+ # Version: 4.1.1: Add network error handling & input validation
18
18
  # for Ruby 3.4+ compatibility
19
19
 
20
20
  # LOAD MODULES {{{1
@@ -27,6 +27,7 @@ require 'json'
27
27
  require 'date'
28
28
  require 'time'
29
29
  require 'rcurses'
30
+ require 'termpix'
30
31
  include Rcurses
31
32
  include Rcurses::Input
32
33
  include Rcurses::Cursor
@@ -558,19 +559,19 @@ class AstroPanelApp
558
559
  end
559
560
  end
560
561
 
561
- # 8) Bortle scale (0.0–9.0) {{{3
562
+ # 8) Bortle scale (1..9) {{{3
562
563
  loop do
563
- input = @footer.ask("Bortle scale (0.0–9.0): ", "")
564
+ input = @footer.ask("Bortle scale (1..9): ", "")
564
565
  if input.strip.empty?
565
566
  @footer.say("Bortle value is required")
566
567
  next
567
568
  end
568
569
  val = input.to_f
569
- if (0.0..9.0).include?(val)
570
+ if (1.0..9.0).include?(val)
570
571
  @bortle = val
571
572
  break
572
573
  else
573
- @footer.say("Enter a number between 0.0 and 9.0")
574
+ @footer.say("Enter a number between 1 and 9")
574
575
  end
575
576
  end
576
577
  end
@@ -613,34 +614,34 @@ class AstroPanelApp
613
614
  end
614
615
 
615
616
  def setup_image_display # {{{2
616
- # allow override from ~/.ap.conf
617
- @w3mimgdisplay ||= "/usr/lib/w3m/w3mimgdisplay"
618
- @showimage = File.executable?(@w3mimgdisplay)
617
+ @termpix = Termpix::Display.new
618
+ @showimage = @termpix.supported?
619
619
  @current_image = nil
620
620
  end
621
621
 
622
622
  def check_image_redraw # {{{2
623
623
  # Only check if image display is enabled
624
624
  return unless @showimage
625
-
625
+
626
626
  # Only check if we have an image currently displayed
627
627
  return unless @current_image && File.exist?(@current_image)
628
-
628
+
629
629
  begin
630
- # Check if we're in the active window - if not, image overlay might be cleared
631
- active_window = `xdotool getactivewindow 2>/dev/null`.chomp
632
- return if active_window.empty?
633
-
634
- # Check if this terminal window is the active one
635
- current_window = `xdotool getwindowfocus 2>/dev/null`.chomp
636
-
637
- # Only redraw if we detect a focus change (simple heuristic)
638
- if @last_active_window && @last_active_window != active_window && current_window == active_window
639
- # Window focus changed and we're now active - redraw image
640
- show_image(@current_image)
630
+ # For w3m protocol, check if image overlay was cleared
631
+ if @termpix.protocol == :w3m
632
+ active_window = `xdotool getactivewindow 2>/dev/null`.chomp
633
+ return if active_window.empty?
634
+
635
+ current_window = `xdotool getwindowfocus 2>/dev/null`.chomp
636
+
637
+ # Only redraw if we detect a focus change
638
+ if @last_active_window && @last_active_window != active_window && current_window == active_window
639
+ show_image(@current_image)
640
+ end
641
+
642
+ @last_active_window = active_window
641
643
  end
642
-
643
- @last_active_window = active_window
644
+ # Sixel images persist, no redraw needed
644
645
  rescue
645
646
  # Silently fail - we don't want focus checking to break anything
646
647
  end
@@ -650,47 +651,32 @@ class AstroPanelApp
650
651
  return unless @showimage
651
652
 
652
653
  begin
653
- # if anything in here takes longer than 2s, we skip it
654
654
  Timeout.timeout(2) do
655
- # grab window pixel dimensions
656
- info = `xwininfo -id $(xdotool getactivewindow) 2>/dev/null`
657
- return unless info =~ /Width:\s*(\d+).*Height:\s*(\d+)/m
658
- term_w, term_h = $1.to_i, $2.to_i
659
-
660
- # compute character‐cell size
661
655
  rows, cols = IO.console.winsize
662
- cw = term_w.to_f / cols
663
- ch = term_h.to_f / rows
664
656
 
665
- # top‐left pixel of the image
666
- px = ((@main.x - 1) * cw).to_i
667
- py = (24 * ch).to_i
657
+ # Always clear previous image first to prevent overlapping
658
+ @termpix.clear(
659
+ x: @main.x - 1,
660
+ y: 24,
661
+ width: @main.w,
662
+ height: rows - 26,
663
+ term_width: cols,
664
+ term_height: rows)
668
665
 
669
666
  if file && File.exist?(file) && File.size(file) > 0
670
- # read & scale the image
671
- iw, ih = `identify -format "%wx%h" #{file}`.split('x').map(&:to_i)
672
- max_w = ((@main.w - 3) * cw).to_i
673
- max_h = ((rows - 26) * ch).to_i
674
- if iw > max_w
675
- ih = ih * max_w / iw; iw = max_w
676
- end
677
- if ih > max_h
678
- iw = iw * max_h / ih; ih = max_h
679
- end
680
-
681
- # Clear image area
682
- `echo "6;#{px};#{py};#{max_w};#{max_h};\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
683
- `echo "0;1;#{px};#{py};#{iw};#{ih};;;;;\"#{file}\"\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
684
-
685
- # Track the current image
667
+ # Display new image using termpix (same positioning as original)
668
+ @termpix.show(file,
669
+ x: @main.x - 1,
670
+ y: 27,
671
+ max_width: @main.w - 3,
672
+ max_height: rows - 26)
686
673
  @current_image = file
687
674
  else
688
- # Clear image if no file or file doesn't exist
689
675
  @current_image = nil
690
676
  end
691
677
  end
692
678
  rescue Timeout::Error
693
- # silently skip if w3mimgdisplay/identify/xwininfo hangs
679
+ # silently skip if image display hangs
694
680
  end
695
681
  end
696
682
 
@@ -733,9 +719,15 @@ class AstroPanelApp
733
719
  end
734
720
 
735
721
  def apod # {{{2
736
- html = Net::HTTP.get(URI('https://apod.nasa.gov/apod/astropix.html'))
722
+ begin
723
+ html = Net::HTTP.get(URI('https://apod.nasa.gov/apod/astropix.html'))
724
+ rescue SocketError, Socket::ResolutionError, Timeout::Error,
725
+ Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED,
726
+ Errno::EHOSTUNREACH, Errno::ENETUNREACH, OpenURI::HTTPError => e
727
+ return @footer.say("APOD unavailable (network error: #{e.class})")
728
+ end
737
729
  img = html[/IMG SRC="(.*?)"/,1]
738
- return @footer.say("⚠️ could not parse APOD URL") unless img
730
+ return @footer.say("Could not parse APOD URL") unless img
739
731
 
740
732
  full = "https://apod.nasa.gov/apod/#{img}"
741
733
  tmp = "/tmp/apod.jpg"
@@ -745,10 +737,10 @@ class AstroPanelApp
745
737
  @current_image = tmp
746
738
  show_image(@current_image)
747
739
  else
748
- @footer.say("⚠️ empty apod.jpg")
740
+ @footer.say("Empty apod.jpg")
749
741
  end
750
742
  else
751
- @footer.say("⚠️ curl APOD failed")
743
+ @footer.say("APOD download failed")
752
744
  end
753
745
  end
754
746
 
@@ -858,8 +850,9 @@ class AstroPanelApp
858
850
  if @main.update
859
851
  update_main
860
852
  @main.update = false
853
+ show_image(@current_image) if @current_image
861
854
  end
862
-
855
+
863
856
  update_titles # Always update titles as they're lightweight
864
857
  end
865
858
 
@@ -1070,13 +1063,31 @@ class AstroPanelApp
1070
1063
  when 'END' then @index = @weather.size - 1
1071
1064
  when '?' then @main.say(HELP); getchr; @main.update = true
1072
1065
  when 'l' then @loc = @footer.ask('Loc? ', @loc); @header.update = @footer.update = true
1073
- when 'a' then @lat = @footer.ask('Lat? ', @lat.to_s).to_f; @header.update = @footer.update = true
1074
- when 'o' then @lon = @footer.ask('Lon? ', @lon.to_s).to_f; @header.update = @footer.update = true
1066
+ when 'a'
1067
+ val = @footer.ask('Lat? (-90..90) ', @lat.to_s).to_f
1068
+ if (-90.0..90.0).include?(val)
1069
+ @lat = val; @header.update = @footer.update = true
1070
+ else
1071
+ @footer.say("Latitude must be -90..90"); @footer.update = true
1072
+ end
1073
+ when 'o'
1074
+ val = @footer.ask('Lon? (-180..180) ', @lon.to_s).to_f
1075
+ if (-180.0..180.0).include?(val)
1076
+ @lon = val; @header.update = @footer.update = true
1077
+ else
1078
+ @footer.say("Longitude must be -180..180"); @footer.update = true
1079
+ end
1075
1080
  when 'c' then @cloudlimit = @footer.ask('Maximum Cloud coverage? ', @cloudlimit.to_s).to_i; @footer.update = true
1076
1081
  when 'h' then @humiditylimit = @footer.ask('Maximum Humidity? ', @humiditylimit.to_s).to_i; @footer.update = true
1077
1082
  when 't' then @templimit = @footer.ask('Minimum Temperature? ', @templimit.to_s).to_i; @footer.update = true
1078
1083
  when 'w' then @windlimit = @footer.ask('Maximum Wind? ', @windlimit.to_s).to_i; @footer.update = true
1079
- when 'b' then @bortle = @footer.ask('Bortle? ', @bortle.to_s).to_f; @footer.update = true
1084
+ when 'b'
1085
+ val = @footer.ask('Bortle? (1..9) ', @bortle.to_s).to_f
1086
+ if (1.0..9.0).include?(val)
1087
+ @bortle = val; @footer.update = true
1088
+ else
1089
+ @footer.say("Bortle must be 1..9"); @footer.update = true
1090
+ end
1080
1091
  when 'e' then show_all_events; @main.update = true
1081
1092
  when 's' then starchart; @main.update = true
1082
1093
  when 'S' then system("xdg-open /tmp/starchart.jpg &")
@@ -1211,7 +1222,14 @@ class AstroPanelApp
1211
1222
  "https://in-the-sky.org/rss.php?feed=dfan"\
1212
1223
  "&latitude=#{@lat}&longitude=#{@lon}&timezone=#{@loc}"
1213
1224
  )
1214
- raw = Net::HTTP.get(uri)
1225
+ begin
1226
+ raw = Net::HTTP.get(uri)
1227
+ rescue SocketError, Socket::ResolutionError, Timeout::Error,
1228
+ Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED,
1229
+ Errno::EHOSTUNREACH, Errno::ENETUNREACH, OpenURI::HTTPError => e
1230
+ @footer.say("Events unavailable (network error: #{e.class})")
1231
+ return
1232
+ end
1215
1233
 
1216
1234
  raw.scan(/<item>(.*?)<\/item>/m).each do |match|
1217
1235
  item = match.first
@@ -1247,9 +1265,10 @@ end
1247
1265
  # START PROGRAM {{{1
1248
1266
  begin
1249
1267
  # a single quick DNS + TCP check
1250
- TCPSocket.new('api.met.no', 443).close
1251
- rescue SocketError, Socket::ResolutionError => e
1252
- $stderr.puts "FATAL: can't resolve api.met.no – network appears down (#{e.message})"
1268
+ Timeout.timeout(5) { TCPSocket.new('api.met.no', 443).close }
1269
+ rescue SocketError, Socket::ResolutionError, Timeout::Error,
1270
+ Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH => e
1271
+ $stderr.puts "Cannot reach api.met.no, network appears down (#{e.class}: #{e.message})"
1253
1272
  $stdin.cooked!
1254
1273
  $stdin.echo = true
1255
1274
  exit!
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: astropanel
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.1.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: 2025-08-15 00:00:00.000000000 Z
11
+ date: 2026-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rcurses
@@ -24,12 +24,26 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '6.0'
27
- description: 'This program shows essential data in order to plan your observations:
28
- 9 days weather forecast, full ephemeris for the Sun, the Moon and all major planets,
29
- complete with graphic representation of rise/set times, detailed info for each day
30
- with important astronomical events, star chart displayed in the terminal and more.
31
- Version 4.0.0: Breaking change - requires rcurses 6.0.0+ with explicit initialization
32
- for Ruby 3.4+ compatibility.'
27
+ - !ruby/object:Gem::Dependency
28
+ name: termpix
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ description: 'AstroPanel v4.1.1: Modern image display using termpix gem with Sixel
42
+ and w3m protocol support. This program shows essential data in order to plan your
43
+ observations: 9 days weather forecast, full ephemeris for the Sun, the Moon and
44
+ all major planets, complete with graphic representation of rise/set times, detailed
45
+ info for each day with important astronomical events, star chart displayed in the
46
+ terminal and more.'
33
47
  email: g@isene.com
34
48
  executables:
35
49
  - astropanel