From 1eae0eb3d4014e5290447b591918d2a6175a39e6 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sun, 31 Aug 2025 13:47:06 +0200 Subject: [PATCH] Fix ArchUpdater error codes, revert TrayMenu TrayMenu: reverted it to the old PopupPanel for ignored ArchUpdater: paru error code 1 = no updates available --- Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml | 65 ++- Modules/Bar/Extras/TrayMenu.qml | 370 +++++++++--------- Modules/Bar/Widgets/Tray.qml | 76 ++-- Services/ArchUpdaterService.qml | 137 +++++-- shell.qml | 5 - 5 files changed, 416 insertions(+), 237 deletions(-) diff --git a/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml b/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml index 1e3ad5f..2457776 100644 --- a/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml +++ b/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml @@ -68,7 +68,8 @@ NPanel { // Update summary (only show when packages are available) NText { - visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.aurBusy + visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed + && !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy && ArchUpdaterService.totalUpdates > 0 text: ArchUpdaterService.totalUpdates + " package" + (ArchUpdaterService.totalUpdates !== 1 ? "s" : "") + " can be updated" font.pointSize: Style.fontSizeL * scaling @@ -79,7 +80,8 @@ NPanel { // Package selection info (only show when not updating and have packages) NText { - visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.aurBusy + visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed + && !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy && ArchUpdaterService.totalUpdates > 0 text: ArchUpdaterService.selectedPackagesCount + " of " + ArchUpdaterService.totalUpdates + " packages selected" font.pointSize: Style.fontSizeS * scaling @@ -127,6 +129,58 @@ NPanel { } // Spacer } + // Check failed state (AUR down, network issues, etc.) + Item { + Layout.fillWidth: true + Layout.fillHeight: true + visible: ArchUpdaterService.checkFailed && !ArchUpdaterService.updateInProgress + && !ArchUpdaterService.updateFailed + + ColumnLayout { + anchors.centerIn: parent + spacing: Style.marginM * scaling + + NIcon { + text: "error" + font.pointSize: Style.fontSizeXXXL * scaling + color: Color.mError + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: "Cannot check for updates" + font.pointSize: Style.fontSizeL * scaling + color: Color.mOnSurface + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: ArchUpdaterService.lastCheckError + || "AUR helper is unavailable or network connection failed. This could be due to AUR being down, network issues, or missing AUR helper (yay/paru)." + font.pointSize: Style.fontSizeNormal * scaling + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + Layout.maximumWidth: 280 * scaling + } + + // Prominent refresh button + NIconButton { + icon: "refresh" + tooltipText: "Try checking again" + sizeRatio: 1.2 + colorBg: Color.mPrimary + colorFg: Color.mOnPrimary + onClicked: { + ArchUpdaterService.forceRefresh() + } + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Style.marginL * scaling + } + } + } + // Update failed state Item { Layout.fillWidth: true @@ -181,7 +235,8 @@ NPanel { Item { Layout.fillWidth: true Layout.fillHeight: true - visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.aurBusy + visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed + && !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy && ArchUpdaterService.totalUpdates === 0 ColumnLayout { @@ -251,7 +306,8 @@ NPanel { // Package list (only show when not in any special state) NBox { - visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.aurBusy + visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed + && !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy && ArchUpdaterService.totalUpdates > 0 Layout.fillWidth: true Layout.fillHeight: true @@ -340,6 +396,7 @@ NPanel { // Action buttons (only show when not updating) RowLayout { visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed + && !ArchUpdaterService.checkFailed Layout.fillWidth: true spacing: Style.marginL * scaling diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml index ef44f3c..0d92cfc 100644 --- a/Modules/Bar/Extras/TrayMenu.qml +++ b/Modules/Bar/Extras/TrayMenu.qml @@ -6,21 +6,29 @@ import qs.Commons import qs.Services import qs.Widgets -NPanel { +PopupWindow { id: root - - objectName: "trayMenu" - - panelWidth: 180 * scaling - panelHeight: 220 * scaling - panelAnchorRight: true - property QsMenuHandle menu property var anchorItem: null property real anchorX property real anchorY property bool isSubMenu: false - property bool isHovered: false + property bool isHovered: rootMouseArea.containsMouse + property ShellScreen screen + property real scaling: screen ? ScalingService.scale(screen) : 1.0 + + readonly property int menuWidth: 180 + + implicitWidth: menuWidth * scaling + + // Use the content height of the Flickable for implicit height + implicitHeight: Math.min(screen ? screen.height * 0.9 : Screen.height * 0.9, + flickable.contentHeight + (Style.marginS * 2 * scaling)) + visible: false + color: Color.transparent + anchor.item: anchorItem + anchor.rect.x: anchorX + anchor.rect.y: anchorY - (isSubMenu ? 0 : 4) function showAt(item, x, y) { if (!item) { @@ -28,16 +36,27 @@ NPanel { return } + if (!opener.children || opener.children.values.length === 0) { + //Logger.warn("TrayMenu", "Menu not ready, delaying show") + Qt.callLater(() => showAt(item, x, y)) + return + } + anchorItem = item anchorX = x anchorY = y - // Use NPanel's open method instead of PopupWindow's visible - open(screen) + visible = true + forceActiveFocus() + + // Force update after showing. + Qt.callLater(() => { + root.anchor.updateAnchor() + }) } function hideMenu() { - close() + visible = false // Clean up all submenus recursively for (var i = 0; i < columnLayout.children.length; i++) { @@ -50,188 +69,189 @@ NPanel { } } - panelContent: Rectangle { - color: Color.transparent + // Full-sized, transparent MouseArea to track the mouse. + MouseArea { + id: rootMouseArea + anchors.fill: parent + hoverEnabled: true + } + + Item { + anchors.fill: parent + Keys.onEscapePressed: root.hideMenu() + } + + QsMenuOpener { + id: opener + menu: root.menu + } + + Rectangle { + anchors.fill: parent + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + radius: Style.radiusM * scaling + } + + Flickable { + id: flickable anchors.fill: parent anchors.margins: Style.marginS * scaling + contentHeight: columnLayout.implicitHeight + interactive: true + clip: true - // Full-sized, transparent MouseArea to track the mouse. - MouseArea { - id: rootMouseArea - anchors.fill: parent - hoverEnabled: true - onEntered: root.isHovered = true - onExited: root.isHovered = false - } + // Use a ColumnLayout to handle menu item arrangement + ColumnLayout { + id: columnLayout + width: flickable.width + spacing: 0 - QsMenuOpener { - id: opener - menu: root.menu - } + Repeater { + model: opener.children ? [...opener.children.values] : [] - Component.onCompleted: { - if (menu && opener.children && opener.children.values.length === 0) { - // Menu not ready, try again later - Qt.callLater(() => { - if (opener.children && opener.children.values.length > 0) { - // Menu is now ready - root.menuItemCount = opener.children.values.length - } - }) - } else if (opener.children && opener.children.values.length > 0) { - root.menuItemCount = opener.children.values.length - } - } + delegate: Rectangle { + id: entry + required property var modelData - Flickable { - id: flickable - anchors.fill: parent - anchors.margins: Style.marginS * scaling - contentHeight: columnLayout.implicitHeight - interactive: true - clip: true - - // Use a ColumnLayout to handle menu item arrangement - ColumnLayout { - id: columnLayout - width: flickable.width - spacing: 0 - - Repeater { - model: opener.children ? [...opener.children.values] : [] - - delegate: Rectangle { - id: entry - required property var modelData - - Layout.preferredWidth: parent.width - Layout.preferredHeight: { - if (modelData?.isSeparator) { - return 8 * scaling - } else { - // Calculate based on text content - const textHeight = text.contentHeight || (Style.fontSizeS * scaling * 1.2) - return Math.max(28 * scaling, textHeight + (Style.marginS * 2 * scaling)) - } + Layout.preferredWidth: parent.width + Layout.preferredHeight: { + if (modelData?.isSeparator) { + return 8 * scaling + } else { + // Calculate based on text content + const textHeight = text.contentHeight || (Style.fontSizeS * scaling * 1.2) + return Math.max(28 * scaling, textHeight + (Style.marginS * 2 * scaling)) } + } - color: Color.transparent - property var subMenu: null + color: Color.transparent + property var subMenu: null - NDivider { - anchors.centerIn: parent - width: parent.width - (Style.marginM * scaling * 2) - visible: modelData?.isSeparator ?? false - } + NDivider { + anchors.centerIn: parent + width: parent.width - (Style.marginM * scaling * 2) + visible: modelData?.isSeparator ?? false + } - Rectangle { + Rectangle { + anchors.fill: parent + color: mouseArea.containsMouse ? Color.mTertiary : Color.transparent + radius: Style.radiusS * scaling + visible: !(modelData?.isSeparator ?? false) + + RowLayout { anchors.fill: parent - color: mouseArea.containsMouse ? Color.mTertiary : Color.transparent - radius: Style.radiusS * scaling - visible: !(modelData?.isSeparator ?? false) + anchors.leftMargin: Style.marginM * scaling + anchors.rightMargin: Style.marginM * scaling + spacing: Style.marginS * scaling - RowLayout { - anchors.fill: parent - anchors.leftMargin: Style.marginM * scaling - anchors.rightMargin: Style.marginM * scaling - spacing: Style.marginS * scaling - - NText { - id: text - Layout.fillWidth: true - color: (modelData?.enabled - ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant - text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ').replace(/\s+/g, - ' ').trim() : "..." - font.pointSize: Style.fontSizeS * scaling - verticalAlignment: Text.AlignVCenter - wrapMode: Text.NoWrap - elide: Text.ElideRight - } - - Image { - Layout.preferredWidth: Style.marginL * scaling - Layout.preferredHeight: Style.marginL * scaling - source: modelData?.icon ?? "" - visible: (modelData?.icon ?? "") !== "" - fillMode: Image.PreserveAspectFit - } - - NIcon { - text: modelData?.hasChildren ? "menu" : "" - font.pointSize: Style.fontSizeS * scaling - verticalAlignment: Text.AlignVCenter - visible: modelData?.hasChildren ?? false - color: Color.mOnSurface - } + NText { + id: text + Layout.fillWidth: true + color: (modelData?.enabled + ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant + text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..." + font.pointSize: Style.fontSizeS * scaling + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap } - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - enabled: (modelData?.enabled ?? true) && !(modelData?.isSeparator ?? false) && root.visible + Image { + Layout.preferredWidth: Style.marginL * scaling + Layout.preferredHeight: Style.marginL * scaling + source: modelData?.icon ?? "" + visible: (modelData?.icon ?? "") !== "" + fillMode: Image.PreserveAspectFit + } - onClicked: { - if (modelData && !modelData.isSeparator && !modelData.hasChildren) { - modelData.triggered() - root.hideMenu() - } - } - - onEntered: { - if (!root.visible) - return - - // Close all sibling submenus - for (var i = 0; i < columnLayout.children.length; i++) { - const sibling = columnLayout.children[i] - if (sibling !== entry && sibling?.subMenu) { - sibling.subMenu.hideMenu() - sibling.subMenu.destroy() - sibling.subMenu = null - } - } - - // Create submenu if needed - if (modelData?.hasChildren) { - if (entry.subMenu) { - entry.subMenu.hideMenu() - entry.subMenu.destroy() - } - - // Create submenu using the same TrayMenu component - entry.subMenu = Qt.createComponent("TrayMenu.qml").createObject(root, { - "menu": modelData, - "anchorItem": entry, - "anchorX": entry.width, - "anchorY": 0, - "isSubMenu": true - }) - - if (entry.subMenu) { - entry.subMenu.open(screen) - } - } - } - - onExited: { - Qt.callLater(() => { - if (entry.subMenu && !entry.subMenu.isHovered) { - entry.subMenu.hideMenu() - entry.subMenu.destroy() - entry.subMenu = null - } - }) - } + NIcon { + text: modelData?.hasChildren ? "menu" : "" + font.pointSize: Style.fontSizeS * scaling + verticalAlignment: Text.AlignVCenter + visible: modelData?.hasChildren ?? false + color: Color.mOnSurface } } - Component.onDestruction: { - if (subMenu) { - subMenu.destroy() - subMenu = null + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + enabled: (modelData?.enabled ?? true) && !(modelData?.isSeparator ?? false) && root.visible + + onClicked: { + if (modelData && !modelData.isSeparator && !modelData.hasChildren) { + modelData.triggered() + root.hideMenu() + } } + + onEntered: { + if (!root.visible) + return + + // Close all sibling submenus + for (var i = 0; i < columnLayout.children.length; i++) { + const sibling = columnLayout.children[i] + if (sibling !== entry && sibling?.subMenu) { + sibling.subMenu.hideMenu() + sibling.subMenu.destroy() + sibling.subMenu = null + } + } + + // Create submenu if needed + if (modelData?.hasChildren) { + if (entry.subMenu) { + entry.subMenu.hideMenu() + entry.subMenu.destroy() + } + + // Need a slight overlap so that menu don't close when moving the mouse to a submenu + const submenuWidth = menuWidth * scaling // Assuming a similar width as the parent + const overlap = 4 * scaling // A small overlap to bridge the mouse path + + // Check if there's enough space on the right + const globalPos = entry.mapToGlobal(0, 0) + const openLeft = (globalPos.x + entry.width + submenuWidth > (screen ? screen.width : Screen.width)) + + // Position with overlap + const anchorX = openLeft ? -submenuWidth + overlap : entry.width - overlap + + // Create submenu + entry.subMenu = Qt.createComponent("TrayMenu.qml").createObject(root, { + "menu": modelData, + "anchorItem": entry, + "anchorX": anchorX, + "anchorY": 0, + "isSubMenu": true, + "screen": screen + }) + + if (entry.subMenu) { + entry.subMenu.showAt(entry, anchorX, 0) + } + } + } + + onExited: { + Qt.callLater(() => { + if (entry.subMenu && !entry.subMenu.isHovered) { + entry.subMenu.hideMenu() + entry.subMenu.destroy() + entry.subMenu = null + } + }) + } + } + } + + Component.onDestruction: { + if (subMenu) { + subMenu.destroy() + subMenu = null } } } diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 862b044..4160d98 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -14,7 +14,7 @@ Rectangle { id: root property ShellScreen screen - property real scaling: screen ? ScalingService.scale(screen) : 1.0 + property real scaling: ScalingService.scale(screen) readonly property real itemSize: 24 * scaling visible: SystemTray.items.values.length > 0 @@ -80,42 +80,35 @@ Rectangle { if (mouse.button === Qt.LeftButton) { // Close any open menu first - var trayMenuPanel = PanelService.getPanel("trayMenu") - if (trayMenuPanel && trayMenuPanel.active) { - trayMenuPanel.close() - } + trayPanel.close() if (!modelData.onlyMenu) { modelData.activate() } } else if (mouse.button === Qt.MiddleButton) { // Close any open menu first - var trayMenuPanel = PanelService.getPanel("trayMenu") - if (trayMenuPanel && trayMenuPanel.active) { - trayMenuPanel.close() - } + trayPanel.close() modelData.secondaryActivate && modelData.secondaryActivate() } else if (mouse.button === Qt.RightButton) { trayTooltip.hide() - // Don't open menu if screen is invalid - if (!screen || !screen.name) { - Logger.warn("Tray", "Cannot open tray menu: invalid screen object") + // Close the menu if it was visible + if (trayPanel && trayPanel.visible) { + trayPanel.close() return } - if (modelData.hasMenu && modelData.menu) { - // Get the tray menu panel from PanelService - var trayMenuPanel = PanelService.getPanel("trayMenu") - if (trayMenuPanel) { - trayMenuPanel.menu = modelData.menu - trayMenuPanel.toggle(screen, this) - } else { - Logger.warn("Tray", "Tray menu panel not found") - } + if (modelData.hasMenu && modelData.menu && trayMenu.item) { + trayPanel.open() + + // Anchor the menu to the tray icon item (parent) and position it below the icon + const menuX = (width / 2) - (trayMenu.item.width / 2) + const menuY = (Style.barHeight * scaling) + trayMenu.item.menu = modelData.menu + trayMenu.item.showAt(parent, menuX, menuY) } else { - Logger.log("Tray", "No menu available for", modelData.id) + Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set") } } } @@ -132,4 +125,43 @@ Rectangle { } } } + + PanelWindow { + id: trayPanel + anchors.top: true + anchors.left: true + anchors.right: true + anchors.bottom: true + visible: false + color: Color.transparent + screen: screen + + function open() { + visible = true + + PanelService.willOpenPanel(trayPanel) + } + + function close() { + visible = false + trayMenu.item.hideMenu() + } + + // Clicking outside of the rectangle to close + MouseArea { + anchors.fill: parent + onClicked: trayPanel.close() + } + + Loader { + id: trayMenu + source: "../Extras/TrayMenu.qml" + + onLoaded: { + if (item) { + item.screen = screen + } + } + } + } } diff --git a/Services/ArchUpdaterService.qml b/Services/ArchUpdaterService.qml index ccb12fe..8086b7c 100644 --- a/Services/ArchUpdaterService.qml +++ b/Services/ArchUpdaterService.qml @@ -23,6 +23,8 @@ Singleton { property bool updateInProgress: false property bool updateFailed: false property string lastUpdateError: "" + property bool checkFailed: false + property string lastCheckError: "" // Computed properties readonly property bool aurBusy: checkAurUpdatesProcess.running || checkAurOnlyProcess.running @@ -87,6 +89,8 @@ Singleton { Logger.log("ArchUpdater", "No update processes detected, marking update as complete") updateInProgress = false updateMonitorTimer.stop() + errorCheckTimer.stop() + successCheckTimer.stop() // Don't stop the complete timer - let it handle failures // If the update actually failed, the timer will trigger and set updateFailed = true @@ -111,6 +115,8 @@ Singleton { updateFailed = true updateCompleteTimer.stop() updateMonitorTimer.stop() + errorCheckTimer.stop() + successCheckTimer.stop() lastUpdateError = "Build or update error detected" // Refresh to check actual state @@ -140,6 +146,8 @@ Singleton { updateFailed = false updateCompleteTimer.stop() updateMonitorTimer.stop() + errorCheckTimer.stop() + successCheckTimer.stop() lastUpdateError = "" // Refresh to check actual state @@ -152,7 +160,7 @@ Singleton { // Timer to check for success more frequently when update is in progress Timer { - id: errorCheckTimer + id: successCheckTimer interval: 5000 // Check every 5 seconds repeat: true running: updateInProgress @@ -163,6 +171,19 @@ Singleton { } } + // Timer to check for errors more frequently when update is in progress + Timer { + id: errorCheckTimer + interval: 5000 // Check every 5 seconds + repeat: true + running: updateInProgress + onTriggered: { + if (updateInProgress && !errorCheckProcess.running) { + errorCheckProcess.running = true + } + } + } + // ============================================================================ // MONITORING FUNCTIONS // ============================================================================ @@ -187,14 +208,23 @@ Singleton { // Initial check Component.onCompleted: { - // Initial poll without cooldown restriction - const aurHelper = getAurHelper() - if (aurHelper) { - checkAurUpdatesProcess.command = [aurHelper, "-Qu"] - checkAurOnlyProcess.command = [aurHelper, "-Qua"] - checkAurUpdatesProcess.running = true - lastPollTime = Date.now() - } + // Start AUR helper detection + getAurHelper() + + // Use a timer to check for AUR helper after detection completes + Qt.callLater(() => { + if (cachedAurHelper !== "") { + checkAurUpdatesProcess.command = [cachedAurHelper, "-Qu"] + checkAurOnlyProcess.command = [cachedAurHelper, getAurOnlyFlag()] + checkAurUpdatesProcess.running = true + lastPollTime = Date.now() + } else { + // No AUR helper found + checkFailed = true + lastCheckError = "No AUR helper found (yay or paru not installed)" + Logger.warn("ArchUpdater", "No AUR helper found (yay or paru)") + } + }, 1000) } // ============================================================================ @@ -208,9 +238,12 @@ Singleton { onExited: function (exitCode) { if (exitCode !== 0) { Logger.warn("ArchUpdater", "AUR helper check failed (code:", exitCode, ")") + checkFailed = true + lastCheckError = "Failed to check for updates (exit code: " + exitCode + ")" aurPackages = [] repoPackages = [] } + // Don't clear checkFailed here - wait for the second process to complete } stdout: StdioCollector { onStreamFinished: { @@ -226,10 +259,21 @@ Singleton { id: checkAurOnlyProcess command: [] onExited: function (exitCode) { - if (exitCode !== 0) { + // For paru -Qua, exit code 1 means "no AUR updates available", which is valid + // For yay -Qua, exit code 0 means success + if (exitCode !== 0 && exitCode !== 1) { Logger.warn("ArchUpdater", "AUR helper AUR-only check failed (code:", exitCode, ")") + checkFailed = true + lastCheckError = "Failed to check AUR updates (exit code: " + exitCode + ")" aurPackages = [] repoPackages = [] + } else { + // Only clear checkFailed if both processes succeeded + // Check if the first process also succeeded (no error was set) + if (!checkFailed) { + checkFailed = false + lastCheckError = "" + } } } stdout: StdioCollector { @@ -324,17 +368,21 @@ Singleton { return } - // Get the AUR helper and set commands - const aurHelper = getAurHelper() - if (aurHelper) { - checkAurUpdatesProcess.command = [aurHelper, "-Qu"] - checkAurOnlyProcess.command = [aurHelper, "-Qua"] + // Check if we have a cached AUR helper + if (cachedAurHelper !== "") { + checkAurUpdatesProcess.command = [cachedAurHelper, "-Qu"] + checkAurOnlyProcess.command = [cachedAurHelper, getAurOnlyFlag()] // Start AUR updates check (includes both repo and AUR packages) checkAurUpdatesProcess.running = true lastPollTime = Date.now() } else { - Logger.warn("ArchUpdater", "No AUR helper found (yay or paru)") + // AUR helper detection is still in progress or failed + // Try to detect again if not already in progress + if (!yayCheckProcess.running && !paruCheckProcess.running) { + getAurHelper() + } + Logger.warn("ArchUpdater", "AUR helper detection in progress or failed") } } @@ -363,11 +411,16 @@ Singleton { const terminal = Quickshell.env("TERMINAL") || "xterm" // Check if we have an AUR helper for full system update - const aurHelper = getAurHelper() - if (aurHelper && (aurUpdates > 0 || updates > 0)) { + if (cachedAurHelper !== "" && (aurUpdates > 0 || updates > 0)) { // Use AUR helper for full system update (handles both repo and AUR) - const command = generateUpdateCommand(aurHelper + " -Syu") + const command = generateUpdateCommand(cachedAurHelper + " -Syu") Quickshell.execDetached([terminal, "-e", "bash", "-c", command]) + } else if (cachedAurHelper === "") { + // No AUR helper found + updateInProgress = false + updateFailed = true + lastUpdateError = "No AUR helper found (yay or paru not installed)" + Logger.warn("ArchUpdater", "No AUR helper found for update") } // Start monitoring and timeout timers @@ -407,12 +460,14 @@ Singleton { // Update all packages with AUR helper (handles both repo and AUR) if (selectedPackages.length > 0) { - const aurHelper = getAurHelper() - if (aurHelper) { + if (cachedAurHelper !== "") { const packageList = selectedPackages.join(" ") - const command = generateUpdateCommand(aurHelper + " -S " + packageList) + const command = generateUpdateCommand(cachedAurHelper + " -S " + packageList) Quickshell.execDetached([terminal, "-e", "bash", "-c", command]) } else { + updateInProgress = false + updateFailed = true + lastUpdateError = "No AUR helper found (yay or paru not installed)" Logger.warn("ArchUpdater", "No AUR helper found for packages:", selectedPackages.join(", ")) } } @@ -432,9 +487,13 @@ Singleton { updateInProgress = false lastUpdateError = "" + checkFailed = false + lastCheckError = "" updateCompleteTimer.stop() updateMonitorTimer.stop() refreshTimer.stop() + errorCheckTimer.stop() + successCheckTimer.stop() // Refresh to get current state doPoll() @@ -450,18 +509,24 @@ Singleton { // Clear error states when refreshing updateFailed = false lastUpdateError = "" + checkFailed = false + lastCheckError = "" - // Get the AUR helper and set commands - const aurHelper = getAurHelper() - if (aurHelper) { - checkAurUpdatesProcess.command = [aurHelper, "-Qu"] - checkAurOnlyProcess.command = [aurHelper, "-Qua"] + // Check if we have a cached AUR helper + if (cachedAurHelper !== "") { + checkAurUpdatesProcess.command = [cachedAurHelper, "-Qu"] + checkAurOnlyProcess.command = [cachedAurHelper, getAurOnlyFlag()] // Force refresh by bypassing cooldown checkAurUpdatesProcess.running = true lastPollTime = Date.now() } else { - Logger.warn("ArchUpdater", "No AUR helper found (yay or paru)") + // AUR helper detection is still in progress or failed + // Try to detect again if not already in progress + if (!yayCheckProcess.running && !paruCheckProcess.running) { + getAurHelper() + } + Logger.warn("ArchUpdater", "AUR helper detection in progress or failed") } } @@ -512,9 +577,19 @@ Singleton { yayCheckProcess.running = true paruCheckProcess.running = true - // For now, return a default (will be updated by the processes) - // In a real implementation, you'd want to wait for the processes to complete - return "paru" // Default fallback + // Return empty string to indicate no helper found yet + // The processes will update cachedAurHelper when they complete + return "" + } + + // Helper function to get the correct AUR-only flag for the detected helper + function getAurOnlyFlag() { + if (cachedAurHelper === "yay") { + return "-Qua" + } else if (cachedAurHelper === "paru") { + return "-Qua" // paru uses the same flag but different exit code behavior + } + return "-Qua" // fallback } // ============================================================================ diff --git a/shell.qml b/shell.qml index 884e390..1e0f9ba 100644 --- a/shell.qml +++ b/shell.qml @@ -100,11 +100,6 @@ ShellRoot { objectName: "archUpdaterPanel" } - TrayMenu { - id: trayMenuPanel - objectName: "trayMenu" - } - Component.onCompleted: { // Save a ref. to our lockScreen so we can access it easily PanelService.lockScreen = lockScreen