feat: added versionning & About window

This commit is contained in:
2026-06-16 19:36:56 +02:00
parent 33af122964
commit 69d0780bac
16 changed files with 1398 additions and 56 deletions
+71 -19
View File
@@ -184,6 +184,48 @@ func (m *Manager) SetPTT(on bool) error {
return m.exec(func(b Backend) error { return b.SetPTT(on) })
}
// SpotInfo is one cluster spot to render on a backend that supports a spot
// overlay (the FlexRadio panadapter). Color is an optional "#AARRGGBB" string;
// the backend picks a default when it's empty. (Status-based colouring can be
// driven later by setting Color per spot.)
type SpotInfo struct {
FreqHz int64
Callsign string
Mode string
Color string
Comment string
}
// Spotter is an OPTIONAL backend capability: show cluster spots on the radio
// (FlexRadio panadapter). Backends that don't implement it are simply skipped.
type Spotter interface {
SendSpot(SpotInfo) error
}
// SendSpot pushes a cluster spot to the backend if it supports spotting. Runs on
// the CAT goroutine and is fire-and-forget (dropped if the queue is busy) — a
// missed spot on the panadapter is harmless.
func (m *Manager) SendSpot(s SpotInfo) {
m.mu.RLock()
cmds := m.cmdCh
b := m.backend
m.mu.RUnlock()
if cmds == nil || b == nil {
return
}
if _, ok := b.(Spotter); !ok {
return
}
select {
case cmds <- func() {
if sp, ok := b.(Spotter); ok {
_ = sp.SendSpot(s)
}
}:
default: // queue busy → drop this spot
}
}
// exec marshals a backend operation onto the CAT goroutine. Returns the
// operation's error or a "busy"/"not running" error if dispatch failed.
func (m *Manager) exec(fn func(Backend) error) error {
@@ -210,23 +252,27 @@ func (m *Manager) run(b Backend, stop, done chan struct{}, cmds chan func(), pol
defer runtime.UnlockOSThread()
defer close(done)
if err := b.Connect(); err != nil {
m.update(RigState{
Enabled: true, Backend: b.Name(), Connected: false,
Error: err.Error(), UpdatedAt: time.Now(),
})
// Stay idle until Stop is called — let the user fix config and re-Start.
for {
select {
case <-stop:
return
case fn := <-cmds:
fn()
}
}
}
defer b.Disconnect()
// Connection is (re)established lazily and retried with a backoff, so a rig
// that's off at startup — or a FlexRadio that reboots/drops its TCP link —
// reconnects on its own instead of staying dead until the user toggles CAT.
const reconnectEvery = 5 * time.Second
connected := false
var lastAttempt time.Time
tryConnect := func() {
if connected || time.Since(lastAttempt) < reconnectEvery {
return
}
lastAttempt = time.Now()
if err := b.Connect(); err != nil {
m.update(RigState{Enabled: true, Backend: b.Name(), Connected: false, Error: err.Error(), UpdatedAt: time.Now()})
return
}
connected = true
}
tryConnect()
ticker := time.NewTicker(pollEvery)
defer ticker.Stop()
@@ -238,12 +284,18 @@ func (m *Manager) run(b Backend, stop, done chan struct{}, cmds chan func(), pol
fn()
m.applyCommandDelay()
case <-ticker.C:
if !connected {
tryConnect()
continue
}
ns, err := b.ReadState()
if err != nil {
m.update(RigState{
Enabled: true, Backend: b.Name(), Connected: false,
Error: err.Error(), UpdatedAt: time.Now(),
})
// Lost the rig — drop the backend so the next attempt reconnects
// cleanly, then back off before retrying.
connected = false
lastAttempt = time.Now()
b.Disconnect()
m.update(RigState{Enabled: true, Backend: b.Name(), Connected: false, Error: err.Error(), UpdatedAt: time.Now()})
continue
}
ns.Enabled = true