Skip to content
99 changes: 85 additions & 14 deletions src/hamlibclass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,20 +187,30 @@ bool HamLibClass::readVFO()
}
else
{
// rig_get_vfo() failed. This is non-fatal: many rigs do not support
// this call (e.g. RIG_ENTARGET, RIG_ENAVAIL, RIG_ENIMPL are common).
// Any other unexpected error is also treated as non-fatal here because
// VFO querying is auxiliary — failing it must never interrupt the main
// polling cycle (freq/mode/split reads).
// We log once and disable future calls to avoid log spam.
logEvent(Q_FUNC_INFO,
QString("rig_get_vfo failed (code %1) — "
"VFO query disabled for this session").arg(retcode),
Warning);
vfoQuerySupported = false;
currentVfo = RIG_VFO_CURR;
radioStatus.memoryMode = false;
radioStatus.memoryChannel = -1;
if (retcode == RIG_ENAVAIL || retcode == RIG_ENIMPL)
{
// Rig does not support rig_get_vfo() at all — disable permanently
// to avoid log spam on every poll cycle.
logEvent(Q_FUNC_INFO,
QString("rig_get_vfo not supported (code %1) — "
"VFO query disabled for this session").arg(retcode),
Warning);
vfoQuerySupported = false;
currentVfo = RIG_VFO_CURR;
radioStatus.memoryMode = false;
radioStatus.memoryChannel = -1;
}
else
{
// Transient error (e.g. rigctld busy, timeout due to shared rigctld
// with another application). Do not permanently disable VFO querying
// or reset currentVfo — the memory-channel guard in readSplit() must
// continue to function correctly on the next poll cycle.
logEvent(Q_FUNC_INFO,
QString("rig_get_vfo transient error (code %1) — "
"retaining last known VFO state").arg(retcode),
Warning);
}
}

return true;
Expand Down Expand Up @@ -296,6 +306,10 @@ bool HamLibClass::readSplit()
return false;
if (!splitQuerySupported)
return true;
// Memory-channel mode: split is not applicable and some rigs switch VFOs
// to answer the query, causing display flickering. Skip it entirely.
if (currentVfo == RIG_VFO_MEM)
return true;

split_t split = RIG_SPLIT_OFF;
vfo_t tx_vfo = RIG_VFO_SUB;
Expand Down Expand Up @@ -539,6 +553,61 @@ bool HamLibClass::stop()
return false;
}

void HamLibClass::probeSplitVfoSideEffect()
{
// Probe whether rig_get_split_vfo switches the active VFO as a side-effect.
// Some rigs (e.g. older Kenwood TS-450S) implement this query by physically
// toggling to the TX VFO and back, causing the display to flicker to a
// different band/frequency on every poll cycle (related to issue #947).
// We detect this by comparing the VFO state and frequency before and after
// the probe call. If either changed we restore immediately and disable split
// polling for this session so normal operation is never disturbed.
vfo_t vfoBefore = RIG_VFO_NONE;
freq_t freqBefore = 0;
const bool vfoReadOk = (rig_get_vfo (my_rig, &vfoBefore) == RIG_OK);
const bool freqReadOk = (rig_get_freq(my_rig, RIG_VFO_CURR, &freqBefore) == RIG_OK);

split_t probeSplt = RIG_SPLIT_OFF;
vfo_t probeTxVfo = RIG_VFO_SUB;
const int probeRet = rig_get_split_vfo(my_rig, RIG_VFO_CURR, &probeSplt, &probeTxVfo);

if (probeRet == RIG_ENAVAIL || probeRet == RIG_ENIMPL)
{
splitQuerySupported = false;
logEvent(Q_FUNC_INFO,
"rig_get_split_vfo not supported — split polling disabled.", Warning);
return;
}

if (probeRet != RIG_OK)
return; // unexpected error — leave splitQuerySupported unchanged

// Check for VFO side-effects using both VFO state and frequency.
// Using both makes detection robust even when one read failed due to
// rigctld contention with another application sharing the daemon.
vfo_t vfoAfter = RIG_VFO_NONE;
freq_t freqAfter = 0;
const bool vfoAfterOk = (rig_get_vfo (my_rig, &vfoAfter) == RIG_OK);
const bool freqAfterOk = (rig_get_freq(my_rig, RIG_VFO_CURR, &freqAfter) == RIG_OK);

const bool vfoChanged = (vfoReadOk && vfoAfterOk && vfoAfter != vfoBefore);
const bool freqChanged = (freqReadOk && freqAfterOk && freqAfter != freqBefore);

if (vfoChanged || freqChanged)
{
if (vfoReadOk)
rig_set_vfo(my_rig, vfoBefore);
splitQuerySupported = false;
logEvent(Q_FUNC_INFO,
QString("rig_get_split_vfo caused a VFO/freq change "
"(vfo %1->%2, freq %3->%4 Hz) — "
"split polling disabled to prevent display flickering.")
.arg(vfoBefore).arg(vfoAfter).arg(freqBefore).arg(freqAfter),
Warning);
}
// else: no VFO side-effect detected; splitQuerySupported stays true
}

