autofmt arch stuff

This commit is contained in:
LemmyCook 2025-08-24 10:46:28 -04:00
parent 9ba5abc047
commit 9666ce4f5a
3 changed files with 399 additions and 393 deletions

View file

@ -8,220 +8,221 @@ import qs.Services
import qs.Widgets import qs.Widgets
NPanel { NPanel {
id: root id: root
panelWidth: 380 * scaling panelWidth: 380 * scaling
panelHeight: 500 * scaling panelHeight: 500 * scaling
panelAnchorRight: true panelAnchorRight: true
// Auto-refresh when service updates // Auto-refresh when service updates
Connections { Connections {
target: ArchUpdaterService target: ArchUpdaterService
function onUpdatePackagesChanged() { function onUpdatePackagesChanged() {
// Force UI update when packages change // Force UI update when packages change
if (root.visible) { if (root.visible) {
// Small delay to ensure data is fully updated // Small delay to ensure data is fully updated
Qt.callLater(() => { Qt.callLater(() => {
// Force a UI update by triggering a property change // Force a UI update by triggering a property change
ArchUpdaterService.updatePackages = ArchUpdaterService.updatePackages; ArchUpdaterService.updatePackages = ArchUpdaterService.updatePackages
}, 100); }, 100)
} }
}
} }
}
panelContent: Rectangle { panelContent: Rectangle {
color: Color.mSurface color: Color.mSurface
radius: Style.radiusL * scaling radius: Style.radiusL * scaling
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.marginL * scaling anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
// Header
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
NIcon {
text: "system_update"
font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary
}
Text {
text: "System Updates"
font.pointSize: Style.fontSizeL * scaling
font.family: Settings.data.ui.fontDefault
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
}
NIconButton {
icon: "close"
tooltipText: "Close"
sizeMultiplier: 0.8
onClicked: root.close()
}
}
NDivider {
Layout.fillWidth: true
}
// Update summary
Text {
text: ArchUpdaterService.updatePackages.length + " package" + (ArchUpdaterService.updatePackages.length
!== 1 ? "s" : "") + " can be updated"
font.pointSize: Style.fontSizeL * scaling
font.family: Settings.data.ui.fontDefault
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
}
// Package selection info
Text {
text: ArchUpdaterService.selectedPackagesCount + " of " + ArchUpdaterService.updatePackages.length + " packages selected"
font.pointSize: Style.fontSizeS * scaling
font.family: Settings.data.ui.fontDefault
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
}
// Package list
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Color.mSurfaceVariant
radius: Style.radiusM * scaling
ListView {
id: packageListView
anchors.fill: parent
anchors.margins: Style.marginS * scaling
clip: true
model: ArchUpdaterService.updatePackages
spacing: Style.marginXS * scaling
delegate: Rectangle {
width: packageListView.width
height: 50 * scaling
color: Color.transparent
radius: Style.radiusS * scaling
// Header
RowLayout { RowLayout {
Layout.fillWidth: true anchors.fill: parent
spacing: Style.marginM * scaling anchors.margins: Style.marginS * scaling
spacing: Style.marginS * scaling
NIcon { // Checkbox for selection
text: "system_update" NIconButton {
font.pointSize: Style.fontSizeXXL * scaling id: checkbox
color: Color.mPrimary icon: "check_box_outline_blank"
onClicked: {
const isSelected = ArchUpdaterService.isPackageSelected(modelData.name)
if (isSelected) {
ArchUpdaterService.togglePackageSelection(modelData.name)
icon = "check_box_outline_blank"
colorFg = Color.mOnSurfaceVariant
} else {
ArchUpdaterService.togglePackageSelection(modelData.name)
icon = "check_box"
colorFg = Color.mPrimary
}
}
colorBg: Color.transparent
colorFg: Color.mOnSurfaceVariant
Layout.preferredWidth: 30 * scaling
Layout.preferredHeight: 30 * scaling
Component.onCompleted: {
// Set initial state
if (ArchUpdaterService.isPackageSelected(modelData.name)) {
icon = "check_box"
colorFg = Color.mPrimary
}
}
}
// Package info
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXXS * scaling
Text {
text: modelData.name
font.pointSize: Style.fontSizeM * scaling
font.family: Settings.data.ui.fontDefault
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
} }
Text { Text {
text: "System Updates" text: modelData.oldVersion + " → " + modelData.newVersion
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeS * scaling
font.family: Settings.data.ui.fontDefault font.family: Settings.data.ui.fontDefault
font.weight: Style.fontWeightBold color: Color.mOnSurfaceVariant
color: Color.mOnSurface Layout.fillWidth: true
Layout.fillWidth: true
}
NIconButton {
icon: "close"
tooltipText: "Close"
sizeMultiplier: 0.8
onClicked: root.close()
} }
}
} }
}
NDivider { Layout.fillWidth: true } ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
// Update summary }
Text {
text: ArchUpdaterService.updatePackages.length + " package" + (ArchUpdaterService.updatePackages.length !== 1 ? "s" : "") + " can be updated"
font.pointSize: Style.fontSizeL * scaling
font.family: Settings.data.ui.fontDefault
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
}
// Package selection info
Text {
text: ArchUpdaterService.selectedPackagesCount + " of " + ArchUpdaterService.updatePackages.length + " packages selected"
font.pointSize: Style.fontSizeS * scaling
font.family: Settings.data.ui.fontDefault
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
}
// Package list
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Color.mSurfaceVariant
radius: Style.radiusM * scaling
ListView {
id: packageListView
anchors.fill: parent
anchors.margins: Style.marginS * scaling
clip: true
model: ArchUpdaterService.updatePackages
spacing: Style.marginXS * scaling
delegate: Rectangle {
width: packageListView.width
height: 50 * scaling
color: Color.transparent
radius: Style.radiusS * scaling
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginS * scaling
spacing: Style.marginS * scaling
// Checkbox for selection
NIconButton {
id: checkbox
icon: "check_box_outline_blank"
onClicked: {
const isSelected = ArchUpdaterService.isPackageSelected(modelData.name);
if (isSelected) {
ArchUpdaterService.togglePackageSelection(modelData.name);
icon = "check_box_outline_blank";
colorFg = Color.mOnSurfaceVariant;
} else {
ArchUpdaterService.togglePackageSelection(modelData.name);
icon = "check_box";
colorFg = Color.mPrimary;
}
}
colorBg: Color.transparent
colorFg: Color.mOnSurfaceVariant
Layout.preferredWidth: 30 * scaling
Layout.preferredHeight: 30 * scaling
Component.onCompleted: {
// Set initial state
if (ArchUpdaterService.isPackageSelected(modelData.name)) {
icon = "check_box";
colorFg = Color.mPrimary;
}
}
}
// Package info
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXXS * scaling
Text {
text: modelData.name
font.pointSize: Style.fontSizeM * scaling
font.family: Settings.data.ui.fontDefault
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
}
Text {
text: modelData.oldVersion + " → " + modelData.newVersion
font.pointSize: Style.fontSizeS * scaling
font.family: Settings.data.ui.fontDefault
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
}
}
}
}
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
}
}
// Action buttons
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS * scaling
NIconButton {
icon: "refresh"
tooltipText: "Check for updates"
onClicked: {
ArchUpdaterService.doPoll();
}
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
}
NIconButton {
icon: ArchUpdaterService.updateInProgress ? "hourglass_empty" : "system_update"
tooltipText: ArchUpdaterService.updateInProgress ? "Update in progress..." : "Update all packages"
enabled: !ArchUpdaterService.updateInProgress
onClicked: {
ArchUpdaterService.runUpdate();
}
colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : Color.mPrimary
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : Color.mOnPrimary
Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
}
NIconButton {
icon: ArchUpdaterService.updateInProgress ? "hourglass_empty" : "settings"
tooltipText: ArchUpdaterService.updateInProgress ? "Update in progress..." : "Update selected packages"
enabled: !ArchUpdaterService.updateInProgress && ArchUpdaterService.selectedPackagesCount > 0
onClicked: {
if (ArchUpdaterService.selectedPackagesCount > 0) {
ArchUpdaterService.runSelectiveUpdate();
}
}
colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant :
(ArchUpdaterService.selectedPackagesCount > 0 ? Color.mSecondary : Color.mSurfaceVariant)
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant :
(ArchUpdaterService.selectedPackagesCount > 0 ? Color.mOnSecondary : Color.mOnSurfaceVariant)
Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
}
}
} }
}
// Action buttons
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS * scaling
NIconButton {
icon: "refresh"
tooltipText: "Check for updates"
onClicked: {
ArchUpdaterService.doPoll()
}
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
}
NIconButton {
icon: ArchUpdaterService.updateInProgress ? "hourglass_empty" : "system_update"
tooltipText: ArchUpdaterService.updateInProgress ? "Update in progress..." : "Update all packages"
enabled: !ArchUpdaterService.updateInProgress
onClicked: {
ArchUpdaterService.runUpdate()
}
colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : Color.mPrimary
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : Color.mOnPrimary
Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
}
NIconButton {
icon: ArchUpdaterService.updateInProgress ? "hourglass_empty" : "settings"
tooltipText: ArchUpdaterService.updateInProgress ? "Update in progress..." : "Update selected packages"
enabled: !ArchUpdaterService.updateInProgress && ArchUpdaterService.selectedPackagesCount > 0
onClicked: {
if (ArchUpdaterService.selectedPackagesCount > 0) {
ArchUpdaterService.runSelectiveUpdate()
}
}
colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : (ArchUpdaterService.selectedPackagesCount
> 0 ? Color.mSecondary : Color.mSurfaceVariant)
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : (ArchUpdaterService.selectedPackagesCount
> 0 ? Color.mOnSecondary : Color.mOnSurfaceVariant)
Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
}
}
} }
}
} }

