Add version update check on app load for Python GNOME UI and KCM
Co-authored-by: wheaney <42350981+wheaney@users.noreply.github.com>
This commit is contained in:
parent
3c6d62825b
commit
82d76bfd7f
|
|
@ -0,0 +1 @@
|
|||
.
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@
|
|||
#include <QDebug>
|
||||
#include <QLocale>
|
||||
#include <QSignalBlocker>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
|
|
@ -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,64 @@ void BreezyDesktopEffectConfig::checkEffectLoaded() {
|
|||
}
|
||||
}
|
||||
|
||||
void BreezyDesktopEffectConfig::checkForUpdates() {
|
||||
#ifdef BREEZY_DESKTOP_VERSION_STR
|
||||
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<int> {
|
||||
QList<int> 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<int> latestParts = parseParts(latest);
|
||||
const QList<int> 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<QLabel*>(QStringLiteral("labelUpdateAvailable"))) {
|
||||
label->setText(tr("<a href=\"https://github.com/wheaney/breezy-desktop/releases/latest\">A newer version (%1) is available</a>").arg(latest));
|
||||
label->setVisible(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
static QDBusInterface makeVDInterface() {
|
||||
return QDBusInterface(
|
||||
QStringLiteral("org.kde.KWin"),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <KConfigWatcher>
|
||||
#include <memory>
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QTimer>
|
||||
#include <QVariant>
|
||||
#include <QVariantList>
|
||||
|
|
@ -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<QObject*> 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;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,28 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelUpdateAvailable">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(0,100,200); font-weight: bold;</string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="tabPosition">
|
||||
|
|
|
|||
|
|
@ -79,6 +79,31 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="update_available_banner">
|
||||
<property name="revealed">0</property>
|
||||
<property name="show-close-button">False</property>
|
||||
<property name="message-type">info</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">A newer version is available</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="update_available_button">
|
||||
<property name="label" translatable="yes">View release</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_content" />
|
||||
</child>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class BreezydesktopApplication(Adw.Application):
|
|||
"""
|
||||
win = self.props.active_window
|
||||
if not win:
|
||||
win = BreezydesktopWindow(self._skip_verification, application=self)
|
||||
win = BreezydesktopWindow(self.version, self._skip_verification, application=self)
|
||||
win.connect('close-request', lambda *_: self.on_quit_action())
|
||||
win.connect('destroy', lambda *_: self.on_quit_action())
|
||||
win.present()
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ breezydesktop_sources = [
|
|||
'shortcutdialog.py',
|
||||
'statemanager.py',
|
||||
'time.py',
|
||||
'updatechecker.py',
|
||||
'virtualdisplay.py',
|
||||
'virtualdisplaymanager.py',
|
||||
'verify.py',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
# updatechecker.py
|
||||
#
|
||||
# Copyright 2024 Unknown
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError
|
||||
|
||||
logger = logging.getLogger('breezy_ui')
|
||||
|
||||
GITHUB_RELEASES_URL = 'https://api.github.com/repos/wheaney/breezy-desktop/releases/latest'
|
||||
GITHUB_RELEASES_PAGE = 'https://github.com/wheaney/breezy-desktop/releases/latest'
|
||||
|
||||
|
||||
def _parse_version(version_str):
|
||||
"""Parse a version string like '2.8.10' or 'v2.8.9' into a tuple of ints."""
|
||||
v = version_str.strip().lstrip('v')
|
||||
try:
|
||||
return tuple(int(x) for x in v.split('.'))
|
||||
except (ValueError, AttributeError):
|
||||
return None
|
||||
|
||||
|
||||
def check_for_update(current_version, callback):
|
||||
"""
|
||||
Asynchronously check for a newer version on GitHub.
|
||||
|
||||
Calls callback(latest_version_str) on the calling thread's GLib main loop
|
||||
if a newer version is found, or callback(None) if no update is available
|
||||
or if the check fails.
|
||||
"""
|
||||
def _check():
|
||||
latest_version = None
|
||||
try:
|
||||
req = Request(GITHUB_RELEASES_URL, headers={'User-Agent': 'breezy-desktop-ui'})
|
||||
with urlopen(req, timeout=10) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
latest_tag = data.get('tag_name', '')
|
||||
latest = _parse_version(latest_tag)
|
||||
current = _parse_version(current_version)
|
||||
if latest and current and latest > current:
|
||||
latest_version = latest_tag.lstrip('v')
|
||||
except (URLError, json.JSONDecodeError, ValueError, OSError) as e:
|
||||
logger.debug('Update check failed: %s', e)
|
||||
callback(latest_version)
|
||||
|
||||
threading.Thread(target=_check, daemon=True).start()
|
||||
|
|
@ -29,6 +29,7 @@ from .nodevice import NoDevice
|
|||
from .nodriver import NoDriver
|
||||
from .noextension import NoExtension
|
||||
from .nolicense import NoLicense
|
||||
from .updatechecker import check_for_update, GITHUB_RELEASES_PAGE
|
||||
from .verify import verify_installation
|
||||
|
||||
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/window.ui')
|
||||
|
|
@ -40,8 +41,10 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
|
|||
license_action_needed_button = Gtk.Template.Child()
|
||||
missing_breezy_features_banner = Gtk.Template.Child()
|
||||
missing_breezy_features_button = Gtk.Template.Child()
|
||||
update_available_banner = Gtk.Template.Child()
|
||||
update_available_button = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, skip_verification, **kwargs):
|
||||
def __init__(self, version, skip_verification, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connected_device = ConnectedDevice()
|
||||
|
|
@ -63,6 +66,7 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
|
|||
|
||||
self.license_action_needed_button.connect('clicked', self._on_license_button_clicked)
|
||||
self.missing_breezy_features_button.connect('clicked', self._on_license_button_clicked)
|
||||
self.update_available_button.connect('clicked', self._on_update_button_clicked)
|
||||
|
||||
self._handle_state_update(self.state_manager, None)
|
||||
|
||||
|
|
@ -70,6 +74,8 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
|
|||
|
||||
self.connect("destroy", self._on_window_destroy)
|
||||
|
||||
check_for_update(version, self._on_update_check_result)
|
||||
|
||||
def _handle_settings_update(self, settings_manager, key):
|
||||
self._handle_state_update(self.state_manager, None)
|
||||
|
||||
|
|
@ -112,5 +118,11 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
|
|||
dialog.set_transient_for(widget.get_ancestor(Gtk.Window))
|
||||
dialog.present()
|
||||
|
||||
def _on_update_button_clicked(self, widget):
|
||||
Gtk.show_uri(self, GITHUB_RELEASES_PAGE, 0)
|
||||
|
||||
def _on_update_check_result(self, latest_version):
|
||||
GLib.idle_add(self.update_available_banner.set_revealed, latest_version is not None)
|
||||
|
||||
def _on_window_destroy(self, widget):
|
||||
self.state_manager.disconnect_by_func(self._handle_state_update)
|
||||
Loading…
Reference in New Issue