Compare commits

...

4 commits

Author SHA1 Message Date
c23d756596 Add desktop settings links to Shell KCM
Surface installed desktop settings modules from the Shell KCM
so convergence users can reach display, networking, sound,
locking, virtual keyboard, and KWin workspace controls from
one place.
2026-05-09 10:01:14 +02:00
c5582393bc Promote desktop utilities in action drawer
Reshape the convergence quick settings into a compact
promoted grid and add Clipboard, Disks & Devices, and a
drawer-hosted System Tray popup.

Retry applet popups when their full representation becomes
ready so the desktop utilities open reliably.
2026-05-09 10:00:58 +02:00
85b5aec742 Expose power profiles in battery tile
Use the existing power profile control in the battery quick
setting so users can see and cycle available profiles
without opening settings.
2026-05-09 10:00:36 +02:00
0ec1794929 Add search launcher to convergence dock
Place a Search button next to Overview and rebalance the dock
center so pager, trash, favourites, and running tasks keep
their spacing in convergence mode.
2026-05-09 10:00:30 +02:00
8 changed files with 601 additions and 35 deletions

View file

@ -141,6 +141,7 @@ ecm_target_qml_sources(mobileshellplugin SOURCES
qml/actiondrawer/private/QuickSettingsPanel.qml
qml/actiondrawer/private/ContentContainer.qml
qml/actiondrawer/private/DetailPopup.qml
qml/actiondrawer/private/SystemTrayPopup.qml
qml/actiondrawer/private/LandscapeContentContainer.qml
qml/actiondrawer/private/NotificationDrawer.qml
qml/actiondrawer/private/PortraitContentContainer.qml

View file

@ -55,6 +55,16 @@ QQC2.Popup {
popup.open();
}
Connections {
target: MobileShell.AppletHost
function onAppletReady(pluginId) {
if (pluginId === popup.currentPluginId && !popup.opened) {
popup.show(pluginId);
}
}
}
onClosed: {
if (__currentItem) {
__currentItem.visible = false;

View file

@ -46,9 +46,9 @@ Item {
readonly property int columnCount: Math.floor(width/columnWidth)
readonly property int rowCount: {
let totalRows = Math.ceil(quickSettingsCount / columnCount);
let maxRows = 5; // more than 5 is just disorienting
let targetRows = Math.floor(Window.height * 0.65 / rowHeight);
return Math.min(maxRows, Math.min(totalRows, targetRows));
let maxRows = root.isConvergence ? 3 : 5; // more than 5 is just disorienting
let targetRows = Math.floor(Window.height * (root.isConvergence ? 0.42 : 0.65) / rowHeight);
return Math.max(1, Math.min(maxRows, Math.min(totalRows, targetRows)));
}
readonly property int pageSize: rowCount * columnCount
@ -63,6 +63,10 @@ Item {
})
readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled
function isManagementTile(cmd) { return cmd in __managementCommands; }
readonly property int promotedColumns: isConvergence && width >= Kirigami.Units.gridUnit * 18 ? 2 : 1
readonly property real promotedSpacing: Kirigami.Units.smallSpacing
readonly property real promotedHorizontalMargin: Kirigami.Units.smallSpacing
readonly property real promotedCellWidth: Math.floor((width - 2 * promotedHorizontalMargin - (promotedColumns - 1) * promotedSpacing) / promotedColumns)
readonly property alias brightnessPressedValue: brightnessItem.brightnessPressedValue
@ -136,21 +140,72 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
// Management status rows (convergence mode only)
ColumnLayout {
// Promoted desktop controls (convergence mode only)
GridLayout {
id: promotedGrid
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
Layout.leftMargin: root.promotedHorizontalMargin
Layout.rightMargin: root.promotedHorizontalMargin
Layout.bottomMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
columns: root.promotedColumns
rowSpacing: root.promotedSpacing
columnSpacing: root.promotedSpacing
visible: root.isConvergence
Repeater {
model: [
{
text: i18n("Clipboard"),
status: i18n("History"),
icon: "klipper-symbolic",
pluginId: "org.kde.plasma.clipboard"
},
{
text: i18n("Disks & Devices"),
status: i18n("Removable media"),
icon: "device-notifier-symbolic",
pluginId: "org.kde.plasma.devicenotifier"
},
{
text: i18n("System Tray"),
status: systemTrayPopup.trayItemCount > 0 ? i18np("%1 status item", "%1 status items", systemTrayPopup.trayItemCount) : i18n("No status items"),
icon: "preferences-desktop-notification-symbolic",
trayPopup: true
}
]
delegate: QuickSettingsStatusRow {
required property var modelData
Layout.preferredWidth: root.promotedCellWidth
Layout.fillWidth: true
compact: true
text: modelData.text
status: modelData.status
icon: modelData.icon
enabled: false
toggleFunction: null
onDetailClicked: {
if (modelData.trayPopup) {
systemTrayPopup.show();
} else {
detailPopup.show(modelData.pluginId);
}
}
}
}
Repeater {
model: root.quickSettingsModel
delegate: QuickSettingsStatusRow {
required property var modelData
readonly property bool isPromoted: root.isManagementTile(modelData.settingsCommand)
Layout.preferredWidth: isPromoted ? root.promotedCellWidth : 0
Layout.preferredHeight: isPromoted ? implicitHeight : 0
Layout.maximumWidth: isPromoted ? root.promotedCellWidth : 0
Layout.maximumHeight: isPromoted ? implicitHeight : 0
Layout.fillWidth: true
visible: root.isManagementTile(modelData.settingsCommand)
visible: isPromoted
compact: true
text: modelData.text
status: modelData.status
icon: modelData.icon
@ -286,4 +341,9 @@ Item {
parent: root.Window.window ? root.Window.window.contentItem : root
}
SystemTrayPopup {
id: systemTrayPopup
parent: root.Window.window ? root.Window.window.contentItem : root
}
}

View file

@ -9,8 +9,7 @@ import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
/**
* Full-width management row (Wi-Fi, Bluetooth, Audio, Battery) shown in
* convergence mode. Two interaction zones:
* Management/detail row shown in convergence mode. Two interaction zones:
* - Left toggle pill: icon + indicator dot, tap toggles the service.
* - Right detail area: name + status + chevron, tap opens detail popup.
*/
@ -22,10 +21,11 @@ Item {
required property string icon
required property bool enabled
required property var toggleFunction
property bool compact: false
signal detailClicked()
implicitHeight: Kirigami.Units.gridUnit * 3.6
implicitHeight: Kirigami.Units.gridUnit * (compact ? 3.1 : 3.6)
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Button
@ -35,6 +35,7 @@ Item {
readonly property color enabledBgHover: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.32)
readonly property color enabledBgPressed: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.12)
readonly property color enabledBorder: Qt.darker(Kirigami.Theme.highlightColor, 1.25)
readonly property bool hasToggle: toggleFunction !== null && toggleFunction !== undefined
readonly property color disabledBg: Kirigami.Theme.alternateBackgroundColor
readonly property color disabledBgHover: mixColor(Kirigami.Theme.alternateBackgroundColor, Kirigami.Theme.textColor, 0.06)
@ -146,8 +147,10 @@ Item {
// Indicator bar
Rectangle {
Layout.alignment: Qt.AlignHCenter
visible: root.hasToggle
Layout.preferredHeight: visible ? Math.max(2, Math.round(Kirigami.Units.devicePixelRatio)) : 0
width: root.enabled ? Kirigami.Units.smallSpacing * 3 : Kirigami.Units.smallSpacing * 1.5
height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio))
height: Layout.preferredHeight
radius: height / 2
color: root.enabled ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor
opacity: root.enabled ? 1.0 : 0.4
@ -162,10 +165,14 @@ Item {
id: toggleMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: haptics.buttonVibrate()
cursorShape: root.hasToggle ? Qt.PointingHandCursor : Qt.ArrowCursor
onPressed: {
if (root.hasToggle) {
haptics.buttonVibrate()
}
}
onClicked: {
if (root.toggleFunction) root.toggleFunction();
if (root.hasToggle) root.toggleFunction();
}
}
}

View file

@ -0,0 +1,238 @@
// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.1
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.systemtray as SystemTray
QQC2.Popup {
id: popup
modal: true
dim: true
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutside
x: parent ? Math.round((parent.width - width) / 2) : 0
y: parent ? Math.round((parent.height - height) / 2) : 0
width: Math.min(Kirigami.Units.gridUnit * 22,
parent ? parent.width - Kirigami.Units.gridUnit * 4 : 420)
height: Math.min(Kirigami.Units.gridUnit * 24,
parent ? parent.height - Kirigami.Units.gridUnit * 4 : 480)
padding: Kirigami.Units.smallSpacing
readonly property int trayItemCount: trayList.count
function show() {
popup.open();
}
SystemTray.StatusNotifierModel {
id: trayModel
}
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.cornerRadius
border.color: Kirigami.ColorUtils.linearInterpolation(
Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
border.width: 1
shadow.size: Kirigami.Units.gridUnit
shadow.color: Qt.rgba(0, 0, 0, 0.45)
shadow.yOffset: 2
}
contentItem: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
Layout.topMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: implicitWidth
source: "preferences-desktop-notification-symbolic"
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
QQC2.Label {
Layout.fillWidth: true
text: i18n("System Tray")
font.weight: Font.Bold
elide: Text.ElideRight
}
QQC2.Label {
Layout.fillWidth: true
text: trayList.count > 0 ? i18np("%1 status item", "%1 status items", trayList.count) : i18n("No status items")
opacity: 0.65
elide: Text.ElideRight
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Kirigami.ColorUtils.linearInterpolation(
Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.12)
}
ListView {
id: trayList
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: Kirigami.Units.smallSpacing
boundsBehavior: Flickable.StopAtBounds
model: trayModel
delegate: Item {
id: trayItem
width: ListView.view.width
height: Kirigami.Units.gridUnit * 3
readonly property string itemTitle: model.toolTipTitle ? model.toolTipTitle : (model.title ? model.title : i18n("Status Item"))
readonly property string itemStatus: {
if (model.status === "Passive") {
return i18n("Hidden");
}
if (model.category === "ApplicationStatus") {
return i18n("Application status");
}
return model.status ? model.status : i18n("Active");
}
readonly property bool itemActive: model.category !== "ApplicationStatus" && model.status !== "Passive"
function triggerOperation(operationName) {
if (!model.service) {
return;
}
let operation = model.service.operationDescription(operationName);
if (!operation) {
return;
}
let operationPoint = trayItem.mapToGlobal(trayItem.width, trayItem.height / 2);
operation.x = operationPoint.x;
operation.y = operationPoint.y;
model.service.startOperationCall(operation);
}
Rectangle {
id: rowBackground
anchors.fill: parent
anchors.leftMargin: Kirigami.Units.smallSpacing
anchors.rightMargin: Kirigami.Units.smallSpacing
radius: Kirigami.Units.cornerRadius
color: trayMouse.pressed ? Qt.rgba(Kirigami.Theme.textColor.r,
Kirigami.Theme.textColor.g,
Kirigami.Theme.textColor.b, 0.08)
: trayMouse.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r,
Kirigami.Theme.textColor.g,
Kirigami.Theme.textColor.b, 0.04)
: Kirigami.Theme.alternateBackgroundColor
border.width: 1
border.color: Kirigami.ColorUtils.linearInterpolation(
Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, trayItem.itemActive ? 0.16 : 0.08)
opacity: trayItem.itemActive ? 1 : 0.72
}
RowLayout {
anchors.fill: rowBackground
anchors.leftMargin: Kirigami.Units.smallSpacing * 2
anchors.rightMargin: Kirigami.Units.smallSpacing * 2
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: implicitWidth
source: model.iconName ? model.iconName : (model.icon ? model.icon : "")
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 2
MobileShell.MarqueeLabel {
Layout.fillWidth: true
inputText: trayItem.itemTitle
font.weight: Font.Bold
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9
}
MobileShell.MarqueeLabel {
Layout.fillWidth: true
inputText: trayItem.itemStatus
opacity: 0.6
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
}
}
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter
implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: implicitWidth
source: "go-next-symbolic"
opacity: 0.45
}
}
QQC2.ToolTip.text: trayItem.itemTitle
QQC2.ToolTip.visible: trayMouse.containsMouse && trayItem.itemTitle !== ""
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
MouseArea {
id: trayMouse
anchors.fill: rowBackground
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
trayItem.triggerOperation(mouse.button === Qt.RightButton ? "ContextMenu" : "Activate");
}
}
}
QQC2.Label {
anchors.centerIn: parent
visible: trayList.count === 0
text: i18n("No status items")
opacity: 0.65
}
}
}
enter: Transition {
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
NumberAnimation { property: "scale"; from: 0.9; to: 1; duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
exit: Transition {
NumberAnimation { property: "opacity"; from: 1; to: 0; duration: Kirigami.Units.shortDuration; easing.type: Easing.InCubic }
}
QQC2.Overlay.modal: Rectangle {
color: Qt.rgba(0, 0, 0, 0.5)
}
}

View file

@ -62,11 +62,6 @@ MouseArea {
return "transparent"
}
// Center x for dock items (offset between nav buttons in convergence mode)
readonly property real dockCenterX: convergenceMode
? navButtonWidth + (root.width - 2 * navButtonWidth) / 2
: root.width / 2
// Visible spacer between pinned favourites and running tasks
readonly property bool showSpacer: showRunningTasks && repeater.count > 0 && taskRepeater.count > 0
property real spacerWidth: showSpacer ? Kirigami.Units.largeSpacing * 2 : 0
@ -99,8 +94,15 @@ MouseArea {
readonly property int pagerLeftCount: showPager ? Math.ceil(virtualDesktopInfo.numberOfDesktops / 2) : 0
readonly property int pagerRightCount: showPager ? virtualDesktopInfo.numberOfDesktops - pagerLeftCount : 0
property real trashButtonWidth: convergenceMode ? root.height : 0
property real searchButtonWidth: convergenceMode ? root.height : 0
readonly property real leftControlsWidth: convergenceMode ? navButtonWidth + pagerLeftCount * pagerButtonWidth : 0
readonly property real rightControlsWidth: convergenceMode ? navButtonWidth + searchButtonWidth + trashButtonWidth + pagerRightCount * pagerButtonWidth : 0
readonly property real dockCenterX: convergenceMode
? leftControlsWidth + (root.width - leftControlsWidth - rightControlsWidth) / 2
: root.width / 2
Behavior on pagerButtonWidth { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
Behavior on trashButtonWidth { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
Behavior on searchButtonWidth { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } }
function pagerDesktopName(index) {
let names = virtualDesktopInfo.desktopNames
@ -147,7 +149,7 @@ MouseArea {
return (ids && i < ids.length) ? String(ids[i]) : ""
}
for (let i = 0; i < pagerRightCount; ++i) {
let bx = root.width - navButtonWidth - root.trashButtonWidth - (pagerRightCount - i) * pagerButtonWidth
let bx = root.width - navButtonWidth - root.searchButtonWidth - root.trashButtonWidth - (pagerRightCount - i) * pagerButtonWidth
if (x >= bx && x < bx + pagerButtonWidth) {
let di = pagerLeftCount + i
return (ids && di < ids.length) ? String(ids[di]) : ""
@ -237,7 +239,7 @@ MouseArea {
if (first) { first.keyboardFocus(); return }
let firstTask = taskRepeater.itemAt(0)
if (firstTask) { firstTask.forceActiveFocus(); return }
overviewButton.forceActiveFocus()
searchButton.forceActiveFocus()
}
KeyboardHighlight {
@ -298,11 +300,7 @@ MouseArea {
Keys.onEnterPressed: root.folio.triggerOverview()
Keys.onSpacePressed: root.folio.triggerOverview()
Keys.onLeftPressed: {
let lastTask = taskRepeater.itemAt(taskRepeater.count - 1)
if (lastTask) { lastTask.forceActiveFocus(); return }
let lastFav = repeater.itemAt(repeater.count - 1)
if (lastFav) { lastFav.keyboardFocus(); return }
homeButton.forceActiveFocus()
searchButton.forceActiveFocus()
}
KeyboardHighlight {
@ -338,6 +336,77 @@ MouseArea {
}
}
// Search button (convergence mode, immediately left of Overview)
Rectangle {
id: searchButton
visible: root.convergenceMode || opacity > 0
enabled: root.convergenceMode
opacity: root.convergenceMode ? 1 : 0
activeFocusOnTab: root.convergenceMode
x: root.width - root.navButtonWidth - root.searchButtonWidth
y: 0
width: root.searchButtonWidth
height: root.height
color: "transparent"
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
}
Accessible.role: Accessible.Button
Accessible.name: i18n("Search")
Accessible.onPressAction: root.folio.HomeScreenState.openSearchWidget()
Keys.onReturnPressed: root.folio.HomeScreenState.openSearchWidget()
Keys.onEnterPressed: root.folio.HomeScreenState.openSearchWidget()
Keys.onSpacePressed: root.folio.HomeScreenState.openSearchWidget()
Keys.onLeftPressed: {
let lastTask = taskRepeater.itemAt(taskRepeater.count - 1)
if (lastTask) { lastTask.forceActiveFocus(); return }
let lastFav = repeater.itemAt(repeater.count - 1)
if (lastFav) { lastFav.keyboardFocus(); return }
homeButton.forceActiveFocus()
}
Keys.onRightPressed: overviewButton.forceActiveFocus()
KeyboardHighlight {
anchors.fill: parent
visible: searchButton.activeFocus
}
Rectangle {
anchors.fill: parent
anchors.margins: root.dockItemInset
radius: Kirigami.Units.cornerRadius
color: root.dockItemColor(searchMouseArea.containsPress, searchMouseArea.containsMouse, false)
Behavior on color {
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
}
Kirigami.Icon {
anchors.centerIn: parent
width: root.dockIconSize
height: width
source: "search"
active: searchMouseArea.containsMouse
}
PC3.ToolTip {
visible: searchMouseArea.containsMouse
text: i18n("Search")
}
MouseArea {
id: searchMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: root.convergenceMode ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: root.folio.HomeScreenState.openSearchWidget()
}
}
// ---- Virtual desktop pager: left wing (desktops 1 .. ceil(N/2)) ----
Repeater {
id: leftPagerRepeater
@ -421,7 +490,7 @@ MouseArea {
return root.pagerButtonDesktopAt(cx) === desktopId
}
x: root.width - root.navButtonWidth - root.trashButtonWidth - (root.pagerRightCount - index) * root.pagerButtonWidth
x: root.width - root.navButtonWidth - root.searchButtonWidth - root.trashButtonWidth - (root.pagerRightCount - index) * root.pagerButtonWidth
y: 0
width: root.pagerButtonWidth
height: root.height
@ -503,7 +572,7 @@ MouseArea {
enabled: root.convergenceMode
opacity: root.convergenceMode ? 1 : 0
activeFocusOnTab: root.convergenceMode
x: root.width - root.navButtonWidth - root.trashButtonWidth
x: root.width - root.navButtonWidth - root.searchButtonWidth - root.trashButtonWidth
y: 0
width: root.trashButtonWidth
height: root.height
@ -747,7 +816,7 @@ MouseArea {
if (firstTask) {
firstTask.forceActiveFocus();
} else {
overviewButton.forceActiveFocus();
searchButton.forceActiveFocus();
}
event.accepted = true;
}
@ -1321,7 +1390,7 @@ MouseArea {
Keys.onRightPressed: {
let next = taskRepeater.itemAt(taskDelegate.index + 1)
if (next) { next.forceActiveFocus(); return }
overviewButton.forceActiveFocus()
searchButton.forceActiveFocus()
}
// Position after all favourites

View file

@ -10,6 +10,7 @@ import QtQuick.Controls 2.15 as QQC2
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
KCM.SimpleKCM {
@ -22,6 +23,10 @@ KCM.SimpleKCM {
leftPadding: 0
rightPadding: 0
function openSettingsModule(moduleName) {
MobileShell.ShellUtil.executeCommand("plasma-open-settings " + moduleName);
}
ColumnLayout {
FormCard.FormHeader {
title: i18n("General")
@ -125,6 +130,136 @@ KCM.SimpleKCM {
}
}
}
FormCard.FormDelegateSeparator { above: autoHidePanels; below: displayConfigurationButton }
FormCard.FormButtonDelegate {
id: displayConfigurationButton
icon.name: "preferences-desktop-display-randr"
text: i18n("Display Configuration")
onClicked: root.openSettingsModule("kcm_kscreen")
}
FormCard.FormDelegateSeparator { above: displayConfigurationButton; below: networkingButton }
FormCard.FormButtonDelegate {
id: networkingButton
icon.name: "preferences-system-network"
text: i18n("Wi-Fi & Networking")
onClicked: root.openSettingsModule("kcm_networkmanagement")
}
FormCard.FormDelegateSeparator { above: networkingButton; below: soundButton }
FormCard.FormButtonDelegate {
id: soundButton
icon.name: "preferences-desktop-sound"
text: i18n("Sound")
onClicked: root.openSettingsModule("kcm_pulseaudio")
}
FormCard.FormDelegateSeparator { above: soundButton; below: shortcutsButton }
FormCard.FormButtonDelegate {
id: shortcutsButton
icon.name: "preferences-desktop-keyboard-shortcut"
text: i18n("Shortcuts")
onClicked: root.openSettingsModule("kcm_keys")
}
FormCard.FormDelegateSeparator { above: shortcutsButton; below: accessibilityButton }
FormCard.FormButtonDelegate {
id: accessibilityButton
icon.name: "preferences-desktop-accessibility"
text: i18n("Accessibility")
onClicked: root.openSettingsModule("kcm_access")
}
FormCard.FormDelegateSeparator { above: accessibilityButton; below: notificationsButton }
FormCard.FormButtonDelegate {
id: notificationsButton
icon.name: "preferences-desktop-notification-bell"
text: i18n("Notifications")
onClicked: root.openSettingsModule("kcm_notifications")
}
FormCard.FormDelegateSeparator { above: notificationsButton; below: screenLockingButton }
FormCard.FormButtonDelegate {
id: screenLockingButton
icon.name: "preferences-desktop-user-password"
text: i18n("Screen Locking")
onClicked: root.openSettingsModule("kcm_screenlocker")
}
FormCard.FormDelegateSeparator { above: screenLockingButton; below: virtualKeyboardButton }
FormCard.FormButtonDelegate {
id: virtualKeyboardButton
icon.name: "input-keyboard-virtual"
text: i18n("Virtual Keyboard")
onClicked: root.openSettingsModule("kcm_virtualkeyboard")
}
}
FormCard.FormHeader {
title: i18n("Desktop Workspace")
}
FormCard.FormCard {
FormCard.FormButtonDelegate {
id: virtualDesktopsButton
icon.name: "preferences-desktop-virtual"
text: i18n("Virtual Desktops")
onClicked: root.openSettingsModule("kcm_kwin_virtualdesktops")
}
FormCard.FormDelegateSeparator { above: virtualDesktopsButton; below: windowBehaviorButton }
FormCard.FormButtonDelegate {
id: windowBehaviorButton
icon.name: "preferences-system-windows-actions"
text: i18n("Window Behavior")
onClicked: root.openSettingsModule("kcm_kwinoptions")
}
FormCard.FormDelegateSeparator { above: windowBehaviorButton; below: windowRulesButton }
FormCard.FormButtonDelegate {
id: windowRulesButton
icon.name: "preferences-system-windows-actions"
text: i18n("Window Rules")
onClicked: root.openSettingsModule("kcm_kwinrules")
}
FormCard.FormDelegateSeparator { above: windowRulesButton; below: taskSwitcherButton }
FormCard.FormButtonDelegate {
id: taskSwitcherButton
icon.name: "preferences-system-tabbox"
text: i18n("Task Switcher")
onClicked: root.openSettingsModule("kcm_kwintabbox")
}
FormCard.FormDelegateSeparator { above: taskSwitcherButton; below: desktopEffectsButton }
FormCard.FormButtonDelegate {
id: desktopEffectsButton
icon.name: "preferences-desktop-effects"
text: i18n("Desktop Effects")
onClicked: root.openSettingsModule("kcm_kwin_effects")
}
FormCard.FormDelegateSeparator { above: desktopEffectsButton; below: windowDecorationsButton }
FormCard.FormButtonDelegate {
id: windowDecorationsButton
icon.name: "preferences-desktop-theme-windowdecorations"
text: i18n("Window Decorations")
onClicked: root.openSettingsModule("kcm_kwindecoration")
}
}
FormCard.FormHeader {

View file

@ -5,11 +5,57 @@ import QtQuick 2.15
import org.kde.plasma.private.mobileshell.quicksettingsplugin as QS
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.gamingshellplugin as GamingShell
QS.QuickSetting {
id: root
readonly property var profileOrder: ["power-saver", "balanced", "performance"]
readonly property bool profileAvailable: GamingShell.PowerProfileControl.available
&& GamingShell.PowerProfileControl.profiles.length > 0
property var toggle: profileAvailable ? root.cycleProfile : undefined
text: i18n("Battery")
status: i18n("%1%", MobileShell.BatteryInfo.percent)
icon: "battery-full" + (MobileShell.BatteryInfo.pluggedIn ? "-charging" : "")
enabled: false
status: profileAvailable
? i18n("%1% - %2", MobileShell.BatteryInfo.percent, profileLabel(GamingShell.PowerProfileControl.activeProfile))
: i18n("%1%", MobileShell.BatteryInfo.percent)
icon: profileAvailable ? profileIcon(GamingShell.PowerProfileControl.activeProfile)
: "battery-full" + (MobileShell.BatteryInfo.pluggedIn ? "-charging" : "")
enabled: profileAvailable && GamingShell.PowerProfileControl.activeProfile !== "balanced"
settingsCommand: "plasma-open-settings kcm_mobile_power"
function profileLabel(profile) {
switch (profile) {
case "performance": return i18n("Performance")
case "balanced": return i18n("Balanced")
case "power-saver": return i18n("Power Saver")
default: return profile
}
}
function profileIcon(profile) {
switch (profile) {
case "performance": return "speedometer"
case "power-saver": return "battery-profile-powersave"
default: return "battery-full" + (MobileShell.BatteryInfo.pluggedIn ? "-charging" : "")
}
}
function cycleProfile() {
let availableProfiles = []
for (let i = 0; i < profileOrder.length; ++i) {
let profile = profileOrder[i]
if (GamingShell.PowerProfileControl.profiles.indexOf(profile) >= 0) {
availableProfiles.push(profile)
}
}
if (availableProfiles.length === 0) {
return
}
let currentIndex = availableProfiles.indexOf(GamingShell.PowerProfileControl.activeProfile)
let nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % availableProfiles.length
GamingShell.PowerProfileControl.activeProfile = availableProfiles[nextIndex]
}
}