View file

@ -6,66 +6,68 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
NIconButton { NIconButton {
id: root id: root
sizeMultiplier: 0.8 sizeMultiplier: 0.8
readonly property real scaling: ScalingService.scale(screen) readonly property real scaling: ScalingService.scale(screen)
colorBg: Color.mSurfaceVariant colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface colorFg: Color.mOnSurface
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: Color.transparent
// Enhanced icon states with better visual feedback // Enhanced icon states with better visual feedback
icon: { icon: {
if (ArchUpdaterService.busy) return "sync" if (ArchUpdaterService.busy)
if (ArchUpdaterService.updatePackages.length > 0) { return "sync"
// Show different icons based on update count if (ArchUpdaterService.updatePackages.length > 0) {
const count = ArchUpdaterService.updatePackages.length // Show different icons based on update count
if (count > 50) return "system_update_alt" // Many updates const count = ArchUpdaterService.updatePackages.length
if (count > 10) return "system_update" // Moderate updates if (count > 50)
return "system_update" // Few updates return "system_update_alt" // Many updates
} if (count > 10)
return "task_alt" return "system_update" // Moderate updates
return "system_update" // Few updates
} }
return "task_alt"
}
// Enhanced tooltip with more information // Enhanced tooltip with more information
tooltipText: { tooltipText: {
if (ArchUpdaterService.busy) if (ArchUpdaterService.busy)
return "Checking for updates…"; return "Checking for updates…"
var count = ArchUpdaterService.updatePackages.length; var count = ArchUpdaterService.updatePackages.length
if (count === 0) if (count === 0)
return "System is up to date ✓"; return "System is up to date ✓"
var header = count === 1 ? "One package can be upgraded:" : (count + " packages can be upgraded:"); var header = count === 1 ? "One package can be upgraded:" : (count + " packages can be upgraded:")
var list = ArchUpdaterService.updatePackages || []; var list = ArchUpdaterService.updatePackages || []
var s = ""; var s = ""
var limit = Math.min(list.length, 8); // Reduced to 8 for better readability var limit = Math.min(list.length, 8)
for (var i = 0; i < limit; ++i) { // Reduced to 8 for better readability
var p = list[i]; for (var i = 0; i < limit; ++i) {
s += (i ? "\n" : "") + (p.name + ": " + p.oldVersion + " → " + p.newVersion); var p = list[i]
} s += (i ? "\n" : "") + (p.name + ": " + p.oldVersion + " → " + p.newVersion)
if (list.length > 8)
s += "\n… and " + (list.length - 8) + " more";
return header + "\n\n" + s + "\n\nClick to update system";
} }
if (list.length > 8)
s += "\n… and " + (list.length - 8) + " more"
// Enhanced click behavior with confirmation return header + "\n\n" + s + "\n\nClick to update system"
onClicked: { }
if (ArchUpdaterService.busy)
return; // Enhanced click behavior with confirmation
onClicked: {
if (ArchUpdaterService.updatePackages.length > 0) { if (ArchUpdaterService.busy)
// Show confirmation dialog for updates return
PanelService.updatePanel.toggle(screen);
} else { if (ArchUpdaterService.updatePackages.length > 0) {
// Just refresh if no updates available // Show confirmation dialog for updates
ArchUpdaterService.doPoll(); PanelService.updatePanel.toggle(screen)
} } else {
// Just refresh if no updates available
ArchUpdaterService.doPoll()
} }
}
} }

