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.
- checksums.yaml +4 -4
- data/README.md +6 -0
- data/bin/astropanel +84 -65
- metadata +22 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9868bc13e6a6ee12c977d549646731414134fa1a3efcc369011f4ab905ac19e
|
|
4
|
+
data.tar.gz: eeecdb2b16dafa1d5fb7c7eed003786ade001843be356051ccaa3d9093e19c4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f63cb7786331c386cb05cf098fb70f6e22722e2ec56c020d84fac57840769420342d16f36cd4335e199044bb1aa70d3f8880e97c3a9496509659c28d0b56fed0
|
|
7
|
+
data.tar.gz: 3363eab644b0fde17f2dbf0c4f5b9d2982e48527edb9355510ae3fc5c2566d90cedbec9fd76fb84719cdc733bd11a73d8042ad46e81b7758d52242720defd94e
|
data/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Astropanel
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/astropanel)
|
|
4
|
+
[](https://www.ruby-lang.org/)
|
|
5
|
+
[](https://unlicense.org/)
|
|
6
|
+
[](https://github.com/isene/astropanel/stargazers)
|
|
7
|
+
[](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.
|
|
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 (
|
|
562
|
+
# 8) Bortle scale (1..9) {{{3
|
|
562
563
|
loop do
|
|
563
|
-
input = @footer.ask("Bortle scale (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
617
|
-
@
|
|
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
|
-
#
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
#
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
#
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
|
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
|
-
|
|
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("
|
|
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("
|
|
740
|
+
@footer.say("Empty apod.jpg")
|
|
749
741
|
end
|
|
750
742
|
else
|
|
751
|
-
@footer.say("
|
|
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'
|
|
1074
|
-
|
|
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'
|
|
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
|
-
|
|
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
|
|
1252
|
-
|
|
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.
|
|
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:
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|