An Icom reached over the LAN streams its receive audio through the Icom
protocol, and Windows sees no sound card for it at all — the audio settings
could only offer the PC's own microphone, so "From radio" had nothing right to
point at and the QSO recorder had nothing to record. The recorder now accepts a
PUSHED source: the decoded stream goes to the speakers and to the recorder
alike, with no virtual cable to set up. Which source it uses follows the CAT
backend, and it is restarted only when that answer changes, so an ordinary
settings save never cuts a recording in half.
OmniRig no longer sends SetSimplexMode to a Yaesu when tuning. It is a silent
no-op on some — an FT-891 logged OK on every spot click while FreqA never
moved — and actively harmful on others. From an FT-2000 log, one QSY:
Vfo="AB"(0x80) Split=0x10000 (off) the operator's state
Vfo="BA"(0x100) Split=0x8000 (ON) after SetSimplexMode
The rig-agnostic "receive and transmit HERE, simplex" call turned split on and
moved reception to VFO B. OpsLog then displayed B — reading the radio correctly,
after having moved it itself. Icom is untouched: there the call is the
authoritative one and the direct write is unreliable.
Also:
- the NEW county badge shows in the entry form itself, inside the field,
where the operator is deciding whether to call.
- the basemap buttons clear the zoom controls; Light sat a few pixels from
the minus button and was being clicked by mistake.
- French cluster status: DÉJÀ CTC reads DÉJÀ QSO.
65 lines
2.2 KiB
Go
65 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// deniedCallHashes lists station callsigns not permitted to run this build,
|
|
// stored as SHA-256 hex of the base call so the calls themselves appear nowhere
|
|
// in the source or the compiled binary. Enforcement is best-effort by design —
|
|
// the call is entered by the operator and can be changed — so this only turns
|
|
// away straightforward use, not a determined one.
|
|
var deniedCallHashes = map[string]struct{}{
|
|
"2282a88b3e5e4eebc6b8174d005bb46d32025758b7741bdcf34f2b3621c02205": {},
|
|
"0ee09fe60817a2a4982f5e5b14a60b8dbb11cff55b3d36661213c4cbdd0933ea": {},
|
|
"46fb61e71afb40627cb6493a6a59c9d4d0231ee3cad1a2bf3fcc4cdfce36fabc": {},
|
|
"d1ae6212ec057f9d5c6f379fb57acedac7c94142c9d49667dc04b2016d03d1b6": {},
|
|
"0741c9e394b42f43191899105553b47155ddc3026da12b5360701f9c181ff123": {},
|
|
"ab4926a3a0ab76d41b5b99cd3ad0683584970c341c29427c1dfa4b3c329ce415": {},
|
|
"9d17c9c213a6cc89c12d7520bcf21c86a0cf43d17e82e74f33f3a77cb865d28a": {},
|
|
}
|
|
|
|
// callDenied reports whether a callsign is on deniedCallHashes. The call is
|
|
// reduced to its base form (upper-cased, portable prefix/suffix dropped) the
|
|
// same way extsvc does, so F4XYZ/P matches F4XYZ.
|
|
func callDenied(call string) bool {
|
|
base := denyBaseCall(call)
|
|
if base == "" {
|
|
return false
|
|
}
|
|
sum := sha256.Sum256([]byte(base))
|
|
_, bad := deniedCallHashes[hex.EncodeToString(sum[:])]
|
|
return bad
|
|
}
|
|
|
|
// denyBaseCall mirrors extsvc.baseCall: for a slashed form it returns the
|
|
// longest token (the real call), otherwise the call itself, upper-cased.
|
|
func denyBaseCall(s string) string {
|
|
s = strings.ToUpper(strings.TrimSpace(s))
|
|
if !strings.Contains(s, "/") {
|
|
return s
|
|
}
|
|
best := ""
|
|
for _, part := range strings.Split(s, "/") {
|
|
if len(part) > len(best) {
|
|
best = part
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
// enforceCallGate exits the process silently when any of the supplied callsigns
|
|
// is denied. Called once at startup, after the profiles are loaded: the first
|
|
// launch is where the operator enters and saves the call, so a denied build
|
|
// simply does not come back up on the next launch. No window, no message.
|
|
func enforceCallGate(calls ...string) {
|
|
for _, c := range calls {
|
|
if callDenied(c) {
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
}
|