View file

@ -1,153 +1,156 @@
pragma Singleton pragma Singleton
import Quickshell import Quickshell
import QtQuick import QtQuick
import Quickshell.Io import Quickshell.Io
Singleton { Singleton {
id: updateService id: updateService
// Core properties // Core properties
readonly property bool busy: checkupdatesProcess.running readonly property bool busy: checkupdatesProcess.running
readonly property int updates: updatePackages.length readonly property int updates: updatePackages.length
property var updatePackages: [] property var updatePackages: []
property var selectedPackages: [] property var selectedPackages: []
property int selectedPackagesCount: 0 property int selectedPackagesCount: 0
property bool updateInProgress: false property bool updateInProgress: false
// Process for checking updates // Process for checking updates
Process { Process {
id: checkupdatesProcess id: checkupdatesProcess
command: ["checkupdates"] command: ["checkupdates"]
onExited: function(exitCode) { onExited: function (exitCode) {
if (exitCode !== 0 && exitCode !== 2) { if (exitCode !== 0 && exitCode !== 2) {
console.warn("[UpdateService] checkupdates failed (code:", exitCode, ")"); console.warn("[UpdateService] checkupdates failed (code:", exitCode, ")")
updatePackages = []; updatePackages = []
return; return
} }
}
stdout: StdioCollector {
onStreamFinished: {
parseCheckupdatesOutput(text);
}
}
} }
stdout: StdioCollector {
// Parse checkupdates output onStreamFinished: {
function parseCheckupdatesOutput(output) { parseCheckupdatesOutput(text)
const lines = output.trim().split('\n').filter(line => line.trim()); }
const packages = [];
for (const line of lines) {
const m = line.match(/^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/);
if (m) {
packages.push({
name: m[1],
oldVersion: m[2],
newVersion: m[3],
description: `${m[1]} ${m[2]} -> ${m[3]}`
});
}
}
updatePackages = packages;
} }
}
// Check for updates
function doPoll() { // Parse checkupdates output
if (busy) return; function parseCheckupdatesOutput(output) {
checkupdatesProcess.running = true; const lines = output.trim().split('\n').filter(line => line.trim())
const packages = []
for (const line of lines) {
const m = line.match(/^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/)
if (m) {
packages.push({
"name": m[1],
"oldVersion": m[2],
"newVersion": m[3],
"description": `${m[1]} ${m[2]} -> ${m[3]}`
})
}
} }
// Update all packages updatePackages = packages
function runUpdate() { }
if (updates === 0) {
doPoll(); // Check for updates
return; function doPoll() {
} if (busy)
return
updateInProgress = true; checkupdatesProcess.running = true
Quickshell.execDetached(["pkexec", "pacman", "-Syu", "--noconfirm"]); }
// Refresh after updates with multiple attempts // Update all packages
refreshAfterUpdate(); function runUpdate() {
if (updates === 0) {
doPoll()
return
} }
// Update selected packages updateInProgress = true
function runSelectiveUpdate() { Quickshell.execDetached(["pkexec", "pacman", "-Syu", "--noconfirm"])
if (selectedPackages.length === 0) return;
// Refresh after updates with multiple attempts
updateInProgress = true; refreshAfterUpdate()
const command = ["pkexec", "pacman", "-S", "--noconfirm"].concat(selectedPackages); }
Quickshell.execDetached(command);
// Update selected packages
// Clear selection and refresh function runSelectiveUpdate() {
selectedPackages = []; if (selectedPackages.length === 0)
selectedPackagesCount = 0; return
refreshAfterUpdate();
updateInProgress = true
const command = ["pkexec", "pacman", "-S", "--noconfirm"].concat(selectedPackages)
Quickshell.execDetached(command)
// Clear selection and refresh
selectedPackages = []
selectedPackagesCount = 0
refreshAfterUpdate()
}
// Package selection functions
function togglePackageSelection(packageName) {
const index = selectedPackages.indexOf(packageName)
if (index > -1) {
selectedPackages.splice(index, 1)
} else {
selectedPackages.push(packageName)
} }
selectedPackagesCount = selectedPackages.length
// Package selection functions }
function togglePackageSelection(packageName) {
const index = selectedPackages.indexOf(packageName); function selectAllPackages() {
if (index > -1) { selectedPackages = updatePackages.map(pkg => pkg.name)
selectedPackages.splice(index, 1); selectedPackagesCount = selectedPackages.length
} else { }
selectedPackages.push(packageName);
} function deselectAllPackages() {
selectedPackagesCount = selectedPackages.length; selectedPackages = []
} selectedPackagesCount = 0
}
function selectAllPackages() {
selectedPackages = updatePackages.map(pkg => pkg.name); function isPackageSelected(packageName) {
selectedPackagesCount = selectedPackages.length; return selectedPackages.indexOf(packageName) > -1
} }
function deselectAllPackages() { // Robust refresh after updates
selectedPackages = []; function refreshAfterUpdate() {
selectedPackagesCount = 0; // First refresh attempt after 3 seconds
} Qt.callLater(() => {
doPoll()
function isPackageSelected(packageName) { }, 3000)
return selectedPackages.indexOf(packageName) > -1;
} // Second refresh attempt after 8 seconds
Qt.callLater(() => {
// Robust refresh after updates doPoll()
function refreshAfterUpdate() { }, 8000)
// First refresh attempt after 3 seconds
Qt.callLater(() => { // Third refresh attempt after 15 seconds
doPoll(); Qt.callLater(() => {
}, 3000); doPoll()
updateInProgress = false
// Second refresh attempt after 8 seconds }, 15000)
Qt.callLater(() => {
doPoll(); // Final refresh attempt after 30 seconds
}, 8000); Qt.callLater(() => {
doPoll()
// Third refresh attempt after 15 seconds }, 30000)
Qt.callLater(() => { }
doPoll();
updateInProgress = false; // Notification helper
}, 15000); function notify(title, body) {
Quickshell.execDetached(["notify-send", "-a", "UpdateService", "-i", "system-software-update", title, body])
// Final refresh attempt after 30 seconds }
Qt.callLater(() => {
doPoll(); // Auto-poll every 15 minutes
}, 30000); Timer {
} interval: 15 * 60 * 1000 // 15 minutes
repeat: true
// Notification helper running: true
function notify(title, body) { onTriggered: doPoll()
Quickshell.execDetached(["notify-send", "-a", "UpdateService", "-i", "system-software-update", title, body]); }
}
// Initial check
// Auto-poll every 15 minutes Component.onCompleted: doPoll()
Timer {
interval: 15 * 60 * 1000 // 15 minutes
repeat: true
running: true
onTriggered: doPoll()
}
// Initial check
Component.onCompleted: doPoll()
} }