Add GitHub release update check to Python GNOME and KDE KCM UIs (#160)
This commit is contained in:
parent
cc33623aa7
commit
afb2dd4f4a
|
|
@ -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,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<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 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"),
|
||||
|
|
|
|||
|
|
@ -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,25 @@
|
|||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="tabPosition">
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 9c6bd2b8260540a50b315bb0571ff0c2ac846dbc
|
||||
Subproject commit 40c9979d7e79d31047cb474c154018ce43afb63a
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="FailedVerification" parent="GtkBox">
|
||||
<property name="orientation">1</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="margin-top">20</property>
|
||||
<property name="margin-bottom">20</property>
|
||||
<property name="margin-start">20</property>
|
||||
|
|
@ -10,9 +11,11 @@
|
|||
<property name="spacing">20</property>
|
||||
<child>
|
||||
<object class="AdwStatusPage">
|
||||
<property name="vexpand">True</property>
|
||||
<property name="title" translatable="yes">Breezy Desktop GNOME invalid setup</property>
|
||||
<property name="description" translatable="yes">Your Breezy GNOME setup is invalid or incomplete. Please re-run the setup script. Report this issue if it persists.</property>
|
||||
<property name="width-request">650</property>
|
||||
<property name="height-request">250</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="NoDevice" parent="GtkBox">
|
||||
<property name="orientation">1</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="margin-top">20</property>
|
||||
<property name="margin-bottom">20</property>
|
||||
<property name="margin-start">20</property>
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
<property name="spacing">20</property>
|
||||
<child>
|
||||
<object class="AdwStatusPage">
|
||||
<property name="vexpand">True</property>
|
||||
<property name="title" translatable="yes">No device connected</property>
|
||||
<property name="description" translatable="yes">Breezy Desktop was unable to detect any supported XR devices.</property>
|
||||
<property name="width-request">800</property>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="NoDriver" parent="GtkBox">
|
||||
<property name="orientation">1</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="margin-top">20</property>
|
||||
<property name="margin-bottom">20</property>
|
||||
<property name="margin-start">20</property>
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
<property name="spacing">20</property>
|
||||
<child>
|
||||
<object class="AdwStatusPage">
|
||||
<property name="vexpand">True</property>
|
||||
<property name="title" translatable="yes">No driver running</property>
|
||||
<property name="description" translatable="yes">
|
||||
If you installed via AUR, make sure you ran the recommended post-install command:
|
||||
|
|
@ -18,6 +20,7 @@
|
|||
Otherwise, please file an issue on GitHub, or create a new thread in the #troubleshooting channel on Discord.
|
||||
</property>
|
||||
<property name="width-request">800</property>
|
||||
<property name="height-request">300</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="NoExtension" parent="GtkBox">
|
||||
<property name="orientation">1</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="margin-top">20</property>
|
||||
<property name="margin-bottom">20</property>
|
||||
<property name="margin-start">20</property>
|
||||
|
|
@ -10,9 +11,11 @@
|
|||
<property name="spacing">20</property>
|
||||
<child>
|
||||
<object class="AdwStatusPage">
|
||||
<property name="vexpand">True</property>
|
||||
<property name="title" translatable="yes">Breezy Desktop GNOME extension not ready</property>
|
||||
<property name="description" translatable="yes">If you have just run the setup, then you may need to log out and back in to use it. Otherwise, please follow the Breezy GNOME setup instructions.</property>
|
||||
<property name="width-request">800</property>
|
||||
<property name="height-request">250</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="NoLicense" parent="GtkBox">
|
||||
<property name="orientation">1</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="margin-top">20</property>
|
||||
<property name="margin-bottom">20</property>
|
||||
<property name="margin-start">20</property>
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
<property name="spacing">20</property>
|
||||
<child>
|
||||
<object class="AdwStatusPage">
|
||||
<property name="vexpand">True</property>
|
||||
<property name="title" translatable="yes">No license file was found</property>
|
||||
<property name="description" translatable="yes">
|
||||
The first time you use Breezy Desktop, an internet connection is required to retrieve your device's license.
|
||||
|
|
|
|||
|
|
@ -80,7 +80,29 @@
|
|||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_content" />
|
||||
<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. To update, rerun the breezy_gnome_setup script.</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="wrap">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_content">
|
||||
<property name="vexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,3 @@
|
|||
# main.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 os
|
||||
import sys
|
||||
import gi
|
||||
|
|
@ -90,7 +71,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,63 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
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'
|
||||
|
||||
|
||||
def _is_user_local_install():
|
||||
"""Return True if the app is running from a user-local installation.
|
||||
|
||||
Scripted installs put the binary under the user's home directory (e.g.
|
||||
~/.local/bin/breezydesktop). System-wide package manager installs (e.g.
|
||||
AUR) put the binary in a system path like /usr/bin and don't need a
|
||||
version-update prompt because the package manager handles updates.
|
||||
"""
|
||||
home = os.path.expanduser('~')
|
||||
script_path = os.path.realpath(sys.argv[0])
|
||||
return script_path.startswith(home + os.sep)
|
||||
|
||||
|
||||
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. Does nothing (no callback) when not running from a
|
||||
user-local installation (e.g. installed via AUR).
|
||||
"""
|
||||
if not _is_user_local_install():
|
||||
return
|
||||
|
||||
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()
|
||||
|
|
@ -1,22 +1,3 @@
|
|||
# window.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
|
||||
|
||||
from gi.repository import Gtk, GLib
|
||||
from .extensionsmanager import ExtensionsManager
|
||||
from .license import BREEZY_GNOME_FEATURES
|
||||
|
|
@ -29,6 +10,7 @@ from .nodevice import NoDevice
|
|||
from .nodriver import NoDriver
|
||||
from .noextension import NoExtension
|
||||
from .nolicense import NoLicense
|
||||
from .updatechecker import check_for_update
|
||||
from .verify import verify_installation
|
||||
|
||||
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/window.ui')
|
||||
|
|
@ -40,8 +22,9 @@ 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()
|
||||
|
||||
def __init__(self, skip_verification, **kwargs):
|
||||
def __init__(self, version, skip_verification, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connected_device = ConnectedDevice()
|
||||
|
|
@ -70,6 +53,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 +97,8 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
|
|||
dialog.set_transient_for(widget.get_ancestor(Gtk.Window))
|
||||
dialog.present()
|
||||
|
||||
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