feat: When a call is cleared from MSHV/WSJTx/JTDX it also clears the call field of OpsLog

This commit is contained in:
2026-07-16 11:52:42 +02:00
parent e6744c1d0a
commit 28b6f04ea4
3 changed files with 34 additions and 4 deletions
+6
View File
@@ -8887,6 +8887,12 @@ func (a *App) consumeUDPEvents() {
"source": ev.Source,
"adif": ev.LoggedADIF,
})
case ev.ClearCall:
applog.Printf("udp: emit udp:clear_call (DX Call cleared in the digital app)\n")
wruntime.EventsEmit(a.ctx, "udp:clear_call", map[string]any{
"service": string(ev.Service),
"source": ev.Source,
})
case ev.DXCall != "" && ev.Service == udp.ServiceRemoteCall:
applog.Printf("udp: emit udp:remote_call %q\n", ev.DXCall)
wruntime.EventsEmit(a.ctx, "udp:remote_call", ev.DXCall)
+7 -1
View File
@@ -1787,6 +1787,12 @@ export default function App() {
const unsubRC = EventsOn('udp:remote_call', (raw: string) => {
if (applyUdpCall(raw, true)) restartRecordingForNewTarget(String(raw ?? '')); // explicit remote pick
});
// The DX Call was cleared in WSJT-X / JTDX / MSHV → clear our entry to match.
// Only when something is actually in the entry, so an idle digital app doesn't
// wipe a call being typed by hand.
const unsubClear = EventsOn('udp:clear_call', () => {
if (callsignRef.current?.value?.trim() || callsign.trim()) resetEntry();
});
// Clicked one of OpsLog's spots on the FlexRadio panadapter → fill the call
// (the radio already tuned via trigger_action=Tune, and CAT reads the freq).
// An explicit click always wins over whatever call is currently in the field.
@@ -1811,7 +1817,7 @@ export default function App() {
else setError('UDP auto-log: ' + msg);
}
});
return () => { unsubDX?.(); unsubRC?.(); unsubFlexSpot?.(); unsubProg?.(); unsubLog?.(); };
return () => { unsubDX?.(); unsubRC?.(); unsubClear?.(); unsubFlexSpot?.(); unsubProg?.(); unsubLog?.(); };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+20 -2
View File
@@ -52,6 +52,11 @@ type Event struct {
DecodeFreqHz int64 // RF frequency (dial + audio offset)
DecodeSNR int // reported SNR (dB)
DecodeCQ bool // the decode was a CQ
// ClearCall is set when a WSJT/JTDX/MSHV Status message reports an EMPTY DX
// Call after previously reporting one — i.e. the operator cleared the call in
// the digital app. OpsLog clears its entry to match.
ClearCall bool
}
// Server is a single inbound UDP listener.
@@ -65,6 +70,7 @@ type Server struct {
mu sync.Mutex
lastDialHz int64 // WSJT: dial freq from the last Status, added to Decode offsets
lastDX string // WSJT: last non-empty DX Call seen, to detect a clear
}
func newServer(cfg Config, out chan<- Event) *Server {
@@ -213,6 +219,17 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
ev.Mode = w.Mode
ev.FreqHz = w.FreqHz
ev.LoggedADIF = w.LoggedADIF
// A Status with an empty DX Call, right after one that had a call, means the
// operator cleared it in WSJT-X / JTDX / MSHV. Fire ONE clear (tracked per
// server) — an idle app sends empty Status every second, and we must not
// re-clear (which would fight a manual entry) on each of those.
s.mu.Lock()
prev := s.lastDX
s.lastDX = w.DXCall
s.mu.Unlock()
if w.DXCall == "" && prev != "" {
ev.ClearCall = true
}
case ServiceADIF:
// JTAlert / GridTracker forward a text ADIF record after a QSO is
// logged. Guard against keep-alive / non-ADIF chatter on the socket:
@@ -268,8 +285,9 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
default:
return
}
// Empty events are useless; skip.
if ev.DXCall == "" && ev.LoggedADIF == "" && ev.DecodeCall == "" {
// Empty events are useless; skip — EXCEPT a clear signal, which is meant to be
// empty (the DX Call was cleared in the digital app).
if ev.DXCall == "" && ev.LoggedADIF == "" && ev.DecodeCall == "" && !ev.ClearCall {
return
}
select {