Fix TrayMenu crash after display wake. Add checks if screen exists, else set scaling to 1.0

TrayMenu: Replace PopupPanel with NPanel (for better loading & to
prevent QS crash)
Overview, Background etc: add screen checks, if it doesnt exist set
scaling to 1.0
This commit is contained in:
Ly-sec 2025-08-31 08:55:20 +02:00
parent 714f6c058f
commit 51f1923e22
9 changed files with 119 additions and 137 deletions

View file

@ -12,7 +12,7 @@ Variants {
required property ShellScreen modelData
active: Settings.isLoaded
active: Settings.isLoaded && modelData
sourceComponent: PanelWindow {
id: root
@ -38,7 +38,7 @@ Variants {
property real stripesAngle: 0
// External state management
property string servicedWallpaper: WallpaperService.getWallpaper(modelData.name)
property string servicedWallpaper: modelData ? WallpaperService.getWallpaper(modelData.name) : ""
property string futureWallpaper: ""
onServicedWallpaperChanged: {
// Set wallpaper immediately on startup

View file

@ -12,11 +12,13 @@ Variants {
delegate: Loader {
required property ShellScreen modelData
active: Settings.isLoaded && CompositorService.isNiri
active: Settings.isLoaded && CompositorService.isNiri && modelData
sourceComponent: PanelWindow {
Component.onCompleted: {
Logger.log("Overview", "Loading Overview component for Niri on", modelData.name)
if (modelData) {
Logger.log("Overview", "Loading Overview component for Niri on", modelData.name)
}
}
color: Color.transparent
@ -36,7 +38,7 @@ Variants {
id: bgImage
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
source: WallpaperService.getWallpaper(modelData.name)
source: modelData ? WallpaperService.getWallpaper(modelData.name) : ""
smooth: true
mipmap: false
cache: false

View file

@ -16,13 +16,13 @@ Variants {
id: root
required property ShellScreen modelData
readonly property real scaling: ScalingService.scale(modelData)
readonly property real scaling: modelData ? ScalingService.scale(modelData) : 1.0
active: Settings.isLoaded && modelData ? (Settings.data.bar.monitors.includes(modelData.name)
active: Settings.isLoaded && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name)
|| (Settings.data.bar.monitors.length === 0)) : false
sourceComponent: PanelWindow {
screen: modelData
screen: modelData || null
WlrLayershell.namespace: "noctalia-bar"
@ -65,7 +65,7 @@ Variants {
delegate: NWidgetLoader {
widgetName: modelData
widgetProps: {
"screen": screen
"screen": root.modelData || null
}
anchors.verticalCenter: parent.verticalCenter
}
@ -87,7 +87,7 @@ Variants {
delegate: NWidgetLoader {
widgetName: modelData
widgetProps: {
"screen": screen
"screen": root.modelData || null
}
anchors.verticalCenter: parent.verticalCenter
}
@ -110,7 +110,7 @@ Variants {
delegate: NWidgetLoader {
widgetName: modelData
widgetProps: {
"screen": screen
"screen": root.modelData || null
}
anchors.verticalCenter: parent.verticalCenter
}

View file

@ -6,26 +6,23 @@ import qs.Commons
import qs.Services
import qs.Widgets
PopupWindow {
NPanel {
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: rootMouseArea.containsMouse
property bool isHovered: false
readonly property int menuWidth: 180
implicitWidth: menuWidth * scaling
// Use the content height of the Flickable for implicit height
implicitHeight: Math.min(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) {
@ -33,27 +30,18 @@ PopupWindow {
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
visible = true
forceActiveFocus()
// Use NPanel's open method instead of PopupWindow's visible
open(screen)
// Force update after showing.
Qt.callLater(() => {
root.anchor.updateAnchor()
})
}
function hideMenu() {
visible = false
close()
// Clean up all submenus recursively
for (var i = 0; i < columnLayout.children.length; i++) {
@ -66,38 +54,46 @@ PopupWindow {
}
}
// 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
panelContent: Rectangle {
color: Color.transparent
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
}
QsMenuOpener {
id: opener
menu: root.menu
}
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
}
}
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 {
@ -206,28 +202,17 @@ PopupWindow {
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.width)
// Position with overlap
const anchorX = openLeft ? -submenuWidth + overlap : entry.width - overlap
// Create submenu
// Create submenu using the same TrayMenu component
entry.subMenu = Qt.createComponent("TrayMenu.qml").createObject(root, {
"menu": modelData,
"anchorItem": entry,
"anchorX": anchorX,
"anchorY": 0,
"isSubMenu": true
})
"menu": modelData,
"anchorItem": entry,
"anchorX": entry.width,
"anchorY": 0,
"isSubMenu": true
})
if (entry.subMenu) {
entry.subMenu.showAt(entry, anchorX, 0)
entry.subMenu.open(screen)
}
}
}
@ -254,4 +239,5 @@ PopupWindow {
}
}
}
}
}

View file

@ -14,7 +14,7 @@ Rectangle {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: screen ? ScalingService.scale(screen) : 1.0
readonly property real itemSize: 24 * scaling
visible: SystemTray.items.values.length > 0
@ -80,35 +80,42 @@ Rectangle {
if (mouse.button === Qt.LeftButton) {
// Close any open menu first
trayPanel.close()
var trayMenuPanel = PanelService.getPanel("trayMenu")
if (trayMenuPanel && trayMenuPanel.active) {
trayMenuPanel.close()
}
if (!modelData.onlyMenu) {
modelData.activate()
}
} else if (mouse.button === Qt.MiddleButton) {
// Close any open menu first
trayPanel.close()
var trayMenuPanel = PanelService.getPanel("trayMenu")
if (trayMenuPanel && trayMenuPanel.active) {
trayMenuPanel.close()
}
modelData.secondaryActivate && modelData.secondaryActivate()
} else if (mouse.button === Qt.RightButton) {
trayTooltip.hide()
// Close the menu if it was visible
if (trayPanel && trayPanel.visible) {
trayPanel.close()
// Don't open menu if screen is invalid
if (!screen || !screen.name) {
Logger.warn("Tray", "Cannot open tray menu: invalid screen object")
return
}
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)
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")
}
} else {
Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set")
Logger.log("Tray", "No menu available for", modelData.id)
}
}
}
@ -126,36 +133,5 @@ 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"
}
}
}

View file

@ -93,7 +93,7 @@ Loader {
id: lockBgImage
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
source: WallpaperService.getWallpaper(screen.name)
source: screen ? WallpaperService.getWallpaper(screen.name) : ""
cache: true
smooth: true
mipmap: false

View file

@ -29,7 +29,7 @@ ColumnLayout {
NImageRounded {
anchors.fill: parent
anchors.margins: Style.marginXS * scaling
imagePath: WallpaperService.getWallpaper(screen.name)
imagePath: screen ? WallpaperService.getWallpaper(screen.name) : ""
fallbackIcon: "image"
imageRadius: Style.radiusM * scaling
}
@ -74,7 +74,7 @@ ColumnLayout {
}
}
property list<string> wallpapersList: WallpaperService.getWallpapersList(screen.name)
property list<string> wallpapersList: screen ? WallpaperService.getWallpapersList(screen.name) : []
NToggle {
label: "Assign selection to all monitors"
@ -115,7 +115,7 @@ ColumnLayout {
id: wallpaperItem
property string wallpaperPath: modelData
property bool isSelected: wallpaperPath === WallpaperService.getWallpaper(screen.name)
property bool isSelected: screen ? (wallpaperPath === WallpaperService.getWallpaper(screen.name)) : false
width: wallpaperGridView.itemSize
height: Math.floor(wallpaperGridView.itemSize * 0.67)
@ -183,7 +183,7 @@ ColumnLayout {
onPressed: {
if (Settings.data.wallpaper.setWallpaperOnAllMonitors) {
WallpaperService.changeWallpaper(undefined, wallpaperPath)
} else {
} else if (screen) {
WallpaperService.changeWallpaper(screen.name, wallpaperPath)
}
}

View file

@ -11,7 +11,7 @@ Loader {
asynchronous: true
property ShellScreen screen
readonly property real scaling: ScalingService.scale(screen)
readonly property real scaling: screen ? ScalingService.scale(screen) : 1.0
property Component panelContent: null
property int panelWidth: 1500
@ -50,6 +50,12 @@ Loader {
// -----------------------------------------
function toggle(aScreen, buttonItem) {
// Don't toggle if screen is null or invalid
if (!aScreen || !aScreen.name) {
Logger.warn("NPanel", "Cannot toggle panel: invalid screen object")
return
}
if (!active || isClosing) {
open(aScreen, buttonItem)
} else {
@ -59,6 +65,12 @@ Loader {
// -----------------------------------------
function open(aScreen, buttonItem) {
// Don't open if screen is null or invalid
if (!aScreen || !aScreen.name) {
Logger.warn("NPanel", "Cannot open panel: invalid screen object")
return
}
if (aScreen !== null) {
screen = aScreen
}

View file

@ -16,6 +16,7 @@ import qs.Commons
import qs.Modules.Launcher
import qs.Modules.Background
import qs.Modules.Bar
import qs.Modules.Bar.Extras
import qs.Modules.BluetoothPanel
import qs.Modules.Calendar
import qs.Modules.Dock
@ -99,6 +100,11 @@ 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