bool HamLibClass::init(bool _active)
{
logEvent(Q_FUNC_INFO, "Start", Devel);
Expand Down Expand Up @@ -620,6 +689,8 @@ bool HamLibClass::init(bool _active)
rig_state = RigState::Connected;
//connected = true;

probeSplitVfoSideEffect();

// ¡IMPORTANTE! Iniciar el polling si la conexión tuvo éxito
if (_active && timer) {
timer->start(pollInterval);
Expand Down
1 change: 1 addition & 0 deletions src/hamlibclass.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public slots:
bool readMode();
bool readVFO(); //Reads the current VFO
bool readSplit();
void probeSplitVfoSideEffect(); //Probe for rig_get_split_vfo VFO side-effects at connect time
void cleanup();
bool radioStatusChanged(const RadioStatus _old, const RadioStatus _new);

Expand Down
10 changes: 10 additions & 0 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ MainWindow::MainWindow(DataProxy_SQLite *dp, World *injectedWorld):
statusBarMessage = tr("Starting KLog");

setupDialog = new SetupDialog(dataProxy, world, this);
setupDialog->setLiveHamlib(hamlib); // read-only display reference — Test button still uses its own local instance
// [PROPOSAL-5] SetupDialog: only opened on demand (or first run)
//qInfo() << "[KLOG-TIMING] ctor 021 - SetupDialog [PROPOSAL-5 candidate]:" << timer.elapsed() << "ms"; timer.restart();

Expand Down Expand Up @@ -473,6 +474,15 @@ void MainWindow::init()
//qInfo() << "[KLOG-TIMING] init() 10 - applySettings():" << initTimer.elapsed() << "ms"; initTimer.restart();

dataProxy->loadDuplicateCache(currentLog); // async: lanza hilo BG y vuelve inmediatamente

// If the window was shown before init() ran (Proposal 4 startup ordering),
// showEvent() fired while upAndRunning was still false and skipped scheduling
// slotInitHamlib. Catch that case here so Hamlib always auto-connects.
if (!hamlibConnectionAttempted) {
hamlibConnectionAttempted = true;
QTimer::singleShot(0, this, &MainWindow::slotInitHamlib);
}

logEvent(Q_FUNC_INFO, "END", Debug);
//qInfo() << "[KLOG-TIMING] init() TOTAL:" << initTimer.elapsed() << "ms";
}
Expand Down
5 changes: 5 additions & 0 deletions src/setupdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,11 @@ bool SetupDialog::hamlibTestWasRun() const
return hamlibPage->wasTestRun();
}

void SetupDialog::setLiveHamlib(HamLibClass *liveHamlib)
{
hamlibPage->setLiveHamlib(liveHamlib);
}

void SetupDialog::setLogLevel(const DebugLogLevel _sev)
{
logLevel = _sev;
Expand Down
1 change: 1 addition & 0 deletions src/setupdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class SetupDialog : public QDialog
void loadDarkMode(); // Reads the config to setup the DarkMode
bool hamlibSettingsChanged() const;
bool hamlibTestWasRun() const;
void setLiveHamlib(HamLibClass *liveHamlib);
int getSelectedLog() const;
bool logsWereModified() const;
bool wasDBMoved() const; // true si the user successfully clicked OK to move the DB
Expand Down
Loading