Fix ArchUpdater error codes, revert TrayMenu
TrayMenu: reverted it to the old PopupPanel for ignored ArchUpdater: paru error code 1 = no updates available
This commit is contained in:
parent
91ffa4a9fd
commit
1eae0eb3d4
5 changed files with 416 additions and 237 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue