Let the operator edit the station config in the window instead of hand-editing the file and relaunching.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-11 19:19:05 +02:00
co-authored by Cursor
parent e642b24be0
commit 775d1b5c82
12 changed files with 1567 additions and 179 deletions
+62 -8
View File
@@ -36,17 +36,37 @@ func (s *Station) Run(ctx context.Context) error {
return nil
}
s.mu.Lock()
if s.fatal != "" {
if s.fatal != "" || !s.loaded {
s.mu.Unlock()
<-ctx.Done()
return nil
select {
case <-ctx.Done():
return nil
case <-s.wake:
}
continue
}
s.kick = false
s.state.Connection = "Connecting"
s.publish()
host, port := s.cfg.host, s.cfg.port
epoch := s.epoch
dialCtx, dialCancel := context.WithCancel(ctx)
s.cancelDial = dialCancel
s.mu.Unlock()
conn, err := dialStation(ctx, host, port)
conn, err := dialStation(dialCtx, host, port)
dialCancel()
s.mu.Lock()
s.cancelDial = nil
stale := s.epoch != epoch
s.mu.Unlock()
if stale {
if conn != nil {
conn.Close()
}
s.drainApply()
continue
}
if err != nil {
if ctx.Err() != nil {
return nil
@@ -61,17 +81,28 @@ func (s *Station) Run(ctx context.Context) error {
continue
}
err = s.session(ctx, conn, major)
err = s.session(ctx, conn, major, epoch)
conn.Close()
fallback := errors.Is(err, errFallback)
reconnect := errors.Is(err, errReconnect)
s.mu.Lock()
s.closeUDP()
if s.conn == conn {
s.conn = nil
}
s.mic = nil
s.play = nil
s.audioHeard = false
s.flushWaiting = false
if fallback || reconnect {
kicked := s.kick
if kicked {
s.kick = false
s.drainApply()
if s.up {
s.noteDownLocked(s.clock.Now(), false)
}
}
if fallback || reconnect || kicked {
s.roomBusy = false
s.remoteTalker = ""
s.mu.Unlock()
@@ -120,6 +151,8 @@ func (s *Station) wait(ctx context.Context, d time.Duration) bool {
return false
case <-timer.C():
return true
case <-s.apply:
return true
}
}
@@ -263,7 +296,21 @@ const (
phChallenge
)
func (s *Station) session(ctx context.Context, conn net.Conn, major uint16) error {
func (s *Station) session(ctx context.Context, conn net.Conn, major uint16, epoch uint64) error {
s.mu.Lock()
s.conn = conn
stale := s.epoch != epoch
s.mu.Unlock()
if stale {
return errReconnect
}
defer func() {
s.mu.Lock()
if s.conn == conn {
s.conn = nil
}
s.mu.Unlock()
}()
done := make(chan struct{})
var closeOnce sync.Once
closeConn := func() { closeOnce.Do(func() { conn.Close() }) }
@@ -721,7 +768,7 @@ func (s *Station) closeUDP() {
}
func (s *Station) transmitting() bool {
if s.fatal != "" || s.state.Connection != "Connected" || s.roomBusy {
if s.configOpen || s.fatal != "" || s.state.Connection != "Connected" || s.roomBusy {
return false
}
return s.buttonHeld || s.spaceHeld
@@ -731,6 +778,8 @@ func (s *Station) publish() {
s.noteTransmitLocked()
s.state.Callsign = s.cfg.callsign
s.state.TalkGroup = s.cfg.talkGroup
s.state.NeedsConfig = !s.loaded
s.state.ConfigOpen = s.configOpen
if s.fatal != "" {
s.state.Connection = "Disconnected"
s.state.Detail = s.fatal
@@ -764,6 +813,11 @@ func (s *Station) publish() {
}
}
}
if s.configOpen && s.configErr != "" {
s.state.Detail = s.configErr
} else if s.openBlocked != "" {
s.state.Detail = s.openBlocked
}
select {
case s.wake <- struct{}{}:
default: