From afb2dd4f4a764fa6375dd7c4c07b581e936eadba Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 18:29:27 -0800 Subject: [PATCH] Add GitHub release update check to Python GNOME and KDE KCM UIs (#160) --- kwin/CMakeLists.txt | 2 +- kwin/src/kcm/CMakeLists.txt | 1 + kwin/src/kcm/breezydesktopeffectkcm.cpp | 71 +++++++++++++++++++++++++ kwin/src/kcm/breezydesktopeffectkcm.h | 3 ++ kwin/src/kcm/breezydesktopeffectkcm.ui | 19 +++++++ ui/modules/PyXRLinuxDriverIPC | 2 +- ui/src/gtk/failed-verification.ui | 3 ++ ui/src/gtk/no-device.ui | 2 + ui/src/gtk/no-driver.ui | 3 ++ ui/src/gtk/no-extension.ui | 3 ++ ui/src/gtk/no-license.ui | 2 + ui/src/gtk/window.ui | 24 ++++++++- ui/src/main.py | 21 +------- ui/src/meson.build | 1 + ui/src/updatechecker.py | 63 ++++++++++++++++++++++ ui/src/window.py | 28 +++------- 16 files changed, 205 insertions(+), 43 deletions(-) create mode 100644 ui/src/updatechecker.py diff --git a/kwin/CMakeLists.txt b/kwin/CMakeLists.txt index b2bd26f..bf0f5a0 100644 --- a/kwin/CMakeLists.txt +++ b/kwin/CMakeLists.txt @@ -35,7 +35,7 @@ include(cmake/info.cmake) find_package(epoxy REQUIRED) find_package(XCB REQUIRED COMPONENTS XCB) find_package(KWinDBusInterface CONFIG REQUIRED) -find_package(Qt6 REQUIRED COMPONENTS Core) +find_package(Qt6 REQUIRED COMPONENTS Core Network) # Qt6 sets QT6_INSTALL_QML which is distro-aware get_target_property(QT6_QMAKE_EXECUTABLE Qt6::qmake IMPORTED_LOCATION) diff --git a/kwin/src/kcm/CMakeLists.txt b/kwin/src/kcm/CMakeLists.txt index b9cd832..fd667bd 100644 --- a/kwin/src/kcm/CMakeLists.txt +++ b/kwin/src/kcm/CMakeLists.txt @@ -15,6 +15,7 @@ kcoreaddons_add_plugin(breezy_desktop_config INSTALL_NAMESPACE "plasma/kcms" SOU kconfig_add_kcfg_files(breezy_desktop_config ../breezydesktopconfig.kcfgc) target_link_libraries(breezy_desktop_config Qt6::DBus + Qt6::Network KF6::ConfigCore KF6::ConfigGui KF6::ConfigWidgets diff --git a/kwin/src/kcm/breezydesktopeffectkcm.cpp b/kwin/src/kcm/breezydesktopeffectkcm.cpp index 4124664..a0afea7 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.cpp +++ b/kwin/src/kcm/breezydesktopeffectkcm.cpp @@ -51,6 +51,8 @@ #include #include #include +#include +#include #include #include @@ -237,6 +239,9 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu // One-time check if the KWin effect backend is actually loaded. If not, disable UI early. checkEffectLoaded(); + // Asynchronously check GitHub for a newer release. + checkForUpdates(); + // Show/enable Virtual Display controls only when we're on Wayland const bool isWaylandSession = QGuiApplication::platformName().contains(QStringLiteral("wayland"), Qt::CaseInsensitive) || qEnvironmentVariable("XDG_SESSION_TYPE").compare(QStringLiteral("wayland"), Qt::CaseInsensitive) == 0; @@ -588,6 +593,72 @@ void BreezyDesktopEffectConfig::checkEffectLoaded() { } } +void BreezyDesktopEffectConfig::checkForUpdates() { +#ifdef BREEZY_DESKTOP_VERSION_STR + // Skip update check for system-wide installs (e.g. AUR) — the package + // manager handles updates there. Scripted installs put the plugin under + // the user's home directory, so we use that as the heuristic. + const QString pluginPath = metaData().fileName(); + const QString home = QDir::homePath(); + if (!pluginPath.startsWith(home + QLatin1Char('/'))) + return; + + if (!m_networkManager) + m_networkManager = new QNetworkAccessManager(this); + + QNetworkRequest request(QUrl(QStringLiteral("https://api.github.com/repos/wheaney/breezy-desktop/releases/latest"))); + request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("breezy-desktop-kcm")); + auto *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + qCDebug(KWIN_XR) << "Update check failed:" << reply->errorString(); + return; + } + + const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); + if (!doc.isObject()) return; + const QString latestTag = doc.object().value(QStringLiteral("tag_name")).toString(); + if (latestTag.isEmpty()) return; + + QString latest = latestTag; + if (latest.startsWith(QLatin1Char('v'))) latest.remove(0, 1); + + // Compare version tuples + const QString current = QLatin1String(BREEZY_DESKTOP_VERSION_STR); + auto parseParts = [](const QString &v) -> QList { + QList parts; + for (const QString &p : v.split(QLatin1Char('.'))) { + bool ok; + int n = p.toInt(&ok); + if (!ok) return {}; + parts.append(n); + } + return parts; + }; + const QList latestParts = parseParts(latest); + const QList currentParts = parseParts(current); + if (latestParts.isEmpty() || currentParts.isEmpty()) return; + bool isNewer = false; + for (int i = 0; i < qMax(latestParts.size(), currentParts.size()); ++i) { + int lv = i < latestParts.size() ? latestParts[i] : 0; + int cv = i < currentParts.size() ? currentParts[i] : 0; + if (lv != cv) { + isNewer = lv > cv; + break; + } + } + + if (isNewer) { + if (auto label = widget()->findChild(QStringLiteral("labelUpdateAvailable"))) { + label->setText(tr("A newer version (%1) is available. To update, rerun the breezy_kwin_setup script.").arg(latest)); + label->setVisible(true); + } + } + }); +#endif +} + static QDBusInterface makeVDInterface() { return QDBusInterface( QStringLiteral("org.kde.KWin"), diff --git a/kwin/src/kcm/breezydesktopeffectkcm.h b/kwin/src/kcm/breezydesktopeffectkcm.h index 69ee642..c0a5c80 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.h +++ b/kwin/src/kcm/breezydesktopeffectkcm.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,7 @@ private: void pollDriverState(); void refreshLicenseUi(const QJsonObject &rootObj); void checkEffectLoaded(); + void checkForUpdates(); void showStatus(QLabel *label, bool success, const QString &message); void setRequestInProgress(std::initializer_list widgets, bool inProgress); bool eventFilter(QObject *watched, QEvent *event) override; @@ -71,6 +73,7 @@ private: ::Ui::BreezyDesktopEffectConfig ui; KConfigWatcher::Ptr m_configWatcher; + QNetworkAccessManager *m_networkManager = nullptr; bool m_updatingFromConfig = false; bool m_driverStateInitialized = false; bool m_deviceConnected = false; diff --git a/kwin/src/kcm/breezydesktopeffectkcm.ui b/kwin/src/kcm/breezydesktopeffectkcm.ui index 2ea3bae..ae5997f 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.ui +++ b/kwin/src/kcm/breezydesktopeffectkcm.ui @@ -39,6 +39,25 @@ + + + + + + + false + + + true + + + Qt::AlignHCenter|Qt::AlignVCenter + + + color: rgb(0,100,200); font-weight: bold; + + + diff --git a/ui/modules/PyXRLinuxDriverIPC b/ui/modules/PyXRLinuxDriverIPC index 9c6bd2b..40c9979 160000 --- a/ui/modules/PyXRLinuxDriverIPC +++ b/ui/modules/PyXRLinuxDriverIPC @@ -1 +1 @@ -Subproject commit 9c6bd2b8260540a50b315bb0571ff0c2ac846dbc +Subproject commit 40c9979d7e79d31047cb474c154018ce43afb63a diff --git a/ui/src/gtk/failed-verification.ui b/ui/src/gtk/failed-verification.ui index 315dee8..6f86597 100644 --- a/ui/src/gtk/failed-verification.ui +++ b/ui/src/gtk/failed-verification.ui @@ -3,6 +3,7 @@ diff --git a/ui/src/gtk/no-device.ui b/ui/src/gtk/no-device.ui index 4ce401d..e41a52a 100644 --- a/ui/src/gtk/no-device.ui +++ b/ui/src/gtk/no-device.ui @@ -3,6 +3,7 @@