feat(udp): Highlight Callsign and Replay — the decoder becomes log-aware
Message 13 paints callsigns in WSJT-X/JTDX's own Band Activity window with verdicts from the same cluster status cache that colours the spot grid: watchlist pink, new DXCC green, new band for its entity orange. Deduplicated per instance+call+verdict, one datagram each; a verdict that lapses (the operator worked them) clears that call, and turning the option off clears everything via the protocol's CLEARALL!. Off by default, switch in the Connections panel. Message 7 asks a program heard for the first time this session to replay the decodes already on its screen, so the FT decodes panel starts full instead of empty until the next period. Replayed lines arrive marked not-new and are shown but never auto-answered — the auto-caller now checks, on top of its 30-second freshness gate.
This commit is contained in:
@@ -156,6 +156,8 @@ type Event struct {
|
||||
DecodeModeRaw string
|
||||
// DecodeMsgRaw is the message as sent, untrimmed — see DecodeModeRaw.
|
||||
DecodeMsgRaw string
|
||||
// DecodeIsNew is false on the history a Replay resends: display-only lines.
|
||||
DecodeIsNew bool
|
||||
// ProgramID is the sending application's own id ("WSJT-X", "MSHV", or
|
||||
// "WSJT-X - 2" for a second instance started with --rig-name). It is what
|
||||
// tells two receivers apart on one multicast group — and it is the address a
|
||||
@@ -212,6 +214,9 @@ type Server struct {
|
||||
// lastFrom is the address each program's packets arrive from — where a Reply
|
||||
// has to be sent. See SendReply.
|
||||
lastFrom map[string]*net.UDPAddr
|
||||
// onNewInstance fires (off the read loop) the first time a program id is
|
||||
// heard on this listener — the hook the startup replay hangs from.
|
||||
onNewInstance func(programID string)
|
||||
// instLabel names each running application, keyed by id AND sending address.
|
||||
//
|
||||
// WSJT-X requires --rig-name for a second instance, so its ids differ. MSHV
|
||||
@@ -285,11 +290,12 @@ func describePacket(pkt []byte) string {
|
||||
|
||||
func newServer(cfg Config, out chan<- Event, mgr *Manager) *Server {
|
||||
return &Server{
|
||||
cfg: cfg,
|
||||
out: out,
|
||||
mgr: mgr,
|
||||
stop: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
cfg: cfg,
|
||||
out: out,
|
||||
mgr: mgr,
|
||||
onNewInstance: mgr.onNewInstance,
|
||||
stop: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,13 +521,23 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
// must go to the sender's own address, never to the group.
|
||||
s.mu.Lock()
|
||||
inst := s.instanceLabel(w.ProgramID, remote)
|
||||
newInstance := false
|
||||
if inst != "" && remote != nil {
|
||||
if s.lastFrom == nil {
|
||||
s.lastFrom = map[string]*net.UDPAddr{}
|
||||
}
|
||||
if _, known := s.lastFrom[inst]; !known {
|
||||
newInstance = true
|
||||
}
|
||||
s.lastFrom[inst] = remote
|
||||
}
|
||||
onNew := s.onNewInstance
|
||||
s.mu.Unlock()
|
||||
// A program just heard for the first time this session: tell the app, so
|
||||
// it can ask for a replay of the decodes already on that program's screen.
|
||||
if newInstance && onNew != nil {
|
||||
go onNew(inst)
|
||||
}
|
||||
// Status carries the current dial frequency; remember it so Decode audio
|
||||
// offsets can be turned into RF frequencies for the panadapter.
|
||||
if w.FreqHz > 0 && !w.IsDecode {
|
||||
@@ -580,6 +596,7 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
ev.DecodeModeRaw = w.Mode
|
||||
ev.DecodeMsg = w.DecodeMsg
|
||||
ev.DecodeMsgRaw = w.DecodeMsgRaw
|
||||
ev.DecodeIsNew = w.DecodeIsNew
|
||||
ev.DecodeAt = decodeTime(w.DecodeMsSinceMidnight)
|
||||
ev.DecodeTRPeriod = tr
|
||||
ev.DecodeDial = dial
|
||||
@@ -803,6 +820,10 @@ type Manager struct {
|
||||
repo *Repo
|
||||
out chan Event
|
||||
|
||||
// onNewInstance is copied onto every inbound listener as it starts; see
|
||||
// Server.onNewInstance.
|
||||
onNewInstance func(programID string)
|
||||
|
||||
// noADIFOnce keeps the "nothing to forward to" note to one line a session
|
||||
// rather than one per QSO logged.
|
||||
noADIFOnce sync.Once
|
||||
@@ -940,3 +961,11 @@ func (m *Manager) StopAll() {
|
||||
s.close()
|
||||
}
|
||||
}
|
||||
|
||||
// SetOnNewInstance installs the first-sighting hook. Call before Reload so
|
||||
// listeners are born with it.
|
||||
func (m *Manager) SetOnNewInstance(fn func(programID string)) {
|
||||
m.mu.Lock()
|
||||
m.onNewInstance = fn
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user