diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-05-02 13:43:45 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-05-02 13:43:45 -0500 |
| commit | 8408fc77c1e89637534db62965068572a400a2c6 (patch) | |
| tree | 92938dcb4c3c8b06e6e3dbc42971bb22c341b62c | |
| parent | a2e79c0928c34e753953ccf34af63225046b8db9 (diff) | |
updater: SLR auto-update + wire fluorine updater badge, bump 0.2.0
- OrganizerCore: add fluorineUpdater() getter + checkForSlrUpdates()
fired from startup checkForUpdates(); detached thread runs downloadSlr
when SLR already installed so steamrt4 BUILD_ID drift is picked up
without a launch-time stall.
- MainWindow: connect FluorineUpdater::updateAvailable to existing
updateAvailable() slot so the statusbar badge + actionUpdate light up;
on_actionUpdate_triggered routes to Settings -> Updates when a Fluorine
update is pending (the MO2 self-updater path is no-op'd).
- MainWindow launch path: drop !isSlrInstalled() short-circuit; fresh
installs still get the progress dialog, up-to-date checks rely on the
startup background pass.
- SettingsDialog: selectTabByLabel() so MainWindow can open Updates tab.
- Launcher: rm -rf update-staging/ once a new bundle has synced into
bin/, so the in-app updater doesn't accumulate stale extracts.
- CMakeLists.txt: 0.1.4 -> 0.2.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rwxr-xr-x | docker/build-inner.sh | 5 | ||||
| -rw-r--r-- | src/src/mainwindow.cpp | 101 | ||||
| -rw-r--r-- | src/src/mainwindow.h | 5 | ||||
| -rw-r--r-- | src/src/organizercore.cpp | 29 | ||||
| -rw-r--r-- | src/src/organizercore.h | 2 | ||||
| -rw-r--r-- | src/src/settingsdialog.cpp | 14 | ||||
| -rw-r--r-- | src/src/settingsdialog.h | 5 |
8 files changed, 128 insertions, 37 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 71de209..49b6ddc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,8 @@ project(ModOrganizer2 VERSION 2.5.3 LANGUAGES C CXX) # Fluorine Manager version (distinct from the MO2 engine version above). # --------------------------------------------------------------------------- set(FLUORINE_VERSION_MAJOR 0) -set(FLUORINE_VERSION_MINOR 1) -set(FLUORINE_VERSION_PATCH 4) +set(FLUORINE_VERSION_MINOR 2) +set(FLUORINE_VERSION_PATCH 0) set(FLUORINE_BUILD_CHANNEL "stable" CACHE STRING "Build channel: 'stable' for tagged releases, 'beta' for rolling CI builds") set(FLUORINE_BUILD_TIMESTAMP "" CACHE STRING diff --git a/docker/build-inner.sh b/docker/build-inner.sh index 7adab23..24e965a 100755 --- a/docker/build-inner.sh +++ b/docker/build-inner.sh @@ -454,6 +454,11 @@ if [ "${HERE_REAL}" != "${DST_REAL}" ]; then cp -af "${MANIFEST}" "${BIN_DST}/fluorine-manifest.txt" echo "${CURRENT_VER}" > "${MARKER}" echo "Sync complete." >&2 + + # Clean up the in-app updater's staging directory once the new bundle + # is live. Untouched if the user didn't go through the in-app updater. + STAGING_DIR="${FLUORINE_DATA}/update-staging" + [ -d "${STAGING_DIR}" ] && rm -rf "${STAGING_DIR}" 2>/dev/null || true fi fi diff --git a/src/src/mainwindow.cpp b/src/src/mainwindow.cpp index a928c6d..392c751 100644 --- a/src/src/mainwindow.cpp +++ b/src/src/mainwindow.cpp @@ -39,6 +39,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "executableslist.h" #include "filedialogmemory.h" #include "filterlist.h" +#include "fluorineupdater.h" #include "guessedvalue.h" #include "imodinterface.h" #include "installationmanager.h" @@ -496,6 +497,17 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, } }); + // Fluorine self-updater is constructed during setUserInterface() above + // (lazily by checkForFluorineUpdates()). The bare MO2 self-updater is + // no-op'd, so this is what actually drives the statusbar update badge. + if (auto* fu = m_OrganizerCore.fluorineUpdater()) { + connect(fu, &FluorineUpdater::updateAvailable, this, + [this](const FluorineUpdater::ReleaseInfo&) { + m_FluorineUpdatePending = true; + updateAvailable(); + }); + } + connect(m_OrganizerCore.modList(), &ModList::showMessage, [=, this](auto&& message) { showMessage(message); }); @@ -2381,7 +2393,10 @@ void MainWindow::on_startButton_clicked() }); // Pre-check: if this executable uses Proton and the current instance has SLR - // enabled, download SLR before launching if it isn't installed yet. + // enabled, ensure SLR is installed (and up-to-date with the latest steamrt4 + // BUILD_ID) before launching. downloadSlr() short-circuits on BUILD_ID match + // so once SLR is current this is a single HTTP GET; no UI is shown unless + // the runtime actually needs (re)downloading. if (selectedExecutable->useProton()) { const auto* s = Settings::maybeInstance(); bool useSLR = true; @@ -2389,43 +2404,49 @@ void MainWindow::on_startButton_clicked() QSettings const instanceIni(s->filename(), QSettings::IniFormat); useSLR = instanceIni.value("fluorine/use_slr", true).toBool(); } - if (useSLR && !isSlrInstalled()) { - auto* progress = new QProgressDialog( - tr("Downloading Steam Linux Runtime (~200 MB)...\n" - "This is required to launch games. Check the MO2 log for details."), - tr("Cancel"), 0, 0, this); - progress->setWindowTitle(tr("Steam Linux Runtime")); - progress->setWindowModality(Qt::WindowModal); - progress->setAttribute(Qt::WA_ShowWithoutActivating); - progress->setMinimumDuration(0); + if (useSLR) { + const bool freshInstall = !isSlrInstalled(); + // Only show the heavyweight progress dialog on a fresh install. + // Up-to-date checks happen quietly in the background here so we don't + // block launch on a remote BUILD_ID fetch — the up-to-date case is + // handled at startup by OrganizerCore::checkForSlrUpdates(). + if (freshInstall) { + auto* progress = new QProgressDialog( + tr("Downloading Steam Linux Runtime (~200 MB)...\n" + "This is required to launch games. Check the MO2 log for details."), + tr("Cancel"), 0, 0, this); + progress->setWindowTitle(tr("Steam Linux Runtime")); + progress->setWindowModality(Qt::WindowModal); + progress->setAttribute(Qt::WA_ShowWithoutActivating); + progress->setMinimumDuration(0); - int cancelFlag = 0; - connect(progress, &QProgressDialog::canceled, this, [&cancelFlag] { - cancelFlag = 1; - }); + int cancelFlag = 0; + connect(progress, &QProgressDialog::canceled, this, [&cancelFlag] { + cancelFlag = 1; + }); - // Run download synchronously using an event loop so the dialog stays responsive. - QFutureWatcher<QString> watcher; - QEventLoop loop; - connect(&watcher, &QFutureWatcher<QString>::finished, &loop, &QEventLoop::quit); - watcher.setFuture(QtConcurrent::run([&cancelFlag]() -> QString { - return downloadSlr(nullptr, nullptr, &cancelFlag); - })); - progress->show(); - loop.exec(); - progress->close(); - progress->deleteLater(); + QFutureWatcher<QString> watcher; + QEventLoop loop; + connect(&watcher, &QFutureWatcher<QString>::finished, &loop, &QEventLoop::quit); + watcher.setFuture(QtConcurrent::run([&cancelFlag]() -> QString { + return downloadSlr(nullptr, nullptr, &cancelFlag); + })); + progress->show(); + loop.exec(); + progress->close(); + progress->deleteLater(); - const QString err = watcher.result(); - if (cancelFlag) { - return; // user cancelled, don't launch - } - if (!err.isEmpty()) { - log::error("[SLR] Download failed: {}", err); - QMessageBox::warning(this, tr("Steam Linux Runtime"), - tr("Steam Linux Runtime download failed:\n%1\n\n" - "You can disable SLR in the Instance Manager and try again.").arg(err)); - return; + const QString err = watcher.result(); + if (cancelFlag) { + return; // user cancelled, don't launch + } + if (!err.isEmpty()) { + log::error("[SLR] Download failed: {}", err); + QMessageBox::warning(this, tr("Steam Linux Runtime"), + tr("Steam Linux Runtime download failed:\n%1\n\n" + "You can disable SLR in the Instance Manager and try again.").arg(err)); + return; + } } } } @@ -3144,6 +3165,16 @@ void MainWindow::motdReceived(const QString& motd) void MainWindow::on_actionUpdate_triggered() { + // Fluorine has its own updater (Settings → Updates). The MO2 self-updater + // is no-op'd (see SelfUpdater::testForUpdate). Open the Updates tab so + // the user can see the release info and trigger Install & restart. + if (m_FluorineUpdatePending) { + Settings& settings = m_OrganizerCore.settings(); + SettingsDialog dialog(&m_PluginContainer, settings, this); + dialog.selectTabByLabel(tr("Updates")); + dialog.exec(); + return; + } m_OrganizerCore.startMOUpdate(); } diff --git a/src/src/mainwindow.h b/src/src/mainwindow.h index 20a7286..9af07bd 100644 --- a/src/src/mainwindow.h +++ b/src/src/mainwindow.h @@ -293,6 +293,11 @@ private: QTime m_StartTime;
+ // Set when FluorineUpdater reports a new release; consumed by
+ // on_actionUpdate_triggered() to route to Settings → Updates instead of
+ // the (no-op'd) MO2 self-updater.
+ bool m_FluorineUpdatePending = false;
+
OrganizerCore& m_OrganizerCore;
PluginContainer& m_PluginContainer;
diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp index 8b3171e..3707d58 100644 --- a/src/src/organizercore.cpp +++ b/src/src/organizercore.cpp @@ -2,6 +2,7 @@ #include "categoriesdialog.h" #include "credentialsdialog.h" #include "fluorineupdater.h" +#include "slrmanager.h" #include "delayedfilewriter.h" #include "directoryrefresher.h" #include "env.h" @@ -48,6 +49,7 @@ #include <chrono> #include <cstdio> #include <filesystem> +#include <thread> #include <QApplication> #include <QCoreApplication> #include <QDialog> @@ -393,9 +395,36 @@ void OrganizerCore::checkForUpdates() if (m_UserInterface != nullptr) { m_Updater.testForUpdate(m_Settings); checkForFluorineUpdates(); + checkForSlrUpdates(); } } +void OrganizerCore::checkForSlrUpdates() +{ + // SLR auto-update: only relevant if SLR is already installed. The first- + // install case is handled at game launch in MainWindow, where the user gets + // a progress dialog. Here we silently background-fetch a newer steamrt4 + // image when one exists; downloadSlr() short-circuits on BUILD_ID match so + // the cost is one HTTP GET when already up-to-date. + if (!m_Settings.checkForUpdates()) { + return; + } + if (m_Settings.network().offlineMode()) { + return; + } + if (!isSlrInstalled()) { + return; + } + + // Fire and forget on a detached thread. No UI; result is logged. + std::thread([]() { + const QString err = downloadSlr(nullptr, nullptr, nullptr); + if (!err.isEmpty()) { + MOBase::log::warn("SLR update check failed: {}", err); + } + }).detach(); +} + void OrganizerCore::checkForFluorineUpdates() { // Set up the Fluorine self-update checker lazily so repeated calls don't diff --git a/src/src/organizercore.h b/src/src/organizercore.h index 7f60998..7ef7713 100644 --- a/src/src/organizercore.h +++ b/src/src/organizercore.h @@ -258,10 +258,12 @@ public: void checkForUpdates();
void checkForFluorineUpdates();
+ void checkForSlrUpdates();
void startMOUpdate();
Settings& settings();
SelfUpdater* updater() { return &m_Updater; }
+ class FluorineUpdater* fluorineUpdater() const { return m_FluorineUpdater; }
InstallationManager* installationManager();
MOShared::DirectoryEntry* directoryStructure() { return m_DirectoryStructure; }
DirectoryRefresher* directoryRefresher() { return m_DirectoryRefresher.get(); }
diff --git a/src/src/settingsdialog.cpp b/src/src/settingsdialog.cpp index c0e254b..e07ac71 100644 --- a/src/src/settingsdialog.cpp +++ b/src/src/settingsdialog.cpp @@ -82,11 +82,25 @@ ExitFlags SettingsDialog::exitNeeded() const return m_exit;
}
+void SettingsDialog::selectTabByLabel(const QString& label)
+{
+ for (int i = 0; i < ui->tabWidget->count(); ++i) {
+ if (ui->tabWidget->tabText(i) == label) {
+ ui->tabWidget->setCurrentIndex(i);
+ m_pendingTabOverride = i;
+ return;
+ }
+ }
+}
+
int SettingsDialog::exec()
{
GeometrySaver gs(m_settings, this);
m_settings.widgets().restoreIndex(ui->tabWidget);
+ if (m_pendingTabOverride >= 0) {
+ ui->tabWidget->setCurrentIndex(m_pendingTabOverride);
+ }
auto ret = TutorableDialog::exec();
diff --git a/src/src/settingsdialog.h b/src/src/settingsdialog.h index 864eae7..030f6f6 100644 --- a/src/src/settingsdialog.h +++ b/src/src/settingsdialog.h @@ -80,6 +80,10 @@ public: void setExitNeeded(ExitFlags e);
ExitFlags exitNeeded() const;
+ // Pre-select a tab by its visible label (e.g. "Updates"). Must be called
+ // before exec(); any saved tab index is overridden.
+ void selectTabByLabel(const QString& label);
+
int exec() override;
public slots:
@@ -91,6 +95,7 @@ private: std::vector<std::unique_ptr<SettingsTab>> m_tabs;
ExitFlags m_exit;
PluginContainer* m_pluginContainer;
+ int m_pendingTabOverride = -1;
};
#endif // SETTINGSDIALOG_H
|
