diff --git a/src/hamlibclass.cpp b/src/hamlibclass.cpp index a9895096..5803f7d7 100644 --- a/src/hamlibclass.cpp +++ b/src/hamlibclass.cpp @@ -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; @@ -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; @@ -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); @@ -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); diff --git a/src/hamlibclass.h b/src/hamlibclass.h index 5fe2aae7..f3aaf349 100644 --- a/src/hamlibclass.h +++ b/src/hamlibclass.h @@ -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); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4633985c..e0d5acca 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -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(); @@ -477,6 +478,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"; } diff --git a/src/setupdialog.cpp b/src/setupdialog.cpp index e10fc462..29e43f4b 100644 --- a/src/setupdialog.cpp +++ b/src/setupdialog.cpp @@ -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; diff --git a/src/setupdialog.h b/src/setupdialog.h index f12c5c57..62018e33 100644 --- a/src/setupdialog.h +++ b/src/setupdialog.h @@ -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 diff --git a/src/setuppages/setuppagehamlib.cpp b/src/setuppages/setuppagehamlib.cpp index e2895e77..f0680324 100644 --- a/src/setuppages/setuppagehamlib.cpp +++ b/src/setuppages/setuppagehamlib.cpp @@ -30,7 +30,6 @@ SetupPageHamLib::SetupPageHamLib(DataProxy_SQLite *dp, QWidget *parent) : QWidge //qDebug() << Q_FUNC_INFO ; hamlibTestOK = false; testWasRun = false; - hamlib = new HamLibClass(); activateHamlibCheckBox = new QCheckBox(); readOnlyModeCheckBox = new QCheckBox(); @@ -40,7 +39,7 @@ SetupPageHamLib::SetupPageHamLib(DataProxy_SQLite *dp, QWidget *parent) : QWidge networkConfigWidget = new HamLibNetworkConfigWidget; testHamlibPushButton = new QPushButton(); - defaultFreqMode = tr("000.000 / %1").arg(tr("Mode")); + defaultFreqMode = tr("000.0000 / %1").arg(tr("Mode")); freqDisplayLabel = new QLabel(defaultFreqMode); rigTypeComboBox = new QComboBox; @@ -55,8 +54,10 @@ SetupPageHamLib::SetupPageHamLib(DataProxy_SQLite *dp, QWidget *parent) : QWidge void SetupPageHamLib::stopHamlib () { - hamlib->stop(); - freqDisplayLabel->setText(defaultFreqMode); + // No-op: the live hamlib connection is owned exclusively by MainWindow. + // SetupDialog calls this on OK/Cancel, but with the single-connection design + // stopping it here would kill the radio link whenever the dialog is closed. + // MainWindow handles re-init when settings actually change. } bool SetupPageHamLib::wasTestRun() const @@ -64,46 +65,55 @@ bool SetupPageHamLib::wasTestRun() const return testWasRun; } +void SetupPageHamLib::setLiveHamlib(HamLibClass *liveHamlib) +{ + if (!liveHamlib || liveHamlib == m_liveHamlib) + return; + m_liveHamlib = liveHamlib; + connect(m_liveHamlib, static_cast(&HamLibClass::radioStatusChanged), + this, &SetupPageHamLib::slotRadioStatusChanged); + connect(m_liveHamlib, &HamLibClass::rigDisconnected, this, [this]{ + freqDisplayLabel->setText(defaultFreqMode); + setTestResult(false); + }); +} + void SetupPageHamLib::slotTestHamlib() { //qDebug() << Q_FUNC_INFO; testWasRun = true; - hamlib->stop (); - freqDisplayLabel->setText(defaultFreqMode); - if ((rigTypeComboBox->currentText ().contains ("NET rigctl")) || (rigTypeComboBox->currentText ().contains ("FLRig"))) - { - //qDebug() << Q_FUNC_INFO << " - FLRig/NetRig"; - hamlib->setNetworkPort (networkConfigWidget->getPort ()); - hamlib->setNetworkAddress (networkConfigWidget->getAddress ()); + if (!m_liveHamlib) { + setTestResult(false); + return; } - else - { - //qDebug() << Q_FUNC_INFO << " - Serial rig"; - hamlib->setPort (serialConfigWidget->getSerialPort ()); - hamlib->setSpeed (serialConfigWidget->getSerialBauds ()); - hamlib->setParity(serialConfigWidget->getParity ()); - hamlib->setFlow(serialConfigWidget->getFlowControl ()); - hamlib->setStop(serialConfigWidget->getStopBits ()); - //qDebug() << Q_FUNC_INFO << " - 50"; - hamlib->setDataBits(serialConfigWidget->getDataBits ()); - //qDebug() << Q_FUNC_INFO << " - 51"; + // Apply the form's current settings to the live hamlib, then reconnect. + if ((rigTypeComboBox->currentText().contains("NET rigctl")) || (rigTypeComboBox->currentText().contains("FLRig"))) { + m_liveHamlib->setNetworkPort(networkConfigWidget->getPort()); + m_liveHamlib->setNetworkAddress(networkConfigWidget->getAddress()); + } else { + m_liveHamlib->setPort(serialConfigWidget->getSerialPort()); + m_liveHamlib->setSpeed(serialConfigWidget->getSerialBauds()); + m_liveHamlib->setParity(serialConfigWidget->getParity()); + m_liveHamlib->setFlow(serialConfigWidget->getFlowControl()); + m_liveHamlib->setStop(serialConfigWidget->getStopBits()); + m_liveHamlib->setDataBits(serialConfigWidget->getDataBits()); } - - hamlib->setModelId (hamlib->getModelIdFromName (rigTypeComboBox->currentText ())); - hamlib->setPoll (pollIntervalQSpinBox->value ()); - //qDebug() << Q_FUNC_INFO << " - Calling hamlib->init"; - bool ok = hamlib->init(true); + m_liveHamlib->setModelId(m_liveHamlib->getModelIdFromName(rigTypeComboBox->currentText())); + m_liveHamlib->setPoll(pollIntervalQSpinBox->value()); + m_liveHamlib->stop(); + freqDisplayLabel->setText(defaultFreqMode); + bool ok = m_liveHamlib->init(true); if (ok) - ok = hamlib->readRadio(); - setTestResult (ok); + ok = m_liveHamlib->readRadio(); + setTestResult(ok); if (!ok) - hamlib->stop (); + m_liveHamlib->stop(); //qDebug() << Q_FUNC_INFO << " - END"; } void SetupPageHamLib::slotRadioStatusChanged(RadioStatus _status) { - QString text = _status.freq_VFO_RX.toQString(); + QString text = QString("%1").arg(_status.freq_VFO_RX.toDouble(MHz), 0, 'f', 4); if (!_status.mode_VFO_RX.isEmpty()) text += " / " + _status.mode_VFO_RX; freqDisplayLabel->setText(text); @@ -112,30 +122,27 @@ void SetupPageHamLib::slotRadioStatusChanged(RadioStatus _status) void SetupPageHamLib::setTestResult(const bool _ok) { //qDebug() << Q_FUNC_INFO ; - QPalette pal = testHamlibPushButton->palette(); - if (_ok ) { //qDebug() << Q_FUNC_INFO << " - OK"; testHamlibPushButton->setText (tr("Test: OK")); - pal.setColor(QPalette::Button, QColor(Qt::green)); + // Use stylesheet so the green colour survives Qt's disabled-button rendering. + // QPalette alone is overridden by system/GTK themes on some Linux desktops. + testHamlibPushButton->setStyleSheet( + "QPushButton { background-color: #00cc00; color: black; }" + "QPushButton:disabled { background-color: #00cc00; color: black; }"); + testHamlibPushButton->setEnabled(false); // connected — nothing to test activateHamlibCheckBox->setEnabled (true); - //qDebug() << Q_FUNC_INFO << " - before reading freq"; - //double freq = hamlib->getFrequency (); - //qDebug() << Q_FUNC_INFO << " - after reading freq"; - //dataFromRigLineEdit->setText (QString::number(freq)); } else { //qDebug() << Q_FUNC_INFO << " - NOK"; testHamlibPushButton->setText (tr("Test: NOK")); - pal.setColor(QPalette::Button, QColor(Qt::red)); - activateHamlibCheckBox->setChecked (false); + testHamlibPushButton->setStyleSheet( + "QPushButton { background-color: #ff4444; color: white; }"); + testHamlibPushButton->setEnabled(true); // not connected — allow retry activateHamlibCheckBox->setEnabled (false); } - - testHamlibPushButton->setPalette(pal); - testHamlibPushButton->update(); //qDebug() << Q_FUNC_INFO << " - NOK END"; } @@ -249,28 +256,21 @@ void SetupPageHamLib::createUI() connect(testHamlibPushButton, SIGNAL(clicked(bool)), this, SLOT(slotTestHamlib()) ); connect(rigTypeComboBox, SIGNAL(currentTextChanged(QString)), this, SLOT(slotRadioComboBoxChanged(QString)) ); - connect(hamlib, static_cast(&HamLibClass::radioStatusChanged), - this, &SetupPageHamLib::slotRadioStatusChanged); - connect(hamlib, &HamLibClass::rigDisconnected, this, [this]{ freqDisplayLabel->setText(defaultFreqMode); }); //qDebug() << Q_FUNC_INFO << " - END"; } void SetupPageHamLib::setRig() { //qDebug() << Q_FUNC_INFO; - // Rutine to fill the rig combo boxes - // Do not display debug codes when load the rig's - QStringList rigs; - rigs.clear(); - //qDebug() << Q_FUNC_INFO << " - 10"; - hamlib->initClass(); - rigs << hamlib->getRigList(); + if (!m_liveHamlib) return; + // getRigList() lazy-loads the rig database on first call (no initClass() needed). + QStringList rigs = m_liveHamlib->getRigList(); //qDebug() << Q_FUNC_INFO << " - rigs: " << QString::number(rigs.length())<< QT_ENDL; + rigTypeComboBox->blockSignals(true); rigTypeComboBox->clear (); rigTypeComboBox->addItems (rigs); rigTypeComboBox->setCurrentIndex(0); - //rigTypeComboBox->clear(); - //rigTypeComboBox->addItems(rigs); + rigTypeComboBox->blockSignals(false); //qDebug() << Q_FUNC_INFO << " - END"; } @@ -296,12 +296,17 @@ void SetupPageHamLib::showEvent(QShowEvent *event) setRig(); // calls hamlib->initClass() + fills combo from getRigList() loadSettings(); // re-apply saved settings now that combo is populated } + // Reflect the live connection state each time the tab becomes visible. + // Uses the read-only live pointer if available; does not touch the test hamlib. + if (m_liveHamlib) + setTestResult(m_liveHamlib->isRunning()); QWidget::showEvent(event); } bool SetupPageHamLib::setRigType(const QString &_radio) { - int _index = rigTypeComboBox->findText(hamlib->getNameFromModelId(_radio.toInt()), Qt::MatchFlag::MatchExactly); + if (!m_liveHamlib) return false; + int _index = rigTypeComboBox->findText(m_liveHamlib->getNameFromModelId(_radio.toInt()), Qt::MatchFlag::MatchExactly); //qDebug() << "SetupPageHamLib::setRig: After: " << QString::number(_index) ; if (_index >= 0) { @@ -338,7 +343,7 @@ void SetupPageHamLib::saveSettings() settings.beginGroup ("HamLib"); settings.setValue ("HamLibActive", QVariant((activateHamlibCheckBox->isChecked()))); settings.setValue ("HamLibReadOnly", QVariant((readOnlyModeCheckBox->isChecked()))); - settings.setValue ("HamLibRigType", hamlib->getModelIdFromName(rigTypeComboBox->currentText ())); + settings.setValue ("HamLibRigType", m_liveHamlib ? m_liveHamlib->getModelIdFromName(rigTypeComboBox->currentText()) : -1); settings.setValue ("HamLibRigPollRate", QString::number(pollIntervalQSpinBox->value ())); settings.setValue ("HamLibSerialPort", serialConfigWidget->getSerialPort ()); settings.setValue ("HamLibSerialBauds", QString::number(serialConfigWidget->getSerialBauds ())); @@ -358,7 +363,11 @@ void SetupPageHamLib::loadSettings() Utilities util(Q_FUNC_INFO); QSettings settings(util.getCfgFile (), QSettings::IniFormat); settings.beginGroup ("HamLib"); + // Block combo signals during load so slotRadioComboBoxChanged does not call + // setTestResult(false) and clobber the connection state indicator. + rigTypeComboBox->blockSignals(true); setRigType (settings.value("HamLibRigType").toString()); + rigTypeComboBox->blockSignals(false); pollIntervalQSpinBox->setValue(settings.value("HamLibRigPollRate", 2000).toInt ()); serialConfigWidget->setSerialPort (settings.value("HamLibSerialPort").toString()); serialConfigWidget->setSerialBauds (settings.value("HamLibSerialBauds", 9600).toInt ()); @@ -374,7 +383,7 @@ void SetupPageHamLib::loadSettings() snapshot.active = activateHamlibCheckBox->isChecked(); snapshot.readOnly = readOnlyModeCheckBox->isChecked(); - snapshot.rigModelId = hamlib->getModelIdFromName(rigTypeComboBox->currentText()); + snapshot.rigModelId = m_liveHamlib ? m_liveHamlib->getModelIdFromName(rigTypeComboBox->currentText()) : -1; snapshot.pollRate = pollIntervalQSpinBox->value(); snapshot.serialPort = serialConfigWidget->getSerialPort(); snapshot.serialBauds = serialConfigWidget->getSerialBauds(); @@ -390,7 +399,7 @@ bool SetupPageHamLib::hasSettingsChanged() const { if (snapshot.active != activateHamlibCheckBox->isChecked()) return true; if (snapshot.readOnly != readOnlyModeCheckBox->isChecked()) return true; - if (snapshot.rigModelId != hamlib->getModelIdFromName(rigTypeComboBox->currentText())) return true; + if (snapshot.rigModelId != (m_liveHamlib ? m_liveHamlib->getModelIdFromName(rigTypeComboBox->currentText()) : -1)) return true; if (snapshot.pollRate != pollIntervalQSpinBox->value()) return true; if (snapshot.serialPort != serialConfigWidget->getSerialPort()) return true; if (snapshot.serialBauds != serialConfigWidget->getSerialBauds()) return true; diff --git a/src/setuppages/setuppagehamlib.h b/src/setuppages/setuppagehamlib.h index fce4f4e2..3b324a30 100644 --- a/src/setuppages/setuppagehamlib.h +++ b/src/setuppages/setuppagehamlib.h @@ -64,6 +64,7 @@ class SetupPageHamLib : public QWidget void loadSettings(); bool hasSettingsChanged() const; bool wasTestRun() const; + void setLiveHamlib(HamLibClass *liveHamlib); public slots: //void slotScanPorts(); @@ -95,7 +96,7 @@ public slots: int pollMin, pollMax, rigctlport; QString defaultFreqMode; - HamLibClass *hamlib; + HamLibClass *m_liveHamlib = nullptr; // the main application hamlib connection QCheckBox *activateHamlibCheckBox, *readOnlyModeCheckBox; //, *RTSCheckBox, *DTRCheckBox; bool networkRadio, hamlibTestOK, testWasRun;