From e12e6b3a6664d6e3f28fa25179900487644c6e1a Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Wed, 8 Apr 2026 20:12:16 +0200 Subject: [PATCH] Add system tray to status bar in convergence mode Reintroduce StatusNotifierModel-based system tray icons, gated on convergenceModeEnabled. Desktop apps (KDE Connect, nm-applet, etc.) expose tray icons that were removed in 1914e9be as unusable on phone screens. The status bar wrapper is raised above the ActionDrawerOpenSurface so tray icon MouseAreas receive click events while non-interactive areas still fall through to the swipe handler. TaskWidget.qml supports left-click (Activate), right-click (ContextMenu), hover tooltip, and hides Passive/ApplicationStatus items. The lockscreen SIGABRT guard (disableSystemTray) is restored on the action drawer's StatusBar instance. --- components/mobileshell/CMakeLists.txt | 1 + .../actiondrawer/private/ContentContainer.qml | 3 + .../mobileshell/qml/statusbar/StatusBar.qml | 20 +++++++ .../mobileshell/qml/statusbar/TaskWidget.qml | 57 +++++++++++++++++++ containments/panel/qml/StatusPanel.qml | 18 ++++++ 5 files changed, 99 insertions(+) create mode 100644 components/mobileshell/qml/statusbar/TaskWidget.qml diff --git a/components/mobileshell/CMakeLists.txt b/components/mobileshell/CMakeLists.txt index c66c2ad6..8e592b2d 100644 --- a/components/mobileshell/CMakeLists.txt +++ b/components/mobileshell/CMakeLists.txt @@ -102,6 +102,7 @@ ecm_target_qml_sources(mobileshellplugin SOURCES qml/statusbar/indicators/VolumeIndicator.qml qml/statusbar/ClockText.qml qml/statusbar/StatusBar.qml + qml/statusbar/TaskWidget.qml qml/widgets/krunner/KRunnerScreen.qml qml/widgets/mediacontrols/BlurredBackground.qml diff --git a/components/mobileshell/qml/actiondrawer/private/ContentContainer.qml b/components/mobileshell/qml/actiondrawer/private/ContentContainer.qml index aef9d156..52674335 100644 --- a/components/mobileshell/qml/actiondrawer/private/ContentContainer.qml +++ b/components/mobileshell/qml/actiondrawer/private/ContentContainer.qml @@ -269,6 +269,9 @@ Item { showDropShadow: false showTime: root.actionDrawer.mode == MobileShell.ActionDrawer.Portrait + // Disable system tray on lockscreen to prevent SIGABRT + disableSystemTray: root.actionDrawer.restrictedPermissions + opacity: brightnessPressedValue } diff --git a/components/mobileshell/qml/statusbar/StatusBar.qml b/components/mobileshell/qml/statusbar/StatusBar.qml index d07d890d..42f35cc4 100644 --- a/components/mobileshell/qml/statusbar/StatusBar.qml +++ b/components/mobileshell/qml/statusbar/StatusBar.qml @@ -46,6 +46,11 @@ Item { */ property bool showTime: true + /** + * Whether to disable the system tray (e.g. on the lockscreen to prevent SIGABRT). + */ + property bool disableSystemTray: false + readonly property real textPixelSize: Math.round(11 * ShellSettings.Settings.statusBarScaleFactor) readonly property real smallerTextPixelSize: Math.round(9 * ShellSettings.Settings.statusBarScaleFactor) readonly property real elementSpacing: Math.round(Kirigami.Units.smallSpacing * 1.5) @@ -153,6 +158,21 @@ Item { implicitHeight: mainRow.rowHeight Layout.preferredWidth: height } + + // System tray icons (convergence mode only) + Loader { + id: statusNotifierSourceLoader + active: ShellSettings.Settings.convergenceModeEnabled && !root.disableSystemTray + sourceComponent: SystemTray.StatusNotifierModel {} + } + + Repeater { + id: statusNotifierRepeater + model: statusNotifierSourceLoader.item + delegate: TaskWidget { + Layout.leftMargin: root.elementSpacing + } + } } } diff --git a/components/mobileshell/qml/statusbar/TaskWidget.qml b/components/mobileshell/qml/statusbar/TaskWidget.qml new file mode 100644 index 00000000..01e9c67e --- /dev/null +++ b/components/mobileshell/qml/statusbar/TaskWidget.qml @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2011 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls as Controls +import org.kde.kirigami as Kirigami + +Item { + id: taskIcon + width: parent.height + height: width + + // Hide ApplicationStatus and Passive items + opacity: (model.category !== "ApplicationStatus" && model.status !== "Passive") ? 1 : 0 + onOpacityChanged: visible = opacity + + Behavior on opacity { + NumberAnimation { + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad + } + } + + Kirigami.Icon { + id: icon + source: model.iconName ? model.iconName : (model.icon ? model.icon : "") + width: Math.min(parent.width, parent.height) + height: width + anchors.centerIn: parent + } + + Controls.ToolTip.text: model.toolTipTitle ? model.toolTipTitle : (model.title ? model.title : "") + Controls.ToolTip.visible: mouseArea.containsMouse && Controls.ToolTip.text !== "" + Controls.ToolTip.delay: Kirigami.Units.toolTipDelay + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: (mouse) => { + if (!model.service) { + return; + } + + var operationName = mouse.button === Qt.RightButton ? "ContextMenu" : "Activate"; + var operation = model.service.operationDescription(operationName); + operation.x = taskIcon.mapToGlobal(0, 0).x; + operation.y = taskIcon.mapToGlobal(0, taskIcon.height).y; + model.service.startOperationCall(operation); + } + } +} diff --git a/containments/panel/qml/StatusPanel.qml b/containments/panel/qml/StatusPanel.qml index b1565e67..00077fb7 100644 --- a/containments/panel/qml/StatusPanel.qml +++ b/containments/panel/qml/StatusPanel.qml @@ -64,8 +64,12 @@ Item { } // Status bar component + // z: 1 so system tray icon MouseAreas inside StatusBar receive events + // before ActionDrawerOpenSurface (z: default). Non-interactive areas + // pass through to the swipe surface below. StatusBarWrapper { id: statusBarWrapper + z: 1 anchors.fill: parent statusPanelHeight: MobileShell.Constants.topPanelHeight @@ -116,6 +120,20 @@ Item { } } + // Keyboard shortcut to toggle action drawer (convergence mode) + Shortcut { + sequence: "Meta+A" + enabled: ShellSettings.Settings.convergenceModeEnabled + context: Qt.ApplicationShortcut + onActivated: { + if (drawer.actionDrawer.intendedToBeVisible) { + drawer.actionDrawer.close(); + } else { + drawer.actionDrawer.open(); + } + } + } + // Swiping area for swipe-down drawer MobileShell.ActionDrawerOpenSurface { id: swipeArea