mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-02 02:22:06 -04:00
system updater: complete overhaul
Move system update flow to GO, with a CLI (convenient AIO tool) and server integration. All lifecycle, scheduling, execution occurs on backend side. Run some backends via pkexec, some via terminal like paru/yay. Incorporate flatpak as an option to update. Add terminal override setting in GUI, in addition to $TERMINAL env variable. fixes #2307 fixes #822 fixes #1102 fixes #1812 fixes #1087 fixes #1743
This commit is contained in:
@@ -30,9 +30,36 @@ Singleton {
|
||||
property bool isLightMode: false
|
||||
property bool doNotDisturb: false
|
||||
property real doNotDisturbUntil: 0
|
||||
property string terminalOverride: ""
|
||||
property bool isSwitchingMode: false
|
||||
property bool suppressOSD: true
|
||||
|
||||
readonly property var terminalOptions: ["ghostty", "kitty", "foot", "alacritty", "wezterm", "konsole", "gnome-terminal", "xterm"]
|
||||
property var installedTerminals: []
|
||||
|
||||
function resolveTerminal() {
|
||||
if (terminalOverride && terminalOverride.length > 0) {
|
||||
return terminalOverride;
|
||||
}
|
||||
const env = Quickshell.env("TERMINAL");
|
||||
if (env && env.length > 0) {
|
||||
return env;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Process {
|
||||
id: terminalProbe
|
||||
running: true
|
||||
command: ["sh", "-c", "for t in ghostty kitty foot alacritty wezterm konsole gnome-terminal xterm; do command -v \"$t\" >/dev/null 2>&1 && echo \"$t\"; done"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const found = text.trim().split("\n").filter(line => line.length > 0);
|
||||
root.installedTerminals = found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: dndExpireTimer
|
||||
repeat: false
|
||||
|
||||
@@ -640,6 +640,9 @@ Singleton {
|
||||
property bool updaterUseCustomCommand: false
|
||||
property string updaterCustomCommand: ""
|
||||
property string updaterTerminalAdditionalParams: ""
|
||||
property int updaterIntervalSeconds: 1800
|
||||
property bool updaterIncludeFlatpak: true
|
||||
property bool updaterAllowAUR: true
|
||||
|
||||
property string displayNameMode: "system"
|
||||
property var screenPreferences: ({})
|
||||
|
||||
@@ -4,6 +4,7 @@ var SPEC = {
|
||||
isLightMode: { def: false },
|
||||
doNotDisturb: { def: false },
|
||||
doNotDisturbUntil: { def: 0 },
|
||||
terminalOverride: { def: "" },
|
||||
|
||||
wallpaperPath: { def: "" },
|
||||
perMonitorWallpaper: { def: false },
|
||||
|
||||
@@ -428,6 +428,9 @@ var SPEC = {
|
||||
updaterUseCustomCommand: { def: false },
|
||||
updaterCustomCommand: { def: "" },
|
||||
updaterTerminalAdditionalParams: { def: "" },
|
||||
updaterIntervalSeconds: { def: 1800 },
|
||||
updaterIncludeFlatpak: { def: true },
|
||||
updaterAllowAUR: { def: true },
|
||||
|
||||
displayNameMode: { def: "system" },
|
||||
screenPreferences: { def: {} },
|
||||
|
||||
@@ -895,7 +895,12 @@ Item {
|
||||
|
||||
SystemUpdatePopout {
|
||||
id: systemUpdatePopout
|
||||
onPopoutClosed: PopoutService.unloadSystemUpdate()
|
||||
onPopoutClosed: {
|
||||
if (systemUpdatePopout._reopenAfterUpgrade) {
|
||||
return;
|
||||
}
|
||||
PopoutService.unloadSystemUpdate();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.systemUpdatePopout = systemUpdatePopout;
|
||||
|
||||
@@ -1881,7 +1881,7 @@ Item {
|
||||
function openTerminal(path) {
|
||||
if (!path)
|
||||
return;
|
||||
var terminal = Quickshell.env("TERMINAL") || "xterm";
|
||||
var terminal = SessionData.resolveTerminal() || "xterm";
|
||||
Quickshell.execDetached({
|
||||
command: [terminal],
|
||||
workingDirectory: path
|
||||
|
||||
@@ -164,7 +164,8 @@ Rectangle {
|
||||
"id": "updater",
|
||||
"text": I18n.tr("System Updater"),
|
||||
"icon": "refresh",
|
||||
"tabIndex": 20
|
||||
"tabIndex": 20,
|
||||
"updaterOnly": true
|
||||
},
|
||||
{
|
||||
"id": "desktop_widgets",
|
||||
@@ -340,6 +341,8 @@ Rectangle {
|
||||
return false;
|
||||
if (item.clipboardOnly && (!DMSService.isConnected || DMSService.apiVersion < 23))
|
||||
return false;
|
||||
if (item.updaterOnly && !SystemUpdateService.sysupdateAvailable)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
441
quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml
Normal file
441
quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml
Normal file
@@ -0,0 +1,441 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankPopout {
|
||||
id: systemUpdatePopout
|
||||
|
||||
layerNamespace: "dms:system-update"
|
||||
|
||||
property var parentWidget: null
|
||||
property var triggerScreen: null
|
||||
|
||||
Ref {
|
||||
service: SystemUpdateService
|
||||
}
|
||||
|
||||
property bool _reopenAfterUpgrade: false
|
||||
|
||||
Connections {
|
||||
target: SystemUpdateService
|
||||
function onIsUpgradingChanged() {
|
||||
if (SystemUpdateService.isUpgrading) {
|
||||
return;
|
||||
}
|
||||
if (!systemUpdatePopout._reopenAfterUpgrade) {
|
||||
return;
|
||||
}
|
||||
systemUpdatePopout._reopenAfterUpgrade = false;
|
||||
systemUpdatePopout.open();
|
||||
}
|
||||
}
|
||||
|
||||
popupWidth: 440
|
||||
popupHeight: 560
|
||||
triggerWidth: 55
|
||||
positioning: ""
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: false
|
||||
|
||||
onBackgroundClicked: close()
|
||||
|
||||
onShouldBeVisibleChanged: {
|
||||
if (!shouldBeVisible) {
|
||||
return;
|
||||
}
|
||||
const stale = !SystemUpdateService.lastCheckUnix || (Date.now() / 1000 - SystemUpdateService.lastCheckUnix) > 300;
|
||||
if (stale && !SystemUpdateService.isChecking && !SystemUpdateService.isUpgrading) {
|
||||
SystemUpdateService.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Rectangle {
|
||||
id: updaterPanel
|
||||
|
||||
color: "transparent"
|
||||
focus: true
|
||||
|
||||
readonly property bool hasTerminalBackend: (SystemUpdateService.backends || []).some(b => b.runsInTerminal === true)
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
systemUpdatePopout.close();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (systemUpdatePopout.shouldBeVisible) {
|
||||
forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: header
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
anchors.topMargin: Theme.spacingL
|
||||
height: 40
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("System Updates")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
switch (true) {
|
||||
case SystemUpdateService.isUpgrading:
|
||||
return I18n.tr("Upgrading...");
|
||||
case SystemUpdateService.isChecking:
|
||||
return I18n.tr("Checking...");
|
||||
case SystemUpdateService.hasError:
|
||||
return I18n.tr("Error");
|
||||
case SystemUpdateService.updateCount === 0:
|
||||
return I18n.tr("Up to date");
|
||||
case SystemUpdateService.updateCount === 1:
|
||||
return I18n.tr("%1 update").arg(SystemUpdateService.updateCount);
|
||||
default:
|
||||
return I18n.tr("%1 updates").arg(SystemUpdateService.updateCount);
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: SystemUpdateService.hasError ? Theme.error : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: refreshButton
|
||||
buttonSize: 28
|
||||
iconName: "refresh"
|
||||
iconSize: 18
|
||||
iconColor: Theme.surfaceText
|
||||
enabled: !SystemUpdateService.isChecking && !SystemUpdateService.isUpgrading
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
onClicked: SystemUpdateService.checkForUpdates()
|
||||
|
||||
RotationAnimation {
|
||||
target: refreshButton
|
||||
property: "rotation"
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
running: SystemUpdateService.isChecking
|
||||
loops: Animation.Infinite
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
refreshButton.rotation = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: backendsRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: header.bottom
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
anchors.topMargin: Theme.spacingS
|
||||
visible: SystemUpdateService.backends.length > 0 && !SystemUpdateService.isUpgrading
|
||||
text: {
|
||||
const names = (SystemUpdateService.backends || []).map(b => b.displayName).join(", ");
|
||||
return I18n.tr("Backends: %1").arg(names);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonsRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
anchors.bottomMargin: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
height: 44
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: primaryMouseArea.containsMouse && primaryMouseArea.enabled ? Theme.primaryHover : Theme.secondaryHover
|
||||
opacity: primaryMouseArea.enabled ? 1.0 : 0.5
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: SystemUpdateService.isUpgrading ? I18n.tr("Cancel") : I18n.tr("Update All")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: primaryMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: SystemUpdateService.isUpgrading || SystemUpdateService.updateCount > 0
|
||||
onClicked: {
|
||||
if (SystemUpdateService.isUpgrading) {
|
||||
SystemUpdateService.cancelUpdates();
|
||||
return;
|
||||
}
|
||||
const opts = {
|
||||
includeFlatpak: SettingsData.updaterIncludeFlatpak,
|
||||
includeAUR: SettingsData.updaterAllowAUR,
|
||||
terminal: SessionData.terminalOverride
|
||||
};
|
||||
if (updaterPanel.hasTerminalBackend) {
|
||||
systemUpdatePopout._reopenAfterUpgrade = true;
|
||||
SystemUpdateService.runUpdates(opts);
|
||||
systemUpdatePopout.close();
|
||||
return;
|
||||
}
|
||||
SystemUpdateService.runUpdates(opts);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: closeMouseArea.containsMouse ? Theme.errorPressed : Theme.secondaryHover
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Close")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: systemUpdatePopout.close()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bodyArea
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: backendsRow.visible ? backendsRow.bottom : header.bottom
|
||||
anchors.bottom: buttonsRow.top
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
anchors.topMargin: Theme.spacingM
|
||||
anchors.bottomMargin: Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
|
||||
|
||||
StyledText {
|
||||
id: statusText
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: !SystemUpdateService.isUpgrading && (SystemUpdateService.updateCount === 0 || SystemUpdateService.hasError || SystemUpdateService.isChecking)
|
||||
text: {
|
||||
switch (true) {
|
||||
case SystemUpdateService.hasError:
|
||||
return I18n.tr("Failed: %1").arg(SystemUpdateService.errorMessage);
|
||||
case !SystemUpdateService.helperAvailable:
|
||||
return I18n.tr("No supported package manager found.");
|
||||
case SystemUpdateService.isChecking:
|
||||
return I18n.tr("Checking for updates...");
|
||||
default:
|
||||
return I18n.tr("Your system is up to date!");
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: SystemUpdateService.hasError ? Theme.errorText : Theme.surfaceText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: packagesList
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
visible: !SystemUpdateService.isUpgrading && SystemUpdateService.updateCount > 0 && !SystemUpdateService.hasError && !SystemUpdateService.isChecking
|
||||
clip: true
|
||||
spacing: Theme.spacingXS
|
||||
model: SystemUpdateService.availableUpdates
|
||||
|
||||
delegate: Rectangle {
|
||||
width: ListView.view.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: packageMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent"
|
||||
|
||||
required property var modelData
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 64
|
||||
height: 18
|
||||
radius: 9
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.18)
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.repo || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 64 - Theme.spacingS
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: modelData.name || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: {
|
||||
const from = modelData.fromVersion || "";
|
||||
const to = modelData.toVersion || "";
|
||||
if (from && to) {
|
||||
return `${from} → ${to}`;
|
||||
}
|
||||
return to || from || "";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: packageMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: modelData.changelogUrl ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (modelData.changelogUrl) {
|
||||
Qt.openUrlExternally(modelData.changelogUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
visible: SystemUpdateService.isUpgrading && updaterPanel.hasTerminalBackend
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "terminal"
|
||||
size: 32
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Running in terminal")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("AUR helpers are interactive — see the terminal window for prompts. This popout will return to idle when the upgrade exits.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
visible: SystemUpdateService.isUpgrading && !updaterPanel.hasTerminalBackend
|
||||
contentWidth: width
|
||||
contentHeight: logText.implicitHeight
|
||||
clip: true
|
||||
|
||||
onContentHeightChanged: {
|
||||
if (contentHeight > height) {
|
||||
contentY = contentHeight - height;
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: logText
|
||||
width: parent.width
|
||||
text: (SystemUpdateService.recentLog || []).join("\n")
|
||||
font.family: Theme.monoFontFamily || "monospace"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,41 +7,33 @@ import qs.Widgets
|
||||
BasePill {
|
||||
id: root
|
||||
|
||||
property var widgetData: null
|
||||
property bool isActive: false
|
||||
|
||||
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
|
||||
readonly property bool isChecking: SystemUpdateService.isChecking
|
||||
readonly property bool shouldHide: SettingsData.updaterHideWidget && !hasUpdates && !isChecking && !SystemUpdateService.hasError
|
||||
readonly property bool isClean: SystemUpdateService.sysupdateAvailable && !hasUpdates && !isChecking && !SystemUpdateService.hasError
|
||||
readonly property bool hideWhenIdle: widgetData?.hideWhenIdle === true
|
||||
readonly property bool shouldHide: hideWhenIdle && isClean
|
||||
|
||||
width: shouldHide ? 0 : (isVerticalOrientation ? barThickness : visualWidth)
|
||||
height: shouldHide ? 0 : (isVerticalOrientation ? visualHeight : barThickness)
|
||||
visible: !shouldHide
|
||||
opacity: shouldHide ? 0 : 1
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "hidden_horizontal"
|
||||
when: root.shouldHide && !isVerticalOrientation
|
||||
PropertyChanges {
|
||||
target: root
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hidden_vertical"
|
||||
when: root.shouldHide && isVerticalOrientation
|
||||
PropertyChanges {
|
||||
target: root
|
||||
height: 0
|
||||
}
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
NumberAnimation {
|
||||
properties: "width,height"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
|
||||
@@ -189,10 +189,10 @@ Item {
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!SystemUpdateService.shellVersion && !DMSService.cliVersion)
|
||||
if (!ShellVersionService.shellVersion && !DMSService.cliVersion)
|
||||
return "dms";
|
||||
|
||||
let version = SystemUpdateService.shellVersion || "";
|
||||
let version = ShellVersionService.shellVersion || "";
|
||||
let cliVersion = DMSService.cliVersion || "";
|
||||
|
||||
// Debian/Ubuntu/OpenSUSE git format: 1.0.3+git2264.c5c5ce84
|
||||
@@ -218,7 +218,7 @@ Item {
|
||||
|
||||
let baseVersion = extractBaseVersion(cliVersion);
|
||||
if (!baseVersion)
|
||||
baseVersion = extractBaseVersion(SystemUpdateService.semverVersion);
|
||||
baseVersion = extractBaseVersion(ShellVersionService.semverVersion);
|
||||
if (baseVersion) {
|
||||
return `dms (git) v${baseVersion}-${match[1]}`;
|
||||
}
|
||||
@@ -253,8 +253,8 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: SystemUpdateService.shellCodename.length > 0
|
||||
text: `"${SystemUpdateService.shellCodename}"`
|
||||
visible: ShellVersionService.shellCodename.length > 0
|
||||
text: `"${ShellVersionService.shellCodename}"`
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.italic: true
|
||||
color: Theme.surfaceVariantText
|
||||
|
||||
@@ -325,6 +325,8 @@ Item {
|
||||
placeholderText: I18n.tr("Enter launch prefix (e.g., 'uwsm-app')")
|
||||
onTextEdited: SettingsData.set("launchPrefix", text)
|
||||
}
|
||||
|
||||
TerminalPickerRow {}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Settings.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property var intervalOptions: [
|
||||
{
|
||||
label: I18n.tr("Every 15 minutes"),
|
||||
seconds: 900
|
||||
},
|
||||
{
|
||||
label: I18n.tr("Every 30 minutes"),
|
||||
seconds: 1800
|
||||
},
|
||||
{
|
||||
label: I18n.tr("Every hour"),
|
||||
seconds: 3600
|
||||
},
|
||||
{
|
||||
label: I18n.tr("Every 4 hours"),
|
||||
seconds: 14400
|
||||
},
|
||||
{
|
||||
label: I18n.tr("Once a day"),
|
||||
seconds: 86400
|
||||
}
|
||||
]
|
||||
|
||||
function intervalLabelFor(seconds) {
|
||||
for (const opt of intervalOptions) {
|
||||
if (opt.seconds === seconds) {
|
||||
return opt.label;
|
||||
}
|
||||
}
|
||||
return intervalOptions[1].label;
|
||||
}
|
||||
|
||||
function intervalSecondsFor(label) {
|
||||
for (const opt of intervalOptions) {
|
||||
if (opt.label === label) {
|
||||
return opt.seconds;
|
||||
}
|
||||
}
|
||||
return 1800;
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
@@ -25,18 +67,60 @@ Item {
|
||||
title: I18n.tr("System Updater")
|
||||
settingKey: "systemUpdater"
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Hide Updater Widget", "When updater widget is used, then hide it if no update found")
|
||||
description: I18n.tr("When updater widget is used, then hide it if no update found")
|
||||
checked: SettingsData.updaterHideWidget
|
||||
onToggled: checked => {
|
||||
SettingsData.set("updaterHideWidget", checked);
|
||||
StyledText {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
visible: SystemUpdateService.backends.length > 0
|
||||
text: {
|
||||
const names = (SystemUpdateService.backends || []).map(b => b.displayName).join(", ");
|
||||
return I18n.tr("Detected backends: %1").arg(names);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
text: I18n.tr("Check interval")
|
||||
description: I18n.tr("How often the server polls for new updates.")
|
||||
options: root.intervalOptions.map(o => o.label)
|
||||
currentValue: root.intervalLabelFor(SettingsData.updaterIntervalSeconds)
|
||||
onValueChanged: label => {
|
||||
const secs = root.intervalSecondsFor(label);
|
||||
SettingsData.set("updaterIntervalSeconds", secs);
|
||||
SystemUpdateService.setInterval(secs);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Use Custom Command")
|
||||
description: I18n.tr("Use custom command for update your system")
|
||||
text: I18n.tr("Include Flatpak updates")
|
||||
description: I18n.tr("Apply Flatpak updates alongside system updates when running 'Update All'.")
|
||||
visible: (SystemUpdateService.backends || []).some(b => b.repo === "flatpak")
|
||||
checked: SettingsData.updaterIncludeFlatpak
|
||||
onToggled: checked => SettingsData.set("updaterIncludeFlatpak", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Include AUR updates")
|
||||
description: I18n.tr("Run paru/yay with AUR enabled when 'Update All' is clicked.")
|
||||
visible: (SystemUpdateService.backends || []).some(b => b.id === "paru" || b.id === "yay")
|
||||
checked: SettingsData.updaterAllowAUR
|
||||
onToggled: checked => SettingsData.set("updaterAllowAUR", checked)
|
||||
}
|
||||
|
||||
TerminalPickerRow {}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "tune"
|
||||
title: I18n.tr("Advanced")
|
||||
settingKey: "systemUpdaterAdvanced"
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Use custom command")
|
||||
description: I18n.tr("Open a terminal and run a custom command instead of the in-shell upgrade flow.")
|
||||
checked: SettingsData.updaterUseCustomCommand
|
||||
onToggled: checked => {
|
||||
if (!checked) {
|
||||
@@ -49,11 +133,32 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
visible: SettingsData.updaterUseCustomCommand
|
||||
height: warnText.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||
|
||||
StyledText {
|
||||
id: warnText
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
text: I18n.tr("Custom command and terminal params are split on whitespace; paths with spaces will break.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.warning
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
height: customCommandColumn.implicitHeight
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
visible: SettingsData.updaterUseCustomCommand
|
||||
|
||||
Column {
|
||||
id: customCommandColumn
|
||||
@@ -61,7 +166,7 @@ Item {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("System update custom command")
|
||||
text: I18n.tr("Custom update command")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
@@ -69,7 +174,7 @@ Item {
|
||||
DankTextField {
|
||||
id: updaterCustomCommand
|
||||
width: parent.width
|
||||
placeholderText: "myPkgMngr --sysupdate"
|
||||
placeholderText: "topgrade --no-retry"
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
@@ -98,6 +203,7 @@ Item {
|
||||
height: terminalParamsColumn.implicitHeight
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
visible: SettingsData.updaterUseCustomCommand
|
||||
|
||||
Column {
|
||||
id: terminalParamsColumn
|
||||
@@ -105,7 +211,7 @@ Item {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Terminal custom additional parameters")
|
||||
text: I18n.tr("Terminal additional parameters")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
@@ -113,7 +219,7 @@ Item {
|
||||
DankTextField {
|
||||
id: updaterTerminalCustomClass
|
||||
width: parent.width
|
||||
placeholderText: "-T udpClass"
|
||||
placeholderText: "-T updater"
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
31
quickshell/Modules/Settings/Widgets/TerminalPickerRow.qml
Normal file
31
quickshell/Modules/Settings/Widgets/TerminalPickerRow.qml
Normal file
@@ -0,0 +1,31 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
SettingsDropdownRow {
|
||||
id: root
|
||||
|
||||
readonly property string autoLabel: I18n.tr("Auto")
|
||||
|
||||
text: I18n.tr("Terminal")
|
||||
settingKey: "terminalOverride"
|
||||
|
||||
options: {
|
||||
const opts = [autoLabel];
|
||||
const installed = SessionData.installedTerminals || [];
|
||||
const list = installed.length > 0 ? installed : SessionData.terminalOptions;
|
||||
for (const t of list) {
|
||||
opts.push(t);
|
||||
}
|
||||
if (SessionData.terminalOverride && !opts.includes(SessionData.terminalOverride)) {
|
||||
opts.push(SessionData.terminalOverride);
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
currentValue: SessionData.terminalOverride.length > 0 ? SessionData.terminalOverride : autoLabel
|
||||
|
||||
onValueChanged: label => {
|
||||
const next = label === autoLabel ? "" : label;
|
||||
SessionData.set("terminalOverride", next);
|
||||
}
|
||||
}
|
||||
@@ -246,7 +246,8 @@ Item {
|
||||
"text": I18n.tr("System Update"),
|
||||
"description": I18n.tr("Check for system updates"),
|
||||
"icon": "update",
|
||||
"enabled": SystemUpdateService.distributionSupported
|
||||
"enabled": SystemUpdateService.sysupdateAvailable,
|
||||
"warning": SystemUpdateService.sysupdateAvailable ? undefined : I18n.tr("Requires DMS server with sysupdate capability")
|
||||
},
|
||||
{
|
||||
"id": "powerMenuButton",
|
||||
@@ -430,7 +431,7 @@ Item {
|
||||
"id": widget.id,
|
||||
"enabled": widget.enabled
|
||||
};
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion"];
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion", "hideWhenIdle"];
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (widget[keys[i]] !== undefined)
|
||||
result[keys[i]] = widget[keys[i]];
|
||||
@@ -579,6 +580,17 @@ Item {
|
||||
setWidgetsForSection(sectionId, widgets);
|
||||
}
|
||||
|
||||
function handleHideWhenIdleChanged(sectionId, widgetIndex, enabled) {
|
||||
var widgets = getWidgetsForSection(sectionId).slice();
|
||||
if (widgetIndex < 0 || widgetIndex >= widgets.length) {
|
||||
return;
|
||||
}
|
||||
var newWidget = cloneWidgetData(widgets[widgetIndex]);
|
||||
newWidget.hideWhenIdle = enabled;
|
||||
widgets[widgetIndex] = newWidget;
|
||||
setWidgetsForSection(sectionId, widgets);
|
||||
}
|
||||
|
||||
function handleDiskUsageModeChanged(sectionId, widgetIndex, mode) {
|
||||
var widgets = getWidgetsForSection(sectionId).slice();
|
||||
if (widgetIndex < 0 || widgetIndex >= widgets.length) {
|
||||
@@ -714,6 +726,8 @@ Item {
|
||||
item.barShowOverflowBadge = widget.barShowOverflowBadge;
|
||||
if (widget.trayUseInlineExpansion !== undefined)
|
||||
item.trayUseInlineExpansion = widget.trayUseInlineExpansion;
|
||||
if (widget.hideWhenIdle !== undefined)
|
||||
item.hideWhenIdle = widget.hideWhenIdle;
|
||||
}
|
||||
widgets.push(item);
|
||||
});
|
||||
@@ -1003,6 +1017,9 @@ Item {
|
||||
onOverflowSettingChanged: (sectionId, widgetIndex, settingName, value) => {
|
||||
widgetsTab.handleOverflowSettingChanged(sectionId, widgetIndex, settingName, value);
|
||||
}
|
||||
onHideWhenIdleChanged: (sectionId, widgetIndex, enabled) => {
|
||||
widgetsTab.handleHideWhenIdleChanged(sectionId, widgetIndex, enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,6 +1087,9 @@ Item {
|
||||
onOverflowSettingChanged: (sectionId, widgetIndex, settingName, value) => {
|
||||
widgetsTab.handleOverflowSettingChanged(sectionId, widgetIndex, settingName, value);
|
||||
}
|
||||
onHideWhenIdleChanged: (sectionId, widgetIndex, enabled) => {
|
||||
widgetsTab.handleHideWhenIdleChanged(sectionId, widgetIndex, enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1137,6 +1157,9 @@ Item {
|
||||
onOverflowSettingChanged: (sectionId, widgetIndex, settingName, value) => {
|
||||
widgetsTab.handleOverflowSettingChanged(sectionId, widgetIndex, settingName, value);
|
||||
}
|
||||
onHideWhenIdleChanged: (sectionId, widgetIndex, enabled) => {
|
||||
widgetsTab.handleHideWhenIdleChanged(sectionId, widgetIndex, enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ Column {
|
||||
signal showInGbChanged(string sectionId, int widgetIndex, bool enabled)
|
||||
signal diskUsageModeChanged(string sectionId, int widgetIndex, int mode)
|
||||
signal overflowSettingChanged(string sectionId, int widgetIndex, string settingName, var value)
|
||||
signal hideWhenIdleChanged(string sectionId, int widgetIndex, bool enabled)
|
||||
|
||||
function cloneWidgetData(widget) {
|
||||
var result = {
|
||||
@@ -335,6 +336,25 @@ Column {
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: hideWhenIdleButton
|
||||
buttonSize: 28
|
||||
visible: modelData.id === "systemUpdate"
|
||||
iconName: "visibility_off"
|
||||
iconSize: 16
|
||||
iconColor: (modelData.hideWhenIdle === true) ? Theme.primary : Theme.outline
|
||||
onClicked: {
|
||||
root.hideWhenIdleChanged(root.sectionId, index, modelData.hideWhenIdle !== true);
|
||||
}
|
||||
onEntered: {
|
||||
const tooltipText = modelData.hideWhenIdle === true ? "Hide when no updates: ON" : "Hide when no updates: OFF";
|
||||
sharedTooltip.show(tooltipText, hideWhenIdleButton, 0, 0, "bottom");
|
||||
}
|
||||
onExited: {
|
||||
sharedTooltip.hide();
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: memMenuButton
|
||||
visible: modelData.id === "memUsage"
|
||||
|
||||
@@ -1,329 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankPopout {
|
||||
id: systemUpdatePopout
|
||||
|
||||
layerNamespace: "dms:system-update"
|
||||
|
||||
property var parentWidget: null
|
||||
property var triggerScreen: null
|
||||
|
||||
Ref {
|
||||
service: SystemUpdateService
|
||||
}
|
||||
|
||||
popupWidth: 400
|
||||
popupHeight: 500
|
||||
triggerWidth: 55
|
||||
positioning: ""
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: false
|
||||
|
||||
onBackgroundClicked: close()
|
||||
|
||||
onShouldBeVisibleChanged: {
|
||||
if (shouldBeVisible) {
|
||||
if (SystemUpdateService.updateCount === 0 && !SystemUpdateService.isChecking) {
|
||||
SystemUpdateService.checkForUpdates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Rectangle {
|
||||
id: updaterPanel
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
height: parent.height - Theme.spacingL * 2
|
||||
x: Theme.spacingL
|
||||
y: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("System Updates")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (SystemUpdateService.isChecking)
|
||||
return I18n.tr("Checking...");
|
||||
if (SystemUpdateService.hasError)
|
||||
return I18n.tr("Error");
|
||||
if (SystemUpdateService.updateCount === 0)
|
||||
return I18n.tr("Up to date");
|
||||
return SystemUpdateService.updateCount === 1
|
||||
? I18n.tr("%1 update").arg(SystemUpdateService.updateCount)
|
||||
: I18n.tr("%1 updates").arg(SystemUpdateService.updateCount);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (SystemUpdateService.hasError)
|
||||
return Theme.error;
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: checkForUpdatesButton
|
||||
buttonSize: 28
|
||||
iconName: "refresh"
|
||||
iconSize: 18
|
||||
z: 15
|
||||
iconColor: Theme.surfaceText
|
||||
enabled: !SystemUpdateService.isChecking
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
onClicked: {
|
||||
SystemUpdateService.checkForUpdates();
|
||||
}
|
||||
|
||||
RotationAnimation {
|
||||
target: checkForUpdatesButton
|
||||
property: "rotation"
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
running: SystemUpdateService.isChecking
|
||||
loops: Animation.Infinite
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
checkForUpdatesButton.rotation = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: {
|
||||
let usedHeight = 40 + Theme.spacingL;
|
||||
usedHeight += 48 + Theme.spacingL;
|
||||
return parent.height - usedHeight;
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.rightMargin: 0
|
||||
|
||||
StyledText {
|
||||
id: statusText
|
||||
width: parent.width
|
||||
text: {
|
||||
if (SystemUpdateService.hasError) {
|
||||
return I18n.tr("Failed to check for updates:\n%1").arg(SystemUpdateService.errorMessage);
|
||||
}
|
||||
if (!SystemUpdateService.helperAvailable) {
|
||||
return I18n.tr("No package manager found. Please install 'paru' or 'yay' on Arch-based systems to check for updates.");
|
||||
}
|
||||
if (SystemUpdateService.isChecking) {
|
||||
return I18n.tr("Checking for updates...");
|
||||
}
|
||||
if (SystemUpdateService.updateCount === 0) {
|
||||
return I18n.tr("Your system is up to date!");
|
||||
}
|
||||
return SystemUpdateService.updateCount === 1
|
||||
? I18n.tr("Found %1 package to update:").arg(SystemUpdateService.updateCount)
|
||||
: I18n.tr("Found %1 packages to update:").arg(SystemUpdateService.updateCount);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
if (SystemUpdateService.hasError)
|
||||
return Theme.errorText;
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
wrapMode: Text.WordWrap
|
||||
visible: SystemUpdateService.updateCount === 0 || SystemUpdateService.hasError || SystemUpdateService.isChecking
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: packagesList
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - (SystemUpdateService.updateCount === 0 || SystemUpdateService.hasError || SystemUpdateService.isChecking ? statusText.height + Theme.spacingM : 0)
|
||||
visible: SystemUpdateService.updateCount > 0 && !SystemUpdateService.isChecking && !SystemUpdateService.hasError
|
||||
clip: true
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
model: SystemUpdateService.availableUpdates
|
||||
|
||||
delegate: Rectangle {
|
||||
width: ListView.view.width - Theme.spacingM
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: packageMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent"
|
||||
border.color: Theme.outlineLight
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - Theme.spacingM
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: modelData.name || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: `${modelData.currentVersion} → ${modelData.newVersion}`
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: packageMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 48
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: updateMouseArea.containsMouse ? Theme.primaryHover : Theme.secondaryHover
|
||||
opacity: SystemUpdateService.updateCount > 0 ? 1.0 : 0.5
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "system_update_alt"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Update All")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: updateMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: SystemUpdateService.updateCount > 0
|
||||
onClicked: {
|
||||
SystemUpdateService.runUpdates();
|
||||
systemUpdatePopout.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: closeMouseArea.containsMouse ? Theme.errorPressed : Theme.secondaryHover
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "close"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Close")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
systemUpdatePopout.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,12 +61,13 @@ Singleton {
|
||||
signal screensaverStateUpdate(var data)
|
||||
signal clipboardStateUpdate(var data)
|
||||
signal locationStateUpdate(var data)
|
||||
signal sysupdateStateUpdate(var data)
|
||||
|
||||
property bool capsLockState: false
|
||||
property bool screensaverInhibited: false
|
||||
property var screensaverInhibitors: []
|
||||
|
||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location"]
|
||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location", "sysupdate"]
|
||||
|
||||
Component.onCompleted: {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
@@ -393,6 +394,8 @@ Singleton {
|
||||
clipboardStateUpdate(data);
|
||||
} else if (service === "location") {
|
||||
locationStateUpdate(data);
|
||||
} else if (service === "sysupdate") {
|
||||
sysupdateStateUpdate(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,4 +752,37 @@ Singleton {
|
||||
"name": name
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function sysupdateGetState(callback) {
|
||||
sendRequest("sysupdate.getState", null, callback);
|
||||
}
|
||||
|
||||
function sysupdateRefresh(force, callback) {
|
||||
sendRequest("sysupdate.refresh", {
|
||||
"force": force === true
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function sysupdateUpgrade(opts, callback) {
|
||||
const params = opts || {};
|
||||
sendRequest("sysupdate.upgrade", params, callback);
|
||||
}
|
||||
|
||||
function sysupdateCancel(callback) {
|
||||
sendRequest("sysupdate.cancel", null, callback);
|
||||
}
|
||||
|
||||
function sysupdateSetInterval(seconds, callback) {
|
||||
sendRequest("sysupdate.setInterval", {
|
||||
"seconds": seconds
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function sysupdateAcquire(callback) {
|
||||
sendRequest("sysupdate.acquire", null, callback);
|
||||
}
|
||||
|
||||
function sysupdateRelease(callback) {
|
||||
sendRequest("sysupdate.release", null, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ Singleton {
|
||||
return terminalFlags[terminal] ?? ["-e"]
|
||||
}
|
||||
|
||||
readonly property string terminal: Quickshell.env("TERMINAL") || "ghostty"
|
||||
readonly property string terminal: SessionData.resolveTerminal() || "ghostty"
|
||||
|
||||
function _terminalPrefix() {
|
||||
return [terminal].concat(getTerminalFlag(terminal))
|
||||
|
||||
@@ -860,7 +860,7 @@ Singleton {
|
||||
function checkPluginCompatibility(requiresDms) {
|
||||
if (!requiresDms)
|
||||
return true;
|
||||
return SystemUpdateService.checkVersionRequirement(requiresDms, SystemUpdateService.getParsedShellVersion());
|
||||
return ShellVersionService.checkVersionRequirement(requiresDms, ShellVersionService.getParsedShellVersion());
|
||||
}
|
||||
|
||||
function getIncompatiblePlugins() {
|
||||
|
||||
@@ -237,7 +237,7 @@ Singleton {
|
||||
const finalEnv = Object.assign({}, cursorEnv, overrideEnv);
|
||||
|
||||
if (desktopEntry.runInTerminal) {
|
||||
const terminal = Quickshell.env("TERMINAL") || "xterm";
|
||||
const terminal = SessionData.resolveTerminal() || "xterm";
|
||||
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ");
|
||||
const shellCmd = prefix.length > 0 ? `${prefix} ${escapedCmd}` : escapedCmd;
|
||||
Quickshell.execDetached({
|
||||
|
||||
134
quickshell/Services/ShellVersionService.qml
Normal file
134
quickshell/Services/ShellVersionService.qml
Normal file
@@ -0,0 +1,134 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string shellVersion: ""
|
||||
property string shellCodename: ""
|
||||
property string semverVersion: ""
|
||||
|
||||
function getParsedShellVersion() {
|
||||
return parseVersion(semverVersion);
|
||||
}
|
||||
|
||||
Process {
|
||||
id: versionDetection
|
||||
running: true
|
||||
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -d .git ]; then echo "(git) $(git rev-parse --short HEAD)"; elif [ -f VERSION ]; then cat VERSION; fi`]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: shellVersion = text.trim()
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: semverDetection
|
||||
running: true
|
||||
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f VERSION ]; then cat VERSION; fi`]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: semverVersion = text.trim()
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: codenameDetection
|
||||
running: true
|
||||
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f CODENAME ]; then cat CODENAME; fi`]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: shellCodename = text.trim()
|
||||
}
|
||||
}
|
||||
|
||||
function parseVersion(versionStr) {
|
||||
if (!versionStr || typeof versionStr !== "string") {
|
||||
return {
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 0
|
||||
};
|
||||
}
|
||||
let v = versionStr.trim();
|
||||
if (v.startsWith("v")) {
|
||||
v = v.substring(1);
|
||||
}
|
||||
const dashIdx = v.indexOf("-");
|
||||
if (dashIdx !== -1) {
|
||||
v = v.substring(0, dashIdx);
|
||||
}
|
||||
const plusIdx = v.indexOf("+");
|
||||
if (plusIdx !== -1) {
|
||||
v = v.substring(0, plusIdx);
|
||||
}
|
||||
const parts = v.split(".");
|
||||
return {
|
||||
major: parseInt(parts[0], 10) || 0,
|
||||
minor: parseInt(parts[1], 10) || 0,
|
||||
patch: parseInt(parts[2], 10) || 0
|
||||
};
|
||||
}
|
||||
|
||||
function compareVersions(v1, v2) {
|
||||
if (v1.major !== v2.major) {
|
||||
return v1.major - v2.major;
|
||||
}
|
||||
if (v1.minor !== v2.minor) {
|
||||
return v1.minor - v2.minor;
|
||||
}
|
||||
return v1.patch - v2.patch;
|
||||
}
|
||||
|
||||
function checkVersionRequirement(requirementStr, currentVersion) {
|
||||
if (!requirementStr || typeof requirementStr !== "string") {
|
||||
return true;
|
||||
}
|
||||
const req = requirementStr.trim();
|
||||
let operator = ">=";
|
||||
let versionPart = req;
|
||||
switch (true) {
|
||||
case req.startsWith(">="):
|
||||
operator = ">=";
|
||||
versionPart = req.substring(2);
|
||||
break;
|
||||
case req.startsWith("<="):
|
||||
operator = "<=";
|
||||
versionPart = req.substring(2);
|
||||
break;
|
||||
case req.startsWith(">"):
|
||||
operator = ">";
|
||||
versionPart = req.substring(1);
|
||||
break;
|
||||
case req.startsWith("<"):
|
||||
operator = "<";
|
||||
versionPart = req.substring(1);
|
||||
break;
|
||||
case req.startsWith("="):
|
||||
operator = "=";
|
||||
versionPart = req.substring(1);
|
||||
break;
|
||||
}
|
||||
|
||||
const reqVersion = parseVersion(versionPart);
|
||||
const cmp = compareVersions(currentVersion, reqVersion);
|
||||
switch (operator) {
|
||||
case ">=":
|
||||
return cmp >= 0;
|
||||
case ">":
|
||||
return cmp > 0;
|
||||
case "<=":
|
||||
return cmp <= 0;
|
||||
case "<":
|
||||
return cmp < 0;
|
||||
case "=":
|
||||
return cmp === 0;
|
||||
default:
|
||||
return cmp >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,288 +10,185 @@ Singleton {
|
||||
id: root
|
||||
|
||||
property int refCount: 0
|
||||
|
||||
property bool sysupdateAvailable: false
|
||||
|
||||
property var availableUpdates: []
|
||||
property bool isChecking: false
|
||||
property bool isUpgrading: false
|
||||
property bool hasError: false
|
||||
property string errorMessage: ""
|
||||
property string updChecker: ""
|
||||
property string pkgManager: ""
|
||||
property string errorCode: ""
|
||||
property var backends: []
|
||||
property string distribution: ""
|
||||
property string distributionPretty: ""
|
||||
property string pkgManager: ""
|
||||
property bool distributionSupported: false
|
||||
property string shellVersion: ""
|
||||
property string shellCodename: ""
|
||||
property string semverVersion: ""
|
||||
property var recentLog: []
|
||||
property int intervalSeconds: 1800
|
||||
property int lastCheckUnix: 0
|
||||
property int nextCheckUnix: 0
|
||||
|
||||
function getParsedShellVersion() {
|
||||
return parseVersion(semverVersion);
|
||||
}
|
||||
|
||||
readonly property var archBasedUCSettings: {
|
||||
"listUpdatesSettings": {
|
||||
"params": [],
|
||||
"correctExitCodes": [0, 2] // Exit code 0 = updates available, 2 = no updates
|
||||
},
|
||||
"parserSettings": {
|
||||
"lineRegex": /^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/,
|
||||
"entryProducer": function (match) {
|
||||
return {
|
||||
"name": match[1],
|
||||
"currentVersion": match[2],
|
||||
"newVersion": match[3],
|
||||
"description": `${match[1]} ${match[2]} → ${match[3]}`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var archBasedPMSettings: function(requiresSudo) {
|
||||
return {
|
||||
"listUpdatesSettings": {
|
||||
"params": ["-Qu"],
|
||||
"correctExitCodes": [0, 1] // Exit code 0 = updates available, 1 = no updates
|
||||
},
|
||||
"upgradeSettings": {
|
||||
"params": ["-Syu"],
|
||||
"requiresSudo": requiresSudo
|
||||
},
|
||||
"parserSettings": {
|
||||
"lineRegex": /^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/,
|
||||
"entryProducer": function (match) {
|
||||
return {
|
||||
"name": match[1],
|
||||
"currentVersion": match[2],
|
||||
"newVersion": match[3],
|
||||
"description": `${match[1]} ${match[2]} → ${match[3]}`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var fedoraBasedPMSettings: {
|
||||
"listUpdatesSettings": {
|
||||
"params": ["list", "--upgrades", "--quiet", "--color=never"],
|
||||
"correctExitCodes": [0, 1] // Exit code 0 = updates available, 1 = no updates
|
||||
},
|
||||
"upgradeSettings": {
|
||||
"params": ["upgrade"],
|
||||
"requiresSudo": true
|
||||
},
|
||||
"parserSettings": {
|
||||
"lineRegex": /^([^\s]+)\s+([^\s]+)\s+.*$/,
|
||||
"entryProducer": function (match) {
|
||||
return {
|
||||
"name": match[1],
|
||||
"currentVersion": "",
|
||||
"newVersion": match[2],
|
||||
"description": `${match[1]} → ${match[2]}`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var updateCheckerParams: {
|
||||
"checkupdates": archBasedUCSettings
|
||||
}
|
||||
readonly property var packageManagerParams: {
|
||||
"yay": archBasedPMSettings(false),
|
||||
"paru": archBasedPMSettings(false),
|
||||
"pacman": archBasedPMSettings(true),
|
||||
"dnf": fedoraBasedPMSettings
|
||||
}
|
||||
readonly property list<string> supportedDistributions: ["arch", "artix", "cachyos", "manjaro", "endeavouros", "fedora"]
|
||||
readonly property int updateCount: availableUpdates.length
|
||||
readonly property bool helperAvailable: pkgManager !== "" && distributionSupported
|
||||
readonly property bool helperAvailable: sysupdateAvailable && backends.length > 0
|
||||
|
||||
Process {
|
||||
id: distributionDetection
|
||||
command: ["sh", "-c", "cat /etc/os-release | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'"]
|
||||
running: true
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
distribution = stdout.text.trim().toLowerCase();
|
||||
distributionSupported = supportedDistributions.includes(distribution);
|
||||
|
||||
if (distributionSupported) {
|
||||
updateFinderDetection.running = true;
|
||||
pkgManagerDetection.running = true;
|
||||
checkForUpdates();
|
||||
} else {
|
||||
console.warn("SystemUpdate: Unsupported distribution:", distribution);
|
||||
}
|
||||
Connections {
|
||||
target: DMSService
|
||||
function onCapabilitiesReceived() {
|
||||
root.checkCapabilities();
|
||||
}
|
||||
function onConnectionStateChanged() {
|
||||
if (DMSService.isConnected) {
|
||||
root.checkCapabilities();
|
||||
} else {
|
||||
console.warn("SystemUpdate: Failed to detect distribution");
|
||||
root.sysupdateAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
|
||||
Component.onCompleted: {
|
||||
versionDetection.running = true;
|
||||
function onSysupdateStateUpdate(data) {
|
||||
root._applyState(data);
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: versionDetection
|
||||
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -d .git ]; then echo "(git) $(git rev-parse --short HEAD)"; elif [ -f VERSION ]; then cat VERSION; fi`]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
shellVersion = text.trim();
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (DMSService.dmsAvailable) {
|
||||
checkCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: semverDetection
|
||||
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f VERSION ]; then cat VERSION; fi`]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
semverVersion = text.trim();
|
||||
}
|
||||
function checkCapabilities() {
|
||||
if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) {
|
||||
sysupdateAvailable = false;
|
||||
return;
|
||||
}
|
||||
const has = DMSService.capabilities.includes("sysupdate");
|
||||
if (has && !sysupdateAvailable) {
|
||||
sysupdateAvailable = true;
|
||||
requestState();
|
||||
} else if (!has) {
|
||||
sysupdateAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: codenameDetection
|
||||
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f CODENAME ]; then cat CODENAME; fi`]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
shellCodename = text.trim();
|
||||
}
|
||||
function requestState() {
|
||||
if (!DMSService.isConnected || !sysupdateAvailable) {
|
||||
return;
|
||||
}
|
||||
DMSService.sysupdateGetState(resp => {
|
||||
if (resp && resp.result) {
|
||||
_applyState(resp.result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updateFinderDetection
|
||||
command: ["sh", "-c", "which checkupdates"]
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
const exeFound = stdout.text.trim();
|
||||
updChecker = exeFound.split('/').pop();
|
||||
} else {
|
||||
console.warn("SystemUpdate: No update checker found. Will use package manager.");
|
||||
}
|
||||
function _applyState(data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
availableUpdates = data.packages || [];
|
||||
backends = data.backends || [];
|
||||
distribution = data.distro || "";
|
||||
distributionPretty = data.distroPretty || "";
|
||||
distributionSupported = (backends.length > 0);
|
||||
recentLog = data.recentLog || [];
|
||||
intervalSeconds = data.intervalSeconds || 1800;
|
||||
lastCheckUnix = data.lastCheckUnix || 0;
|
||||
nextCheckUnix = data.nextCheckUnix || 0;
|
||||
|
||||
stdout: StdioCollector {}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: pkgManagerDetection
|
||||
command: ["sh", "-c", "which paru || which yay || which pacman || which dnf"]
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
const exeFound = stdout.text.trim();
|
||||
pkgManager = exeFound.split('/').pop();
|
||||
} else {
|
||||
console.warn("SystemUpdate: No package manager found");
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updateChecker
|
||||
|
||||
onExited: exitCode => {
|
||||
const phase = data.phase || "idle";
|
||||
switch (phase) {
|
||||
case "refreshing":
|
||||
isChecking = true;
|
||||
isUpgrading = false;
|
||||
break;
|
||||
case "upgrading":
|
||||
isChecking = false;
|
||||
const correctExitCodes = updChecker.length > 0 ? [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.correctExitCodes) : [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.correctExitCodes);
|
||||
if (correctExitCodes.includes(exitCode)) {
|
||||
parseUpdates(stdout.text);
|
||||
hasError = false;
|
||||
errorMessage = "";
|
||||
} else {
|
||||
hasError = true;
|
||||
errorMessage = "Failed to check for updates";
|
||||
console.warn("SystemUpdate: Update check failed with code:", exitCode);
|
||||
}
|
||||
isUpgrading = true;
|
||||
break;
|
||||
default:
|
||||
isChecking = false;
|
||||
isUpgrading = false;
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
}
|
||||
if (data.error) {
|
||||
hasError = true;
|
||||
errorMessage = data.error.message || "";
|
||||
errorCode = data.error.code || "";
|
||||
} else {
|
||||
hasError = false;
|
||||
errorMessage = "";
|
||||
errorCode = "";
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updater
|
||||
onExited: exitCode => {
|
||||
checkForUpdates();
|
||||
if (backends.length > 0) {
|
||||
const sys = backends.find(b => b.repo === "system" || b.repo === "ostree");
|
||||
pkgManager = sys ? sys.id : backends[0].id;
|
||||
} else {
|
||||
pkgManager = "";
|
||||
}
|
||||
}
|
||||
|
||||
function checkForUpdates() {
|
||||
if (!distributionSupported || (!pkgManager && !updChecker) || isChecking)
|
||||
return;
|
||||
isChecking = true;
|
||||
hasError = false;
|
||||
if (pkgManager === "paru" || pkgManager === "yay") {
|
||||
const repoCmd = updChecker.length > 0 ? updChecker : `${pkgManager} -Qu`;
|
||||
updateChecker.command = ["sh", "-c", `(${repoCmd} 2>/dev/null; ${pkgManager} -Qua 2>/dev/null) || true`];
|
||||
} else if (updChecker.length > 0) {
|
||||
updateChecker.command = [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.params);
|
||||
} else {
|
||||
updateChecker.command = [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.params);
|
||||
}
|
||||
updateChecker.running = true;
|
||||
DMSService.sysupdateRefresh(false, null);
|
||||
}
|
||||
|
||||
function parseUpdates(output) {
|
||||
const lines = output.trim().split('\n').filter(line => line.trim());
|
||||
const updates = [];
|
||||
|
||||
const regex = packageManagerParams[pkgManager].parserSettings.lineRegex;
|
||||
const entryProducer = packageManagerParams[pkgManager].parserSettings.entryProducer;
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(regex);
|
||||
if (match) {
|
||||
updates.push(entryProducer(match));
|
||||
}
|
||||
}
|
||||
|
||||
availableUpdates = updates;
|
||||
}
|
||||
|
||||
function runUpdates() {
|
||||
if (!distributionSupported || !pkgManager || updateCount === 0)
|
||||
return;
|
||||
const terminal = Quickshell.env("TERMINAL") || "xterm";
|
||||
|
||||
function runUpdates(opts) {
|
||||
const params = opts || {};
|
||||
if (SettingsData.updaterUseCustomCommand && SettingsData.updaterCustomCommand.length > 0) {
|
||||
const updateCommand = `${SettingsData.updaterCustomCommand} && echo -n "Updates complete! " ; echo "Press Enter to close..." && read`;
|
||||
const termClass = SettingsData.updaterTerminalAdditionalParams;
|
||||
|
||||
var finalCommand = [terminal];
|
||||
if (termClass.length > 0) {
|
||||
finalCommand = finalCommand.concat(termClass.split(" "));
|
||||
}
|
||||
finalCommand.push("-e");
|
||||
finalCommand.push("sh");
|
||||
finalCommand.push("-c");
|
||||
finalCommand.push(updateCommand);
|
||||
updater.command = finalCommand;
|
||||
} else {
|
||||
const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ");
|
||||
const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : "";
|
||||
const updateCommand = `${sudo} ${pkgManager} ${params} && echo -n "Updates complete! " ; echo "Press Enter to close..." && read`;
|
||||
|
||||
updater.command = [terminal, "-e", "sh", "-c", updateCommand];
|
||||
_runCustomTerminalCommand();
|
||||
return;
|
||||
}
|
||||
updater.running = true;
|
||||
DMSService.sysupdateUpgrade(params, null);
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 30 * 60 * 1000
|
||||
repeat: true
|
||||
running: refCount > 0 && distributionSupported && (pkgManager || updChecker)
|
||||
onTriggered: checkForUpdates()
|
||||
function cancelUpdates() {
|
||||
DMSService.sysupdateCancel(null);
|
||||
}
|
||||
|
||||
function setInterval(seconds) {
|
||||
DMSService.sysupdateSetInterval(seconds, null);
|
||||
}
|
||||
|
||||
function _runCustomTerminalCommand() {
|
||||
const terminal = SessionData.resolveTerminal();
|
||||
if (!terminal || terminal.length === 0) {
|
||||
ToastService.showError(I18n.tr("No terminal configured"), I18n.tr("Pick a terminal in Settings → Launcher (or set $TERMINAL)."));
|
||||
return;
|
||||
}
|
||||
const updateCommand = `${SettingsData.updaterCustomCommand} && echo -n "Updates complete! " ; echo "Press Enter to close..." && read`;
|
||||
const termClass = SettingsData.updaterTerminalAdditionalParams || "";
|
||||
var argv = [terminal];
|
||||
if (termClass.length > 0) {
|
||||
argv = argv.concat(termClass.split(" "));
|
||||
}
|
||||
argv.push("-e");
|
||||
argv.push("sh");
|
||||
argv.push("-c");
|
||||
argv.push(updateCommand);
|
||||
customRunner.command = argv;
|
||||
customRunner.running = true;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: customRunner
|
||||
onExited: root.checkForUpdates()
|
||||
}
|
||||
|
||||
onRefCountChanged: _syncAcquire()
|
||||
onSysupdateAvailableChanged: _syncAcquire()
|
||||
|
||||
property bool _acquired: false
|
||||
|
||||
function _syncAcquire() {
|
||||
const want = refCount > 0 && sysupdateAvailable;
|
||||
if (want === _acquired) {
|
||||
return;
|
||||
}
|
||||
_acquired = want;
|
||||
if (want) {
|
||||
DMSService.sysupdateAcquire(null);
|
||||
return;
|
||||
}
|
||||
DMSService.sysupdateRelease(null);
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
@@ -301,96 +198,11 @@ Singleton {
|
||||
if (root.isChecking) {
|
||||
return "ERROR: already checking";
|
||||
}
|
||||
if (!distributionSupported) {
|
||||
return "ERROR: distribution not supported";
|
||||
}
|
||||
if (!pkgManager && !updChecker) {
|
||||
return "ERROR: update checker not available";
|
||||
if (root.backends.length === 0) {
|
||||
return "ERROR: no package manager available";
|
||||
}
|
||||
root.checkForUpdates();
|
||||
return "SUCCESS: Now checking...";
|
||||
}
|
||||
}
|
||||
|
||||
function parseVersion(versionStr) {
|
||||
if (!versionStr || typeof versionStr !== "string")
|
||||
return {
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 0
|
||||
};
|
||||
|
||||
let v = versionStr.trim();
|
||||
if (v.startsWith("v"))
|
||||
v = v.substring(1);
|
||||
|
||||
const dashIdx = v.indexOf("-");
|
||||
if (dashIdx !== -1)
|
||||
v = v.substring(0, dashIdx);
|
||||
|
||||
const plusIdx = v.indexOf("+");
|
||||
if (plusIdx !== -1)
|
||||
v = v.substring(0, plusIdx);
|
||||
|
||||
const parts = v.split(".");
|
||||
return {
|
||||
major: parseInt(parts[0], 10) || 0,
|
||||
minor: parseInt(parts[1], 10) || 0,
|
||||
patch: parseInt(parts[2], 10) || 0
|
||||
};
|
||||
}
|
||||
|
||||
function compareVersions(v1, v2) {
|
||||
if (v1.major !== v2.major)
|
||||
return v1.major - v2.major;
|
||||
if (v1.minor !== v2.minor)
|
||||
return v1.minor - v2.minor;
|
||||
return v1.patch - v2.patch;
|
||||
}
|
||||
|
||||
function checkVersionRequirement(requirementStr, currentVersion) {
|
||||
if (!requirementStr || typeof requirementStr !== "string")
|
||||
return true;
|
||||
|
||||
const req = requirementStr.trim();
|
||||
let operator = "";
|
||||
let versionPart = req;
|
||||
|
||||
if (req.startsWith(">=")) {
|
||||
operator = ">=";
|
||||
versionPart = req.substring(2);
|
||||
} else if (req.startsWith("<=")) {
|
||||
operator = "<=";
|
||||
versionPart = req.substring(2);
|
||||
} else if (req.startsWith(">")) {
|
||||
operator = ">";
|
||||
versionPart = req.substring(1);
|
||||
} else if (req.startsWith("<")) {
|
||||
operator = "<";
|
||||
versionPart = req.substring(1);
|
||||
} else if (req.startsWith("=")) {
|
||||
operator = "=";
|
||||
versionPart = req.substring(1);
|
||||
} else {
|
||||
operator = ">=";
|
||||
}
|
||||
|
||||
const reqVersion = parseVersion(versionPart);
|
||||
const cmp = compareVersions(currentVersion, reqVersion);
|
||||
|
||||
switch (operator) {
|
||||
case ">=":
|
||||
return cmp >= 0;
|
||||
case ">":
|
||||
return cmp > 0;
|
||||
case "<=":
|
||||
return cmp <= 0;
|
||||
case "<":
|
||||
return cmp < 0;
|
||||
case "=":
|
||||
return cmp === 0;
|
||||
default:
|
||||
return cmp >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user