qsl designer
This commit is contained in:
@@ -18,27 +18,28 @@ import (
|
||||
|
||||
"hamlog/internal/adif"
|
||||
"hamlog/internal/applog"
|
||||
"hamlog/internal/backup"
|
||||
"hamlog/internal/audio"
|
||||
"hamlog/internal/cat"
|
||||
"hamlog/internal/clublog"
|
||||
"hamlog/internal/award"
|
||||
"hamlog/internal/awardref"
|
||||
"hamlog/internal/backup"
|
||||
"hamlog/internal/cat"
|
||||
"hamlog/internal/clublog"
|
||||
"hamlog/internal/cluster"
|
||||
"hamlog/internal/pota"
|
||||
"hamlog/internal/db"
|
||||
"hamlog/internal/dxcc"
|
||||
"hamlog/internal/email"
|
||||
"hamlog/internal/extsvc"
|
||||
"hamlog/internal/integrations/udp"
|
||||
"hamlog/internal/operating"
|
||||
"hamlog/internal/dxcc"
|
||||
"hamlog/internal/lookup"
|
||||
"hamlog/internal/operating"
|
||||
"hamlog/internal/pota"
|
||||
"hamlog/internal/profile"
|
||||
"hamlog/internal/qslcard"
|
||||
"hamlog/internal/qso"
|
||||
"hamlog/internal/rotator/pst"
|
||||
"hamlog/internal/settings"
|
||||
"hamlog/internal/ultrabeam"
|
||||
"hamlog/internal/winkeyer"
|
||||
"hamlog/internal/settings"
|
||||
|
||||
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go.bug.st/serial"
|
||||
@@ -72,11 +73,11 @@ const (
|
||||
keyListsRSTDigital = "lists.rst_digital"
|
||||
|
||||
keyCATEnabled = "cat.enabled"
|
||||
keyCATBackend = "cat.backend" // "omnirig" (only one for now)
|
||||
keyCATOmniRigNum = "cat.omnirig.rig" // 1 or 2
|
||||
keyCATBackend = "cat.backend" // "omnirig" (only one for now)
|
||||
keyCATOmniRigNum = "cat.omnirig.rig" // 1 or 2
|
||||
keyCATPollMs = "cat.poll_ms"
|
||||
keyCATDelayMs = "cat.delay_ms" // pause between commands
|
||||
keyCATDigitalDefault = "cat.digital_default" // mode to use when CAT reports DATA
|
||||
keyCATDelayMs = "cat.delay_ms" // pause between commands
|
||||
keyCATDigitalDefault = "cat.digital_default" // mode to use when CAT reports DATA
|
||||
|
||||
// Audio (Digital Voice Keyer + QSO recorder). Machine-local hardware, so
|
||||
// global (not per-profile) like CAT/rotator. Device fields store the
|
||||
@@ -118,10 +119,10 @@ const (
|
||||
// file download. Visible in the binary but must not be exposed publicly.
|
||||
clublogAppAPIKey = "5767f19333363a9ef432ee9cd4141fe76b8adf38"
|
||||
|
||||
keyRotatorEnabled = "rotator.enabled"
|
||||
keyRotatorHost = "rotator.host"
|
||||
keyRotatorPort = "rotator.port"
|
||||
keyRotatorHasElevation = "rotator.has_elevation"
|
||||
keyRotatorEnabled = "rotator.enabled"
|
||||
keyRotatorHost = "rotator.host"
|
||||
keyRotatorPort = "rotator.port"
|
||||
keyRotatorHasElevation = "rotator.has_elevation"
|
||||
|
||||
// Ultrabeam antenna (TCP, e.g. via an RS232↔Ethernet adapter) — Hardware → Antenna.
|
||||
keyUltrabeamEnabled = "ultrabeam.enabled"
|
||||
@@ -146,10 +147,10 @@ const (
|
||||
keyWKAutoSpace = "winkeyer.autospace"
|
||||
keyWKUsePTT = "winkeyer.use_ptt"
|
||||
keyWKSerialEcho = "winkeyer.serial_echo"
|
||||
keyWKMacros = "winkeyer.macros" // JSON array of {label,text}
|
||||
keyWKEngine = "winkeyer.engine" // "winkeyer" | "tci"
|
||||
keyWKMacros = "winkeyer.macros" // JSON array of {label,text}
|
||||
keyWKEngine = "winkeyer.engine" // "winkeyer" | "tci"
|
||||
keyWKEscClears = "winkeyer.esc_clears_call" // ESC also clears the callsign
|
||||
keyWKSendOnType = "winkeyer.send_on_type" // key characters live as typed
|
||||
keyWKSendOnType = "winkeyer.send_on_type" // key characters live as typed
|
||||
|
||||
keyClusterAutoConnect = "cluster.auto_connect" // open every enabled server at app start
|
||||
|
||||
@@ -172,10 +173,10 @@ const (
|
||||
|
||||
// External services (logbook upload). QRZ.com first; Clublog / LoTW
|
||||
// will add their own keys under the same extsvc.* prefix.
|
||||
keyExtQRZAPIKey = "extsvc.qrz.api_key"
|
||||
keyExtQRZForceCall = "extsvc.qrz.force_station_callsign"
|
||||
keyExtQRZAutoUpload = "extsvc.qrz.auto_upload"
|
||||
keyExtQRZUploadMode = "extsvc.qrz.upload_mode"
|
||||
keyExtQRZAPIKey = "extsvc.qrz.api_key"
|
||||
keyExtQRZForceCall = "extsvc.qrz.force_station_callsign"
|
||||
keyExtQRZAutoUpload = "extsvc.qrz.auto_upload"
|
||||
keyExtQRZUploadMode = "extsvc.qrz.upload_mode"
|
||||
|
||||
keyExtClublogEmail = "extsvc.clublog.email"
|
||||
keyExtClublogPassword = "extsvc.clublog.password"
|
||||
@@ -240,11 +241,11 @@ type ModePreset struct {
|
||||
// ListsSettings holds the user-customisable dropdown lists used by the
|
||||
// entry form. Default values match common HF/VHF practice.
|
||||
type ListsSettings struct {
|
||||
Bands []string `json:"bands"`
|
||||
Modes []ModePreset `json:"modes"`
|
||||
RSTPhone []string `json:"rst_phone"` // RS reports for phone modes
|
||||
RSTCW []string `json:"rst_cw"` // RST reports for CW/RTTY/PSK
|
||||
RSTDigital []string `json:"rst_digital"` // dB reports for FT8/FT4/JT…
|
||||
Bands []string `json:"bands"`
|
||||
Modes []ModePreset `json:"modes"`
|
||||
RSTPhone []string `json:"rst_phone"` // RS reports for phone modes
|
||||
RSTCW []string `json:"rst_cw"` // RST reports for CW/RTTY/PSK
|
||||
RSTDigital []string `json:"rst_digital"` // dB reports for FT8/FT4/JT…
|
||||
}
|
||||
|
||||
var defaultBands = []string{
|
||||
@@ -322,51 +323,52 @@ type StationSettings struct {
|
||||
// Primary / Failsafe hold a provider name ("qrz" | "hamqth" | "") to
|
||||
// route lookups: primary first, failsafe on not-found / error.
|
||||
type LookupSettings struct {
|
||||
QRZUser string `json:"qrz_user"`
|
||||
QRZPassword string `json:"qrz_password"`
|
||||
HamQTHUser string `json:"hamqth_user"`
|
||||
HamQTHPassword string `json:"hamqth_password"`
|
||||
Primary string `json:"primary"`
|
||||
Failsafe string `json:"failsafe"`
|
||||
DownloadImages bool `json:"download_images"` // show QRZ profile pictures in the UI
|
||||
CacheTTLDays int `json:"cache_ttl_days"`
|
||||
QRZUser string `json:"qrz_user"`
|
||||
QRZPassword string `json:"qrz_password"`
|
||||
HamQTHUser string `json:"hamqth_user"`
|
||||
HamQTHPassword string `json:"hamqth_password"`
|
||||
Primary string `json:"primary"`
|
||||
Failsafe string `json:"failsafe"`
|
||||
DownloadImages bool `json:"download_images"` // show QRZ profile pictures in the UI
|
||||
CacheTTLDays int `json:"cache_ttl_days"`
|
||||
}
|
||||
|
||||
// App is the application context bound to the Wails runtime.
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
db *sql.DB
|
||||
qso *qso.Repo
|
||||
settings *settings.Store
|
||||
profiles *profile.Repo
|
||||
lookup *lookup.Manager
|
||||
cache *lookup.Cache
|
||||
cat *cat.Manager
|
||||
dxcc *dxcc.Manager
|
||||
cluster *cluster.Manager
|
||||
pota *pota.Cache
|
||||
awardRefs *awardref.Repo
|
||||
operating *operating.Repo
|
||||
udp *udp.Manager
|
||||
udpRepo *udp.Repo
|
||||
extsvc *extsvc.Manager
|
||||
winkeyer *winkeyer.Manager
|
||||
clublog *clublog.Manager
|
||||
ultrabeam *ultrabeam.Client // Ultrabeam antenna (TCP); nil when disabled
|
||||
ubFollowStop chan struct{} // stops the "follow frequency" loop; nil when off
|
||||
audioMgr *audio.Manager
|
||||
qsoRec *audio.Recorder // continuous QSO recorder (rolling pre-roll)
|
||||
dvkRecSlot int // slot currently being recorded (DVKStartRecord → DVKStopRecord)
|
||||
dvkPttKeyed bool // we keyed PTT for a voice message; unkey when it ends
|
||||
pttMu sync.Mutex
|
||||
udpLogMu sync.Mutex // serialises UDP auto-log so concurrent packets can't both pass the dedup check
|
||||
pttPort serial.Port // open serial port while PTT (RTS/DTR) is asserted
|
||||
pttKeyedMethod string // "cat" | "rts" | "dtr" while keyed; "" when idle
|
||||
pttGen int64 // bumped on every key; a delayed unkey only fires if unchanged (guards against a stale release cutting a new transmission)
|
||||
startupErr string // captured for surfacing to the frontend
|
||||
dbPath string // active database file (may be a user-chosen location)
|
||||
dataDir string // <exeDir>/data — holds config.json, logs, cty.dat
|
||||
migratedFromAppData bool // true when we auto-copied AppData on first portable launch
|
||||
ctx context.Context
|
||||
db *sql.DB
|
||||
qso *qso.Repo
|
||||
settings *settings.Store
|
||||
profiles *profile.Repo
|
||||
lookup *lookup.Manager
|
||||
cache *lookup.Cache
|
||||
cat *cat.Manager
|
||||
dxcc *dxcc.Manager
|
||||
cluster *cluster.Manager
|
||||
pota *pota.Cache
|
||||
awardRefs *awardref.Repo
|
||||
qslTemplates *qslcard.Repo
|
||||
operating *operating.Repo
|
||||
udp *udp.Manager
|
||||
udpRepo *udp.Repo
|
||||
extsvc *extsvc.Manager
|
||||
winkeyer *winkeyer.Manager
|
||||
clublog *clublog.Manager
|
||||
ultrabeam *ultrabeam.Client // Ultrabeam antenna (TCP); nil when disabled
|
||||
ubFollowStop chan struct{} // stops the "follow frequency" loop; nil when off
|
||||
audioMgr *audio.Manager
|
||||
qsoRec *audio.Recorder // continuous QSO recorder (rolling pre-roll)
|
||||
dvkRecSlot int // slot currently being recorded (DVKStartRecord → DVKStopRecord)
|
||||
dvkPttKeyed bool // we keyed PTT for a voice message; unkey when it ends
|
||||
pttMu sync.Mutex
|
||||
udpLogMu sync.Mutex // serialises UDP auto-log so concurrent packets can't both pass the dedup check
|
||||
pttPort serial.Port // open serial port while PTT (RTS/DTR) is asserted
|
||||
pttKeyedMethod string // "cat" | "rts" | "dtr" while keyed; "" when idle
|
||||
pttGen int64 // bumped on every key; a delayed unkey only fires if unchanged (guards against a stale release cutting a new transmission)
|
||||
startupErr string // captured for surfacing to the frontend
|
||||
dbPath string // active database file (may be a user-chosen location)
|
||||
dataDir string // <exeDir>/data — holds config.json, logs, cty.dat
|
||||
migratedFromAppData bool // true when we auto-copied AppData on first portable launch
|
||||
|
||||
// shuttingDown gates beforeClose re-entry: the first user attempt to
|
||||
// close fires shutdown tasks (backup, future LoTW upload, ...) while
|
||||
@@ -545,8 +547,10 @@ func (a *App) startup(ctx context.Context) {
|
||||
a.db = conn
|
||||
a.qso = qso.NewRepo(conn)
|
||||
a.settings = settings.NewStore(conn)
|
||||
a.settings.SetSensitivePredicate(isSensitiveSetting) // encrypt passwords at rest when a passphrase is set
|
||||
a.profiles = profile.NewRepo(conn)
|
||||
a.awardRefs = awardref.NewRepo(conn)
|
||||
a.qslTemplates = qslcard.NewRepo(conn)
|
||||
a.migrateAwardDefs() // upgrade legacy award definitions (enable + new fields)
|
||||
a.seedBuiltinReferences() // first-run: populate built-in award reference lists
|
||||
a.operating = operating.NewRepo(conn)
|
||||
@@ -777,8 +781,8 @@ func (a *App) plannedShutdownSteps() []shutdownStep {
|
||||
if a.extsvc != nil {
|
||||
if n := a.extsvc.PendingCount(); n > 0 {
|
||||
out = append(out, shutdownStep{
|
||||
ID: "extsvc-upload",
|
||||
Label: fmt.Sprintf("Uploading %d QSO(s) to online logbooks", n),
|
||||
ID: "extsvc-upload",
|
||||
Label: fmt.Sprintf("Uploading %d QSO(s) to online logbooks", n),
|
||||
Status: "pending",
|
||||
})
|
||||
}
|
||||
@@ -1744,14 +1748,14 @@ type POTAUnmatched struct {
|
||||
|
||||
// POTASyncResult summarises a hunter-log sync run for the UI.
|
||||
type POTASyncResult struct {
|
||||
Fetched int `json:"fetched"` // hunter-log entries downloaded
|
||||
Updated int `json:"updated"` // QSOs stamped/appended with a park ref
|
||||
AlreadyTagged int `json:"already_tagged"` // already carried the park
|
||||
Added int `json:"added"` // new QSOs inserted (addMissing)
|
||||
Unmatched int `json:"unmatched"` // no local QSO and not added
|
||||
UnmatchedList []POTAUnmatched `json:"unmatched_list"` // per-entry detail (capped)
|
||||
SkippedOtherCall int `json:"skipped_other_call"` // hunts made under another callsign (onlyMyCall)
|
||||
MyCall string `json:"my_call"` // the profile call used for the onlyMyCall filter
|
||||
Fetched int `json:"fetched"` // hunter-log entries downloaded
|
||||
Updated int `json:"updated"` // QSOs stamped/appended with a park ref
|
||||
AlreadyTagged int `json:"already_tagged"` // already carried the park
|
||||
Added int `json:"added"` // new QSOs inserted (addMissing)
|
||||
Unmatched int `json:"unmatched"` // no local QSO and not added
|
||||
UnmatchedList []POTAUnmatched `json:"unmatched_list"` // per-entry detail (capped)
|
||||
SkippedOtherCall int `json:"skipped_other_call"` // hunts made under another callsign (onlyMyCall)
|
||||
MyCall string `json:"my_call"` // the profile call used for the onlyMyCall filter
|
||||
}
|
||||
|
||||
// SyncPOTAHunterLog downloads the user's POTA hunter log and stamps pota_ref on
|
||||
@@ -2815,8 +2819,8 @@ func (a *App) WorkedBefore(callsign string, dxccHint int) (qso.WorkedBefore, err
|
||||
// Min size must be reduced BEFORE resizing down, otherwise the OS clamps to
|
||||
// the previous (larger) min — and increased BEFORE resizing up.
|
||||
const (
|
||||
compactW, compactH = 1240, 158
|
||||
normalW, normalH = 1400, 900
|
||||
compactW, compactH = 1240, 158
|
||||
normalW, normalH = 1400, 900
|
||||
normalMinW, normalMinH = 1100, 700
|
||||
// Large enough to never constrain a maximised window on big displays.
|
||||
maxW, maxH = 8000, 6000
|
||||
@@ -2868,8 +2872,8 @@ func (a *App) OpenADIFFile() (string, error) {
|
||||
// existing QSO (same call + UTC-minute + band + mode) are handled:
|
||||
// - "skip" : leave the existing QSO untouched (default, safe)
|
||||
// - "update" : merge the file's non-empty fields onto the existing QSO —
|
||||
// refreshes QSL/confirmation statuses when re-syncing from
|
||||
// Log4OM / LoTW without clobbering fields the file omits
|
||||
// refreshes QSL/confirmation statuses when re-syncing from
|
||||
// Log4OM / LoTW without clobbering fields the file omits
|
||||
// - "all" : insert every record, duplicates included
|
||||
//
|
||||
// applyCty, when true, recomputes country / continent / DXCC / CQ / ITU from
|
||||
@@ -3924,7 +3928,11 @@ func (a *App) DVKStopRecord() error {
|
||||
}
|
||||
|
||||
// DVKCancelRecord aborts a recording without saving.
|
||||
func (a *App) DVKCancelRecord() { if a.audioMgr != nil { a.audioMgr.CancelRecording() } }
|
||||
func (a *App) DVKCancelRecord() {
|
||||
if a.audioMgr != nil {
|
||||
a.audioMgr.CancelRecording()
|
||||
}
|
||||
}
|
||||
|
||||
// DVKPlay transmits a slot's message to the rig ("To Radio"), asserting serial
|
||||
// PTT (RTS/DTR) first unless the operator uses VOX. PTT is released
|
||||
@@ -4195,16 +4203,36 @@ func (a *App) applyQSLDefaults(q *qso.QSO) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if q.QSLSent == "" { q.QSLSent = d.QSLSent }
|
||||
if q.QSLRcvd == "" { q.QSLRcvd = d.QSLRcvd }
|
||||
if q.LOTWSent == "" { q.LOTWSent = d.LOTWSent }
|
||||
if q.LOTWRcvd == "" { q.LOTWRcvd = d.LOTWRcvd }
|
||||
if q.EQSLSent == "" { q.EQSLSent = d.EQSLSent }
|
||||
if q.EQSLRcvd == "" { q.EQSLRcvd = d.EQSLRcvd }
|
||||
if q.ClublogUploadStatus == "" { q.ClublogUploadStatus = d.ClublogStatus }
|
||||
if q.HRDLogUploadStatus == "" { q.HRDLogUploadStatus = d.HRDLogStatus }
|
||||
if q.QRZComUploadStatus == "" { q.QRZComUploadStatus = d.QRZComStatus }
|
||||
if q.QRZComDownloadStatus == "" { q.QRZComDownloadStatus = d.QRZComCfm }
|
||||
if q.QSLSent == "" {
|
||||
q.QSLSent = d.QSLSent
|
||||
}
|
||||
if q.QSLRcvd == "" {
|
||||
q.QSLRcvd = d.QSLRcvd
|
||||
}
|
||||
if q.LOTWSent == "" {
|
||||
q.LOTWSent = d.LOTWSent
|
||||
}
|
||||
if q.LOTWRcvd == "" {
|
||||
q.LOTWRcvd = d.LOTWRcvd
|
||||
}
|
||||
if q.EQSLSent == "" {
|
||||
q.EQSLSent = d.EQSLSent
|
||||
}
|
||||
if q.EQSLRcvd == "" {
|
||||
q.EQSLRcvd = d.EQSLRcvd
|
||||
}
|
||||
if q.ClublogUploadStatus == "" {
|
||||
q.ClublogUploadStatus = d.ClublogStatus
|
||||
}
|
||||
if q.HRDLogUploadStatus == "" {
|
||||
q.HRDLogUploadStatus = d.HRDLogStatus
|
||||
}
|
||||
if q.QRZComUploadStatus == "" {
|
||||
q.QRZComUploadStatus = d.QRZComStatus
|
||||
}
|
||||
if q.QRZComDownloadStatus == "" {
|
||||
q.QRZComDownloadStatus = d.QRZComCfm
|
||||
}
|
||||
}
|
||||
|
||||
// ── External services (logbook upload) ─────────────────────────────────
|
||||
@@ -4305,12 +4333,12 @@ func (a *App) loadExternalServices() extsvc.ExternalServices {
|
||||
StationLocation: m[keyExtLoTWStationLoc],
|
||||
ForceStationCallsign: m[keyExtLoTWForceCall],
|
||||
KeyPassword: m[keyExtLoTWKeyPassword],
|
||||
UploadFlag: m[keyExtLoTWUploadFlag],
|
||||
WriteLog: m[keyExtLoTWWriteLog] == "1",
|
||||
Username: m[keyExtLoTWUsername],
|
||||
Password: m[keyExtLoTWWebPassword],
|
||||
AutoUpload: m[keyExtLoTWAutoUpload] == "1",
|
||||
UploadMode: extsvc.UploadMode(m[keyExtLoTWUploadMode]),
|
||||
UploadFlag: m[keyExtLoTWUploadFlag],
|
||||
WriteLog: m[keyExtLoTWWriteLog] == "1",
|
||||
Username: m[keyExtLoTWUsername],
|
||||
Password: m[keyExtLoTWWebPassword],
|
||||
AutoUpload: m[keyExtLoTWAutoUpload] == "1",
|
||||
UploadMode: extsvc.UploadMode(m[keyExtLoTWUploadMode]),
|
||||
}
|
||||
// Default the TQSL path to the standard install location when unset, so
|
||||
// the field is pre-populated if TQSL is present.
|
||||
@@ -5236,20 +5264,53 @@ func (a *App) LogUDPLoggedADIF(adifText string) (int64, error) {
|
||||
// cty.dat). Best-effort: a network failure shouldn't block the log.
|
||||
if a.lookup != nil {
|
||||
if lr, lerr := a.lookup.Lookup(a.ctx, q.Callsign); lerr == nil {
|
||||
if q.Name == "" { q.Name = lr.Name }
|
||||
if q.QTH == "" { q.QTH = lr.QTH }
|
||||
if q.Country == "" { q.Country = lr.Country }
|
||||
if q.Grid == "" { q.Grid = lr.Grid }
|
||||
if q.Continent == "" { q.Continent = lr.Continent }
|
||||
if q.State == "" { q.State = lr.State }
|
||||
if q.County == "" { q.County = lr.County }
|
||||
if q.Address == "" { q.Address = lr.Address }
|
||||
if q.Email == "" { q.Email = lr.Email }
|
||||
if q.DXCC == nil && lr.DXCC != 0 { v := lr.DXCC; q.DXCC = &v }
|
||||
if q.CQZ == nil && lr.CQZ != 0 { v := lr.CQZ; q.CQZ = &v }
|
||||
if q.ITUZ == nil && lr.ITUZ != 0 { v := lr.ITUZ; q.ITUZ = &v }
|
||||
if q.Lat == nil && lr.Lat != 0 { v := lr.Lat; q.Lat = &v }
|
||||
if q.Lon == nil && lr.Lon != 0 { v := lr.Lon; q.Lon = &v }
|
||||
if q.Name == "" {
|
||||
q.Name = lr.Name
|
||||
}
|
||||
if q.QTH == "" {
|
||||
q.QTH = lr.QTH
|
||||
}
|
||||
if q.Country == "" {
|
||||
q.Country = lr.Country
|
||||
}
|
||||
if q.Grid == "" {
|
||||
q.Grid = lr.Grid
|
||||
}
|
||||
if q.Continent == "" {
|
||||
q.Continent = lr.Continent
|
||||
}
|
||||
if q.State == "" {
|
||||
q.State = lr.State
|
||||
}
|
||||
if q.County == "" {
|
||||
q.County = lr.County
|
||||
}
|
||||
if q.Address == "" {
|
||||
q.Address = lr.Address
|
||||
}
|
||||
if q.Email == "" {
|
||||
q.Email = lr.Email
|
||||
}
|
||||
if q.DXCC == nil && lr.DXCC != 0 {
|
||||
v := lr.DXCC
|
||||
q.DXCC = &v
|
||||
}
|
||||
if q.CQZ == nil && lr.CQZ != 0 {
|
||||
v := lr.CQZ
|
||||
q.CQZ = &v
|
||||
}
|
||||
if q.ITUZ == nil && lr.ITUZ != 0 {
|
||||
v := lr.ITUZ
|
||||
q.ITUZ = &v
|
||||
}
|
||||
if q.Lat == nil && lr.Lat != 0 {
|
||||
v := lr.Lat
|
||||
q.Lat = &v
|
||||
}
|
||||
if q.Lon == nil && lr.Lon != 0 {
|
||||
v := lr.Lon
|
||||
q.Lon = &v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5268,9 +5329,16 @@ func (a *App) LogUDPLoggedADIF(adifText string) (int64, error) {
|
||||
if a.operating != nil && a.profiles != nil {
|
||||
if p, err := a.profiles.Active(a.ctx); err == nil {
|
||||
if d, ok2, _ := a.operating.BandDefault(a.ctx, p.ID, q.Band); ok2 {
|
||||
if q.MyRig == "" { q.MyRig = d.StationName }
|
||||
if q.MyAntenna == "" { q.MyAntenna = d.AntennaName }
|
||||
if q.TXPower == nil && d.TXPower != nil { v := *d.TXPower; q.TXPower = &v }
|
||||
if q.MyRig == "" {
|
||||
q.MyRig = d.StationName
|
||||
}
|
||||
if q.MyAntenna == "" {
|
||||
q.MyAntenna = d.AntennaName
|
||||
}
|
||||
if q.TXPower == nil && d.TXPower != nil {
|
||||
v := *d.TXPower
|
||||
q.TXPower = &v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5328,9 +5396,10 @@ func (a *App) LogUDPLoggedADIF(adifText string) (int64, error) {
|
||||
|
||||
// consumeUDPEvents bridges parsed UDP events to the frontend over Wails'
|
||||
// event bus. The frontend listens on:
|
||||
// udp:dx_call → string callsign (also Grid/Mode/Freq when known)
|
||||
// udp:logged_qso → ADIF text of a QSO that finished in WSJT-X/JTDX/MSHV
|
||||
// udp:remote_call → string callsign from a remote-control source
|
||||
//
|
||||
// udp:dx_call → string callsign (also Grid/Mode/Freq when known)
|
||||
// udp:logged_qso → ADIF text of a QSO that finished in WSJT-X/JTDX/MSHV
|
||||
// udp:remote_call → string callsign from a remote-control source
|
||||
func (a *App) consumeUDPEvents() {
|
||||
if a.udp == nil {
|
||||
return
|
||||
@@ -5447,11 +5516,11 @@ func (a *App) OperatingDefaultForBand(band string) (operating.BandDefault, error
|
||||
|
||||
// BackupSettings is the user-tweakable database backup configuration.
|
||||
type BackupSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Folder string `json:"folder"`
|
||||
Rotation int `json:"rotation"`
|
||||
Zip bool `json:"zip"`
|
||||
LastBackupAt string `json:"last_backup_at"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Folder string `json:"folder"`
|
||||
Rotation int `json:"rotation"`
|
||||
Zip bool `json:"zip"`
|
||||
LastBackupAt string `json:"last_backup_at"`
|
||||
DefaultFolder string `json:"default_folder"` // computed, read-only — shown as a hint
|
||||
}
|
||||
|
||||
@@ -5692,10 +5761,10 @@ func (a *App) ClearLookupCache() error {
|
||||
// if it hasn't been loaded yet). Exposed for the Maintenance menu so the
|
||||
// user can see what they're working with before triggering a refresh.
|
||||
type CtyDatInfo struct {
|
||||
Path string `json:"path"`
|
||||
Entities int `json:"entities"`
|
||||
LoadedAt string `json:"loaded_at,omitempty"` // RFC3339, "" if not loaded
|
||||
FileModTime string `json:"file_mod_time,omitempty"` // RFC3339, "" if missing
|
||||
Path string `json:"path"`
|
||||
Entities int `json:"entities"`
|
||||
LoadedAt string `json:"loaded_at,omitempty"` // RFC3339, "" if not loaded
|
||||
FileModTime string `json:"file_mod_time,omitempty"` // RFC3339, "" if missing
|
||||
}
|
||||
|
||||
// GetCtyDatInfo returns metadata about the on-disk cty.dat.
|
||||
@@ -5923,10 +5992,10 @@ func (a *App) DuplicateProfile(id int64, newName string) (profile.Profile, error
|
||||
|
||||
// RotatorSettings is the JSON shape for the Hardware → Rotator panel.
|
||||
type RotatorSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Host string `json:"host"` // default 127.0.0.1
|
||||
Port int `json:"port"` // default 12000
|
||||
HasElevation bool `json:"has_elevation"` // include EL in GoTo packets
|
||||
Enabled bool `json:"enabled"`
|
||||
Host string `json:"host"` // default 127.0.0.1
|
||||
Port int `json:"port"` // default 12000
|
||||
HasElevation bool `json:"has_elevation"` // include EL in GoTo packets
|
||||
}
|
||||
|
||||
// GetRotatorSettings returns the persisted rotator config with defaults.
|
||||
@@ -6201,12 +6270,12 @@ func (a *App) ultrabeamFollowLoop(c *ultrabeam.Client, stepKHz int, stop <-chan
|
||||
// direction control). Enabled mirrors the setting; the rest comes from the
|
||||
// device's most recent status poll.
|
||||
type UltrabeamStatusInfo struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Connected bool `json:"connected"`
|
||||
Direction int `json:"direction"` // 0=normal, 1=180°, 2=bidirectional
|
||||
Frequency int `json:"frequency"` // KHz
|
||||
Band int `json:"band"`
|
||||
Moving bool `json:"moving"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Connected bool `json:"connected"`
|
||||
Direction int `json:"direction"` // 0=normal, 1=180°, 2=bidirectional
|
||||
Frequency int `json:"frequency"` // KHz
|
||||
Band int `json:"band"`
|
||||
Moving bool `json:"moving"`
|
||||
}
|
||||
|
||||
// GetUltrabeamStatus returns the antenna's current state for the UI poll.
|
||||
@@ -6289,9 +6358,9 @@ type WKMacro struct {
|
||||
type WinkeyerSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
winkeyer.Config
|
||||
Engine string `json:"engine"` // keyer backend: "winkeyer" | "tci"
|
||||
EscClearsCall bool `json:"esc_clears_call"` // ESC also resets the callsign
|
||||
SendOnType bool `json:"send_on_type"` // key chars live as typed
|
||||
Engine string `json:"engine"` // keyer backend: "winkeyer" | "tci"
|
||||
EscClearsCall bool `json:"esc_clears_call"` // ESC also resets the callsign
|
||||
SendOnType bool `json:"send_on_type"` // key chars live as typed
|
||||
Macros []WKMacro `json:"macros"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user