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)
|
// Update summary (only show when packages are available)
|
||||||
NText {
|
NText {
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.aurBusy
|
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||||
|
&& !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy
|
||||||
&& ArchUpdaterService.totalUpdates > 0
|
&& ArchUpdaterService.totalUpdates > 0
|
||||||
text: ArchUpdaterService.totalUpdates + " package" + (ArchUpdaterService.totalUpdates !== 1 ? "s" : "") + " can be updated"
|
text: ArchUpdaterService.totalUpdates + " package" + (ArchUpdaterService.totalUpdates !== 1 ? "s" : "") + " can be updated"
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
font.pointSize: Style.fontSizeL * scaling
|
||||||
|
|
@ -79,7 +80,8 @@ NPanel {
|
||||||
|
|
||||||
// Package selection info (only show when not updating and have packages)
|
// Package selection info (only show when not updating and have packages)
|
||||||
NText {
|
NText {
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.aurBusy
|
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||||
|
&& !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy
|
||||||
&& ArchUpdaterService.totalUpdates > 0
|
&& ArchUpdaterService.totalUpdates > 0
|
||||||
text: ArchUpdaterService.selectedPackagesCount + " of " + ArchUpdaterService.totalUpdates + " packages selected"
|
text: ArchUpdaterService.selectedPackagesCount + " of " + ArchUpdaterService.totalUpdates + " packages selected"
|
||||||
font.pointSize: Style.fontSizeS * scaling
|
font.pointSize: Style.fontSizeS * scaling
|
||||||
|
|
@ -127,6 +129,58 @@ NPanel {
|
||||||
} // Spacer
|
} // 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
|
// Update failed state
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -181,7 +235,8 @@ NPanel {
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.aurBusy
|
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||||
|
&& !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy
|
||||||
&& ArchUpdaterService.totalUpdates === 0
|
&& ArchUpdaterService.totalUpdates === 0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|
@ -251,7 +306,8 @@ NPanel {
|
||||||
|
|
||||||
// Package list (only show when not in any special state)
|
// Package list (only show when not in any special state)
|
||||||
NBox {
|
NBox {
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed && !ArchUpdaterService.aurBusy
|
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||||
|
&& !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy
|
||||||
&& ArchUpdaterService.totalUpdates > 0
|
&& ArchUpdaterService.totalUpdates > 0
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
@ -340,6 +396,7 @@ NPanel {
|
||||||
// Action buttons (only show when not updating)
|
// Action buttons (only show when not updating)
|
||||||
RowLayout {
|
RowLayout {
|
||||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||||
|
&& !ArchUpdaterService.checkFailed
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Style.marginL * scaling
|
spacing: Style.marginL * scaling
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,29 @@ import qs.Commons
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
NPanel {
|
PopupWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
objectName: "trayMenu"
|
|
||||||
|
|
||||||
panelWidth: 180 * scaling
|
|
||||||
panelHeight: 220 * scaling
|
|
||||||
panelAnchorRight: true
|
|
||||||
|
|
||||||
property QsMenuHandle menu
|
property QsMenuHandle menu
|
||||||
property var anchorItem: null
|
property var anchorItem: null
|
||||||
property real anchorX
|
property real anchorX
|
||||||
property real anchorY
|
property real anchorY
|
||||||
property bool isSubMenu: false
|
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) {
|
function showAt(item, x, y) {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
|
|
@ -28,16 +36,27 @@ NPanel {
|
||||||
return
|
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
|
anchorItem = item
|
||||||
anchorX = x
|
anchorX = x
|
||||||
anchorY = y
|
anchorY = y
|
||||||
|
|
||||||
// Use NPanel's open method instead of PopupWindow's visible
|
visible = true
|
||||||
open(screen)
|
forceActiveFocus()
|
||||||
|
|
||||||
|
// Force update after showing.
|
||||||
|
Qt.callLater(() => {
|
||||||
|
root.anchor.updateAnchor()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideMenu() {
|
function hideMenu() {
|
||||||
close()
|
visible = false
|
||||||
|
|
||||||
// Clean up all submenus recursively
|
// Clean up all submenus recursively
|
||||||
for (var i = 0; i < columnLayout.children.length; i++) {
|
for (var i = 0; i < columnLayout.children.length; i++) {
|
||||||
|
|
@ -50,188 +69,189 @@ NPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
panelContent: Rectangle {
|
// Full-sized, transparent MouseArea to track the mouse.
|
||||||
color: Color.transparent
|
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.fill: parent
|
||||||
anchors.margins: Style.marginS * scaling
|
anchors.margins: Style.marginS * scaling
|
||||||
|
contentHeight: columnLayout.implicitHeight
|
||||||
|
interactive: true
|
||||||
|
clip: true
|
||||||
|
|
||||||
// Full-sized, transparent MouseArea to track the mouse.
|
// Use a ColumnLayout to handle menu item arrangement
|
||||||
MouseArea {
|
ColumnLayout {
|
||||||
id: rootMouseArea
|
id: columnLayout
|
||||||
anchors.fill: parent
|
width: flickable.width
|
||||||
hoverEnabled: true
|
spacing: 0
|
||||||
onEntered: root.isHovered = true
|
|
||||||
onExited: root.isHovered = false
|
|
||||||
}
|
|
||||||
|
|
||||||
QsMenuOpener {
|
Repeater {
|
||||||
id: opener
|
model: opener.children ? [...opener.children.values] : []
|
||||||
menu: root.menu
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
delegate: Rectangle {
|
||||||
if (menu && opener.children && opener.children.values.length === 0) {
|
id: entry
|
||||||
// Menu not ready, try again later
|
required property var modelData
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Flickable {
|
Layout.preferredWidth: parent.width
|
||||||
id: flickable
|
Layout.preferredHeight: {
|
||||||
anchors.fill: parent
|
if (modelData?.isSeparator) {
|
||||||
anchors.margins: Style.marginS * scaling
|
return 8 * scaling
|
||||||
contentHeight: columnLayout.implicitHeight
|
} else {
|
||||||
interactive: true
|
// Calculate based on text content
|
||||||
clip: true
|
const textHeight = text.contentHeight || (Style.fontSizeS * scaling * 1.2)
|
||||||
|
return Math.max(28 * scaling, textHeight + (Style.marginS * 2 * scaling))
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
property var subMenu: null
|
property var subMenu: null
|
||||||
|
|
||||||
NDivider {
|
NDivider {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width - (Style.marginM * scaling * 2)
|
width: parent.width - (Style.marginM * scaling * 2)
|
||||||
visible: modelData?.isSeparator ?? false
|
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
|
anchors.fill: parent
|
||||||
color: mouseArea.containsMouse ? Color.mTertiary : Color.transparent
|
anchors.leftMargin: Style.marginM * scaling
|
||||||
radius: Style.radiusS * scaling
|
anchors.rightMargin: Style.marginM * scaling
|
||||||
visible: !(modelData?.isSeparator ?? false)
|
spacing: Style.marginS * scaling
|
||||||
|
|
||||||
RowLayout {
|
NText {
|
||||||
anchors.fill: parent
|
id: text
|
||||||
anchors.leftMargin: Style.marginM * scaling
|
Layout.fillWidth: true
|
||||||
anchors.rightMargin: Style.marginM * scaling
|
color: (modelData?.enabled
|
||||||
spacing: Style.marginS * scaling
|
?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant
|
||||||
|
text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..."
|
||||||
NText {
|
font.pointSize: Style.fontSizeS * scaling
|
||||||
id: text
|
verticalAlignment: Text.AlignVCenter
|
||||||
Layout.fillWidth: true
|
wrapMode: Text.WordWrap
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Image {
|
||||||
id: mouseArea
|
Layout.preferredWidth: Style.marginL * scaling
|
||||||
anchors.fill: parent
|
Layout.preferredHeight: Style.marginL * scaling
|
||||||
hoverEnabled: true
|
source: modelData?.icon ?? ""
|
||||||
enabled: (modelData?.enabled ?? true) && !(modelData?.isSeparator ?? false) && root.visible
|
visible: (modelData?.icon ?? "") !== ""
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: {
|
NIcon {
|
||||||
if (modelData && !modelData.isSeparator && !modelData.hasChildren) {
|
text: modelData?.hasChildren ? "menu" : ""
|
||||||
modelData.triggered()
|
font.pointSize: Style.fontSizeS * scaling
|
||||||
root.hideMenu()
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
visible: modelData?.hasChildren ?? false
|
||||||
}
|
color: Color.mOnSurface
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
MouseArea {
|
||||||
if (subMenu) {
|
id: mouseArea
|
||||||
subMenu.destroy()
|
anchors.fill: parent
|
||||||
subMenu = null
|
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
|
id: root
|
||||||
|
|
||||||
property ShellScreen screen
|
property ShellScreen screen
|
||||||
property real scaling: screen ? ScalingService.scale(screen) : 1.0
|
property real scaling: ScalingService.scale(screen)
|
||||||
readonly property real itemSize: 24 * scaling
|
readonly property real itemSize: 24 * scaling
|
||||||
|
|
||||||
visible: SystemTray.items.values.length > 0
|
visible: SystemTray.items.values.length > 0
|
||||||
|
|
@ -80,42 +80,35 @@ Rectangle {
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
if (mouse.button === Qt.LeftButton) {
|
||||||
// Close any open menu first
|
// Close any open menu first
|
||||||
var trayMenuPanel = PanelService.getPanel("trayMenu")
|
trayPanel.close()
|
||||||
if (trayMenuPanel && trayMenuPanel.active) {
|
|
||||||
trayMenuPanel.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modelData.onlyMenu) {
|
if (!modelData.onlyMenu) {
|
||||||
modelData.activate()
|
modelData.activate()
|
||||||
}
|
}
|
||||||
} else if (mouse.button === Qt.MiddleButton) {
|
} else if (mouse.button === Qt.MiddleButton) {
|
||||||
// Close any open menu first
|
// Close any open menu first
|
||||||
var trayMenuPanel = PanelService.getPanel("trayMenu")
|
trayPanel.close()
|
||||||
if (trayMenuPanel && trayMenuPanel.active) {
|
|
||||||
trayMenuPanel.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
modelData.secondaryActivate && modelData.secondaryActivate()
|
modelData.secondaryActivate && modelData.secondaryActivate()
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
trayTooltip.hide()
|
trayTooltip.hide()
|
||||||
|
|
||||||
// Don't open menu if screen is invalid
|
// Close the menu if it was visible
|
||||||
if (!screen || !screen.name) {
|
if (trayPanel && trayPanel.visible) {
|
||||||
Logger.warn("Tray", "Cannot open tray menu: invalid screen object")
|
trayPanel.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelData.hasMenu && modelData.menu) {
|
if (modelData.hasMenu && modelData.menu && trayMenu.item) {
|
||||||
// Get the tray menu panel from PanelService
|
trayPanel.open()
|
||||||
var trayMenuPanel = PanelService.getPanel("trayMenu")
|
|
||||||
if (trayMenuPanel) {
|
// Anchor the menu to the tray icon item (parent) and position it below the icon
|
||||||
trayMenuPanel.menu = modelData.menu
|
const menuX = (width / 2) - (trayMenu.item.width / 2)
|
||||||
trayMenuPanel.toggle(screen, this)
|
const menuY = (Style.barHeight * scaling)
|
||||||
} else {
|
trayMenu.item.menu = modelData.menu
|
||||||
Logger.warn("Tray", "Tray menu panel not found")
|
trayMenu.item.showAt(parent, menuX, menuY)
|
||||||
}
|
|
||||||
} else {
|
} 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 updateInProgress: false
|
||||||
property bool updateFailed: false
|
property bool updateFailed: false
|
||||||
property string lastUpdateError: ""
|
property string lastUpdateError: ""
|
||||||
|
property bool checkFailed: false
|
||||||
|
property string lastCheckError: ""
|
||||||
|
|
||||||
// Computed properties
|
// Computed properties
|
||||||
readonly property bool aurBusy: checkAurUpdatesProcess.running || checkAurOnlyProcess.running
|
readonly property bool aurBusy: checkAurUpdatesProcess.running || checkAurOnlyProcess.running
|
||||||
|
|
@ -87,6 +89,8 @@ Singleton {
|
||||||
Logger.log("ArchUpdater", "No update processes detected, marking update as complete")
|
Logger.log("ArchUpdater", "No update processes detected, marking update as complete")
|
||||||
updateInProgress = false
|
updateInProgress = false
|
||||||
updateMonitorTimer.stop()
|
updateMonitorTimer.stop()
|
||||||
|
errorCheckTimer.stop()
|
||||||
|
successCheckTimer.stop()
|
||||||
|
|
||||||
// Don't stop the complete timer - let it handle failures
|
// Don't stop the complete timer - let it handle failures
|
||||||
// If the update actually failed, the timer will trigger and set updateFailed = true
|
// If the update actually failed, the timer will trigger and set updateFailed = true
|
||||||
|
|
@ -111,6 +115,8 @@ Singleton {
|
||||||
updateFailed = true
|
updateFailed = true
|
||||||
updateCompleteTimer.stop()
|
updateCompleteTimer.stop()
|
||||||
updateMonitorTimer.stop()
|
updateMonitorTimer.stop()
|
||||||
|
errorCheckTimer.stop()
|
||||||
|
successCheckTimer.stop()
|
||||||
lastUpdateError = "Build or update error detected"
|
lastUpdateError = "Build or update error detected"
|
||||||
|
|
||||||
// Refresh to check actual state
|
// Refresh to check actual state
|
||||||
|
|
@ -140,6 +146,8 @@ Singleton {
|
||||||
updateFailed = false
|
updateFailed = false
|
||||||
updateCompleteTimer.stop()
|
updateCompleteTimer.stop()
|
||||||
updateMonitorTimer.stop()
|
updateMonitorTimer.stop()
|
||||||
|
errorCheckTimer.stop()
|
||||||
|
successCheckTimer.stop()
|
||||||
lastUpdateError = ""
|
lastUpdateError = ""
|
||||||
|
|
||||||
// Refresh to check actual state
|
// Refresh to check actual state
|
||||||
|
|
@ -152,7 +160,7 @@ Singleton {
|
||||||
|
|
||||||
// Timer to check for success more frequently when update is in progress
|
// Timer to check for success more frequently when update is in progress
|
||||||
Timer {
|
Timer {
|
||||||
id: errorCheckTimer
|
id: successCheckTimer
|
||||||
interval: 5000 // Check every 5 seconds
|
interval: 5000 // Check every 5 seconds
|
||||||
repeat: true
|
repeat: true
|
||||||
running: updateInProgress
|
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
|
// MONITORING FUNCTIONS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -187,14 +208,23 @@ Singleton {
|
||||||
|
|
||||||
// Initial check
|
// Initial check
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Initial poll without cooldown restriction
|
// Start AUR helper detection
|
||||||
const aurHelper = getAurHelper()
|
getAurHelper()
|
||||||
if (aurHelper) {
|
|
||||||
checkAurUpdatesProcess.command = [aurHelper, "-Qu"]
|
// Use a timer to check for AUR helper after detection completes
|
||||||
checkAurOnlyProcess.command = [aurHelper, "-Qua"]
|
Qt.callLater(() => {
|
||||||
checkAurUpdatesProcess.running = true
|
if (cachedAurHelper !== "") {
|
||||||
lastPollTime = Date.now()
|
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) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
Logger.warn("ArchUpdater", "AUR helper check failed (code:", exitCode, ")")
|
Logger.warn("ArchUpdater", "AUR helper check failed (code:", exitCode, ")")
|
||||||
|
checkFailed = true
|
||||||
|
lastCheckError = "Failed to check for updates (exit code: " + exitCode + ")"
|
||||||
aurPackages = []
|
aurPackages = []
|
||||||
repoPackages = []
|
repoPackages = []
|
||||||
}
|
}
|
||||||
|
// Don't clear checkFailed here - wait for the second process to complete
|
||||||
}
|
}
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
|
|
@ -226,10 +259,21 @@ Singleton {
|
||||||
id: checkAurOnlyProcess
|
id: checkAurOnlyProcess
|
||||||
command: []
|
command: []
|
||||||
onExited: function (exitCode) {
|
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, ")")
|
Logger.warn("ArchUpdater", "AUR helper AUR-only check failed (code:", exitCode, ")")
|
||||||
|
checkFailed = true
|
||||||
|
lastCheckError = "Failed to check AUR updates (exit code: " + exitCode + ")"
|
||||||
aurPackages = []
|
aurPackages = []
|
||||||
repoPackages = []
|
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 {
|
stdout: StdioCollector {
|
||||||
|
|
@ -324,17 +368,21 @@ Singleton {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the AUR helper and set commands
|
// Check if we have a cached AUR helper
|
||||||
const aurHelper = getAurHelper()
|
if (cachedAurHelper !== "") {
|
||||||
if (aurHelper) {
|
checkAurUpdatesProcess.command = [cachedAurHelper, "-Qu"]
|
||||||
checkAurUpdatesProcess.command = [aurHelper, "-Qu"]
|
checkAurOnlyProcess.command = [cachedAurHelper, getAurOnlyFlag()]
|
||||||
checkAurOnlyProcess.command = [aurHelper, "-Qua"]
|
|
||||||
|
|
||||||
// Start AUR updates check (includes both repo and AUR packages)
|
// Start AUR updates check (includes both repo and AUR packages)
|
||||||
checkAurUpdatesProcess.running = true
|
checkAurUpdatesProcess.running = true
|
||||||
lastPollTime = Date.now()
|
lastPollTime = Date.now()
|
||||||
} else {
|
} 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"
|
const terminal = Quickshell.env("TERMINAL") || "xterm"
|
||||||
|
|
||||||
// Check if we have an AUR helper for full system update
|
// Check if we have an AUR helper for full system update
|
||||||
const aurHelper = getAurHelper()
|
if (cachedAurHelper !== "" && (aurUpdates > 0 || updates > 0)) {
|
||||||
if (aurHelper && (aurUpdates > 0 || updates > 0)) {
|
|
||||||
// Use AUR helper for full system update (handles both repo and AUR)
|
// 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])
|
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
|
// Start monitoring and timeout timers
|
||||||
|
|
@ -407,12 +460,14 @@ Singleton {
|
||||||
|
|
||||||
// Update all packages with AUR helper (handles both repo and AUR)
|
// Update all packages with AUR helper (handles both repo and AUR)
|
||||||
if (selectedPackages.length > 0) {
|
if (selectedPackages.length > 0) {
|
||||||
const aurHelper = getAurHelper()
|
if (cachedAurHelper !== "") {
|
||||||
if (aurHelper) {
|
|
||||||
const packageList = selectedPackages.join(" ")
|
const packageList = selectedPackages.join(" ")
|
||||||
const command = generateUpdateCommand(aurHelper + " -S " + packageList)
|
const command = generateUpdateCommand(cachedAurHelper + " -S " + packageList)
|
||||||
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
|
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
|
||||||
} else {
|
} 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(", "))
|
Logger.warn("ArchUpdater", "No AUR helper found for packages:", selectedPackages.join(", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -432,9 +487,13 @@ Singleton {
|
||||||
|
|
||||||
updateInProgress = false
|
updateInProgress = false
|
||||||
lastUpdateError = ""
|
lastUpdateError = ""
|
||||||
|
checkFailed = false
|
||||||
|
lastCheckError = ""
|
||||||
updateCompleteTimer.stop()
|
updateCompleteTimer.stop()
|
||||||
updateMonitorTimer.stop()
|
updateMonitorTimer.stop()
|
||||||
refreshTimer.stop()
|
refreshTimer.stop()
|
||||||
|
errorCheckTimer.stop()
|
||||||
|
successCheckTimer.stop()
|
||||||
|
|
||||||
// Refresh to get current state
|
// Refresh to get current state
|
||||||
doPoll()
|
doPoll()
|
||||||
|
|
@ -450,18 +509,24 @@ Singleton {
|
||||||
// Clear error states when refreshing
|
// Clear error states when refreshing
|
||||||
updateFailed = false
|
updateFailed = false
|
||||||
lastUpdateError = ""
|
lastUpdateError = ""
|
||||||
|
checkFailed = false
|
||||||
|
lastCheckError = ""
|
||||||
|
|
||||||
// Get the AUR helper and set commands
|
// Check if we have a cached AUR helper
|
||||||
const aurHelper = getAurHelper()
|
if (cachedAurHelper !== "") {
|
||||||
if (aurHelper) {
|
checkAurUpdatesProcess.command = [cachedAurHelper, "-Qu"]
|
||||||
checkAurUpdatesProcess.command = [aurHelper, "-Qu"]
|
checkAurOnlyProcess.command = [cachedAurHelper, getAurOnlyFlag()]
|
||||||
checkAurOnlyProcess.command = [aurHelper, "-Qua"]
|
|
||||||
|
|
||||||
// Force refresh by bypassing cooldown
|
// Force refresh by bypassing cooldown
|
||||||
checkAurUpdatesProcess.running = true
|
checkAurUpdatesProcess.running = true
|
||||||
lastPollTime = Date.now()
|
lastPollTime = Date.now()
|
||||||
} else {
|
} 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
|
yayCheckProcess.running = true
|
||||||
paruCheckProcess.running = true
|
paruCheckProcess.running = true
|
||||||
|
|
||||||
// For now, return a default (will be updated by the processes)
|
// Return empty string to indicate no helper found yet
|
||||||
// In a real implementation, you'd want to wait for the processes to complete
|
// The processes will update cachedAurHelper when they complete
|
||||||
return "paru" // Default fallback
|
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"
|
objectName: "archUpdaterPanel"
|
||||||
}
|
}
|
||||||
|
|
||||||
TrayMenu {
|
|
||||||
id: trayMenuPanel
|
|
||||||
objectName: "trayMenu"
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Save a ref. to our lockScreen so we can access it easily
|
// Save a ref. to our lockScreen so we can access it easily
|
||||||
PanelService.lockScreen = lockScreen
|
PanelService.lockScreen = lockScreen
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue