From 839d5e2bffacd3456a30e15a8ff0bd8aa6023fd9 Mon Sep 17 00:00:00 2001 From: Devin Lin Date: Sun, 5 Oct 2025 19:06:52 -0400 Subject: [PATCH] panels: Add support for defining device specific panel tweaks This adds support for specifying options needed to deal with phone display panel pecularities (ex. screen curves, notches, punch holes) This is implemented as settings in ~/.config/plasmamobilerc, which can set panel heights, paddings, and center spacings to duck display cutouts. The pixel values are scaling independent, and so are not affected when the display scaling is changed. This is then exposed over DBus, so that components from outside of plasmashell (ex. KWin) can access it easily without needing to connect to kscreen themselves. Each screen is exposed as a single object. Currently support is only added in the status bar and the navigation panel. Currently all screens have the settings applied. In the future, we may want to limit this just to the internal screen (?) --- This also adds a "devices" folder (in `devices/configs`) where per-device configs can be set. This is installed to `/usr/share/plasma-mobile-device-configs`. In `plasmamobilerc` (installed to `/etc/xdg/plasmamobilerc`, or `~/.config/plasmamobilerc`), envmanager will read: ```toml [Device] device=oneplus-enchilada ``` for the device config to use and write its settings to `~/.config/plasma-mobile/plasmamobilerc`. --- CMakeLists.txt | 2 + README.md | 4 + .../mobileshell/qml/components/Constants.qml | 27 +- .../qml/navigationpanel/NavigationPanel.qml | 35 ++- .../navigationpanel/NavigationPanelAction.qml | 2 +- .../navigationpanel/NavigationPanelButton.qml | 12 +- .../mobileshell/qml/statusbar/StatusBar.qml | 13 +- components/mobileshell/shellutil.cpp | 9 +- components/mobileshellstate/CMakeLists.txt | 29 +- .../panelsettingsdbusclient.cpp | 252 ++++++++++++++++ .../panelsettingsdbusclient.h | 83 ++++++ .../panelsettingsdbusobjectmanager.cpp | 268 ++++++++++++++++++ .../panelsettingsdbusobjectmanager.h | 95 +++++++ .../quicksettingsconfig.cpp | 2 +- components/shellsettingsplugin/CMakeLists.txt | 6 +- .../shellsettingsplugin/kwinsettings.cpp | 2 +- .../mobileshellsettings.cpp | 2 +- .../wallpaperimageplugin/wallpaperplugin.cpp | 4 +- containments/panel/qml/main.qml | 26 +- .../qml/NavigationPanelComponent.qml | 17 +- containments/taskpanel/qml/main.qml | 1 + devices/CMakeLists.txt | 4 + devices/README.md | 32 +++ devices/configs/default.conf | 5 + devices/configs/google-sargo.conf | 7 + devices/configs/oneplus-enchilada.conf | 15 + devices/spec.conf | 47 +++ envmanager/CMakeLists.txt | 1 + envmanager/devicepresets.cpp | 88 ++++++ envmanager/devicepresets.h | 20 ++ envmanager/settings.cpp | 7 +- initialstart/settings.cpp | 2 +- .../package/contents/ui/TaskSwitcher.qml | 13 +- .../contents/layout.js | 1 - .../contents/layout.js | 1 - shell/contents/views/Desktop.qml | 1 + 36 files changed, 1081 insertions(+), 54 deletions(-) create mode 100644 components/mobileshellstate/panelsettingsdbusclient.cpp create mode 100644 components/mobileshellstate/panelsettingsdbusclient.h create mode 100644 components/mobileshellstate/panelsettingsdbusobjectmanager.cpp create mode 100644 components/mobileshellstate/panelsettingsdbusobjectmanager.h create mode 100644 devices/CMakeLists.txt create mode 100644 devices/README.md create mode 100644 devices/configs/default.conf create mode 100644 devices/configs/google-sargo.conf create mode 100644 devices/configs/oneplus-enchilada.conf create mode 100644 devices/spec.conf create mode 100644 envmanager/devicepresets.cpp create mode 100644 envmanager/devicepresets.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 862014b3..723c7bf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,7 @@ plasma_install_package(shell org.kde.plasma.mobileshell shells) add_subdirectory(bin) add_subdirectory(components) add_subdirectory(containments) +add_subdirectory(devices) add_subdirectory(quicksettings) add_subdirectory(kcms) add_subdirectory(kded) @@ -146,6 +147,7 @@ add_subdirectory(layout-templates) if(BUILD_TESTING) add_subdirectory(tests) endif() + install(FILES org.kde.plasma.mobileshell.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) find_program(PlasmaOpenSettings plasma-open-settings) diff --git a/README.md b/README.md index eee6da02..13a5c3a9 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,10 @@ Useful options: QT_QPA_PLATFORM=wayland dbus-run-session kwin_wayland --xwayland "plasmashell -p org.kde.plasma.mobileshell" --output-count 2 --width 360 --height 720 ``` +### Device specific configuration + +See [/devices/README.md] for more details on setting device specific configuration (ex. notches, screen curves). + --- diff --git a/components/mobileshell/qml/components/Constants.qml b/components/mobileshell/qml/components/Constants.qml index 8a76a038..1255f305 100644 --- a/components/mobileshell/qml/components/Constants.qml +++ b/components/mobileshell/qml/components/Constants.qml @@ -5,6 +5,7 @@ import QtQuick import org.kde.kirigami 2.20 as Kirigami import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings +import org.kde.plasma.private.mobileshell.state as MobileShellState // NOTE: This is a singleton in the mobileshell library, so we need to be careful to // make this load as fast as possible (since it may be loaded in other processes ex. lockscreen). @@ -12,8 +13,30 @@ import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings pragma Singleton QtObject { - readonly property real topPanelHeight: Math.round(Kirigami.Units.gridUnit * ShellSettings.Settings.statusBarScaleFactor / 2) * 2 + Kirigami.Units.smallSpacing - readonly property real navigationPanelThickness: ShellSettings.Settings.navigationPanelEnabled ? Kirigami.Units.gridUnit * 2 : 0 + id: root + readonly property var panelSettings: MobileShellState.PanelSettingsDBusClient { + screenName: Screen.name + } + + readonly property real defaultTopPanelHeight: Math.round(Kirigami.Units.gridUnit * ShellSettings.Settings.statusBarScaleFactor / 2) * 2 + Kirigami.Units.smallSpacing + + readonly property real topPanelHeight: { + if (root.panelSettings.statusBarHeight <= 0) { + return defaultTopPanelHeight; + } + return root.panelSettings.statusBarHeight; + } + + readonly property real defaultNavigationPanelThickness: Kirigami.Units.gridUnit * 2 + + readonly property real navigationPanelThickness: { + if (!ShellSettings.Settings.navigationPanelEnabled) { + return 0; + } else if (root.panelSettings.navigationPanelHeight <= 0) { + return defaultNavigationPanelThickness; + } + return root.panelSettings.navigationPanelHeight; + } function navigationPanelOnSide(screenWidth: real, screenHeight: real): bool { // TODO: we have this disabled for now, we might consider just removing this feature entirely due to it causing several issues: diff --git a/components/mobileshell/qml/navigationpanel/NavigationPanel.qml b/components/mobileshell/qml/navigationpanel/NavigationPanel.qml index d69d8488..77278eac 100644 --- a/components/mobileshell/qml/navigationpanel/NavigationPanel.qml +++ b/components/mobileshell/qml/navigationpanel/NavigationPanel.qml @@ -29,11 +29,14 @@ Item { property NavigationPanelAction leftCornerAction property NavigationPanelAction rightCornerAction + property real leftPadding: 0 + property real rightPadding: 0 + property bool isVertical: false // drop shadow for icons MultiEffect { - anchors.fill: root + anchors.fill: icons visible: shadow source: icons blurMax: 16 @@ -42,25 +45,25 @@ Item { shadowOpacity: 0.8 } + // background colour + Rectangle { + anchors.fill: parent + color: root.backgroundColor + } + Item { id: icons anchors.fill: parent property real buttonLength: 0 - // background colour - Rectangle { - anchors.fill: parent - color: root.backgroundColor - } - NavigationPanelButton { id: leftCornerButton visible: root.leftCornerAction.visible Kirigami.Theme.colorSet: root.foregroundColorGroup Kirigami.Theme.inherit: false enabled: root.leftCornerAction.enabled - iconSizeFactor: root.leftCornerAction.iconSizeFactor + shrinkSize: root.leftCornerAction.shrinkSize iconSource: root.leftCornerAction.iconSource onClicked: { if (enabled) { @@ -76,7 +79,7 @@ Item { Kirigami.Theme.colorSet: root.foregroundColorGroup Kirigami.Theme.inherit: false enabled: root.leftAction.enabled - iconSizeFactor: root.leftAction.iconSizeFactor + shrinkSize: root.leftAction.shrinkSize iconSource: root.leftAction.iconSource onClicked: { if (enabled) { @@ -92,7 +95,7 @@ Item { Kirigami.Theme.colorSet: root.foregroundColorGroup Kirigami.Theme.inherit: false enabled: root.middleAction.enabled - iconSizeFactor: root.middleAction.iconSizeFactor + shrinkSize: root.middleAction.shrinkSize iconSource: root.middleAction.iconSource onClicked: { if (enabled) { @@ -107,7 +110,7 @@ Item { Kirigami.Theme.colorSet: root.foregroundColorGroup Kirigami.Theme.inherit: false enabled: root.rightAction.enabled - iconSizeFactor: root.rightAction.iconSizeFactor + shrinkSize: root.rightAction.shrinkSize iconSource: root.rightAction.iconSource onClicked: { if (enabled) { @@ -122,7 +125,7 @@ Item { Kirigami.Theme.colorSet: root.foregroundColorGroup Kirigami.Theme.inherit: false enabled: root.rightCornerAction.enabled - iconSizeFactor: root.rightCornerAction.iconSizeFactor + shrinkSize: root.rightCornerAction.shrinkSize iconSource: root.rightCornerAction.iconSource onClicked: { if (enabled) { @@ -138,6 +141,10 @@ Item { when: root.isVertical PropertyChanges { target: icons + anchors { + topMargin: root.leftPadding + bottomMargin: root.rightPadding + } buttonLength: Math.min(Kirigami.Units.gridUnit * 10, icons.height * 0.7 / 3) } AnchorChanges { @@ -198,6 +205,10 @@ Item { when: !root.isVertical PropertyChanges { target: icons + anchors { + leftMargin: root.leftPadding + rightMargin: root.rightPadding + } buttonLength: Math.min(Kirigami.Units.gridUnit * 8, icons.width * 0.7 / 3) } AnchorChanges { diff --git a/components/mobileshell/qml/navigationpanel/NavigationPanelAction.qml b/components/mobileshell/qml/navigationpanel/NavigationPanelAction.qml index dc66f541..1fa3a96d 100644 --- a/components/mobileshell/qml/navigationpanel/NavigationPanelAction.qml +++ b/components/mobileshell/qml/navigationpanel/NavigationPanelAction.qml @@ -10,7 +10,7 @@ QtObject { property bool enabled property bool visible: true property string iconSource - property real iconSizeFactor + property real shrinkSize signal triggered() } diff --git a/components/mobileshell/qml/navigationpanel/NavigationPanelButton.qml b/components/mobileshell/qml/navigationpanel/NavigationPanelButton.qml index 0acc4771..0b1e9e2e 100644 --- a/components/mobileshell/qml/navigationpanel/NavigationPanelButton.qml +++ b/components/mobileshell/qml/navigationpanel/NavigationPanelButton.qml @@ -20,7 +20,7 @@ Controls.AbstractButton { width: Math.min(parent.width, parent.height) height: width - property double iconSizeFactor: 1 + property int shrinkSize: 0 property alias iconSource: icon.source MobileShell.HapticsEffect { @@ -79,9 +79,11 @@ Controls.AbstractButton { Kirigami.Theme.colorSet: button.Kirigami.Theme.colorSet readonly property real side: Math.min(button.width, button.height) - anchors { - fill: parent - margins: Math.round((side - side * iconSizeFactor * 0.6) / 2) - } + anchors.centerIn: parent + + implicitHeight: Kirigami.Units.iconSizes.smallMedium - shrinkSize + implicitWidth: Kirigami.Units.iconSizes.smallMedium - shrinkSize + width: implicitWidth + height: implicitHeight } } diff --git a/components/mobileshell/qml/statusbar/StatusBar.qml b/components/mobileshell/qml/statusbar/StatusBar.qml index fec1ed0d..0d6d1fae 100644 --- a/components/mobileshell/qml/statusbar/StatusBar.qml +++ b/components/mobileshell/qml/statusbar/StatusBar.qml @@ -21,6 +21,7 @@ import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.kitemmodels as KItemModels import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings +import org.kde.plasma.private.mobileshell.state as MobileShellState Item { id: root @@ -70,6 +71,11 @@ Item { sourceComponent: SystemTray.StatusNotifierModel { } } + MobileShellState.PanelSettingsDBusClient { + id: panelSettings + screenName: Screen.name + } + // drop shadow for icons MultiEffect { anchors.fill: control @@ -87,8 +93,8 @@ Item { z: 1 topPadding: Kirigami.Units.smallSpacing bottomPadding: Kirigami.Units.smallSpacing - rightPadding: Kirigami.Units.smallSpacing * 3 - leftPadding: Kirigami.Units.smallSpacing * 3 + rightPadding: Kirigami.Units.smallSpacing * 3 + panelSettings.statusBarLeftPadding + leftPadding: Kirigami.Units.smallSpacing * 3 + + panelSettings.statusBarRightPadding anchors.fill: parent background: Rectangle { @@ -101,9 +107,10 @@ Item { RowLayout { id: mainRow - readonly property real rowHeight: MobileShell.Constants.topPanelHeight - Kirigami.Units.smallSpacing * 2 + readonly property real rowHeight: MobileShell.Constants.defaultTopPanelHeight - Kirigami.Units.smallSpacing * 2 Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter Layout.preferredHeight: rowHeight spacing: 0 diff --git a/components/mobileshell/shellutil.cpp b/components/mobileshell/shellutil.cpp index 000e4050..a50a9067 100644 --- a/components/mobileshell/shellutil.cpp +++ b/components/mobileshell/shellutil.cpp @@ -29,9 +29,10 @@ #define FORMAT24H "HH:mm:ss" - -ShellUtil::ShellUtil(QObject *parent) : QObject{parent}, m_localeConfig { - KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig) } { +ShellUtil::ShellUtil(QObject *parent) + : QObject{parent} + , m_localeConfig{KSharedConfig::openConfig(QStringLiteral("kdeglobals"))} +{ } void ShellUtil::stackItemBefore(QQuickItem *item1, QQuickItem *item2) @@ -119,7 +120,7 @@ void ShellUtil::setInputRegion(QWindow *window, const QRect ®ion) { qWarning() << "Failed to retrieve Wayland window handle."; return; } - + auto waylandDisplay = dynamic_cast(waylandWindow->display()); if (!waylandDisplay) { qWarning() << "Failed to retrieve Wayland display."; diff --git a/components/mobileshellstate/CMakeLists.txt b/components/mobileshellstate/CMakeLists.txt index 32bd87a0..abd53c9d 100644 --- a/components/mobileshellstate/CMakeLists.txt +++ b/components/mobileshellstate/CMakeLists.txt @@ -8,20 +8,46 @@ set(mobileshellstateplugin_SRCS startupfeedbackmodel.cpp windowlistener.cpp volumeosdlistener.cpp + panelsettingsdbusobjectmanager.cpp + panelsettingsdbusclient.cpp ) +# Add shell dbus API qt_generate_dbus_interface( ${CMAKE_CURRENT_SOURCE_DIR}/shelldbusobject.h org.kde.plasmashell.Mobile.xml OPTIONS -s -m -P ) - qt_add_dbus_adaptor(mobileshellstateplugin_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Mobile.xml ${CMAKE_CURRENT_SOURCE_DIR}/shelldbusobject.h ShellDBusObject) qt_add_dbus_interface(mobileshellstateplugin_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Mobile.xml plasmashellmobileinterface) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Mobile.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) +# --- +# Add shell panels dbus API +qt_generate_dbus_interface( + ${CMAKE_CURRENT_SOURCE_DIR}/panelsettingsdbusobjectmanager.h + org.kde.plasmashell.Mobile.Panels.xml + OPTIONS -s -m -P +) +qt_add_dbus_adaptor(mobileshellstateplugin_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Mobile.Panels.xml + ${CMAKE_CURRENT_SOURCE_DIR}/panelsettingsdbusobjectmanager.h PanelSettingsDBusObject +) +set_source_files_properties( + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Mobile.Panels.xml + PROPERTIES + CLASSNAME "OrgKdePlasmashellMobilePanelsInterface" +) +qt_add_dbus_interface( + mobileshellstateplugin_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Mobile.Panels.xml + plasmashellmobilepanelsinterface +) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Mobile.Panels.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) +# --- ecm_add_qml_module(mobileshellstateplugin URI org.kde.plasma.private.mobileshell.state GENERATE_PLUGIN_SOURCE) @@ -40,6 +66,7 @@ target_link_libraries(mobileshellstateplugin Plasma::KWaylandClient KF6::I18n KF6::Notifications + KF6::Screen Plasma::PlasmaQuick ) diff --git a/components/mobileshellstate/panelsettingsdbusclient.cpp b/components/mobileshellstate/panelsettingsdbusclient.cpp new file mode 100644 index 00000000..4ad9a59c --- /dev/null +++ b/components/mobileshellstate/panelsettingsdbusclient.cpp @@ -0,0 +1,252 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "panelsettingsdbusclient.h" + +#include + +PanelSettingsDBusClient::PanelSettingsDBusClient(QObject *parent) + : QObject{parent} + , m_interface{nullptr} + , m_connected{false} +{ +} + +void PanelSettingsDBusClient::connectToDBus() +{ + if (m_interface) { + return; + } + m_interface = new OrgKdePlasmashellMobilePanelsInterface{QStringLiteral("org.kde.plasmashell"), + QStringLiteral("/Mobile/Panels/") + m_screenName.replace("-", ""), + QDBusConnection::sessionBus(), + this}; + + // Check if the service is already running + if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.plasmashell"))) { + m_connected = true; + if (m_interface->isValid()) { + connectSignals(); + } + } + + connect(QDBusConnection::sessionBus().interface(), + &QDBusConnectionInterface::serviceOwnerChanged, + this, + [this](const QString &service, const QString &oldOwner, const QString &newOwner) { + Q_UNUSED(oldOwner); + if (service == QStringLiteral("org.kde.plasmashell")) { + if (!newOwner.isEmpty() && !m_connected) { + m_connected = true; + if (m_interface->isValid()) { + connectSignals(); + } + } else if (newOwner.isEmpty() && m_connected) { + m_connected = false; + } + } + }); +} + +QString PanelSettingsDBusClient::screenName() const +{ + return m_screenName; +} + +void PanelSettingsDBusClient::setScreenName(const QString &screenName) +{ + if (screenName == m_screenName) { + return; + } + m_screenName = screenName; + Q_EMIT screenNameChanged(); + + connectToDBus(); +} + +void PanelSettingsDBusClient::connectSignals() +{ + connect(m_interface, &OrgKdePlasmashellMobilePanelsInterface::statusBarHeightChanged, this, &PanelSettingsDBusClient::updateStatusBarHeight); + connect(m_interface, &OrgKdePlasmashellMobilePanelsInterface::statusBarLeftPaddingChanged, this, &PanelSettingsDBusClient::updateStatusBarLeftPadding); + connect(m_interface, &OrgKdePlasmashellMobilePanelsInterface::statusBarRightPaddingChanged, this, &PanelSettingsDBusClient::updateStatusBarRightPadding); + connect(m_interface, &OrgKdePlasmashellMobilePanelsInterface::statusBarCenterSpacingChanged, this, &PanelSettingsDBusClient::updateStatusBarCenterSpacing); + connect(m_interface, &OrgKdePlasmashellMobilePanelsInterface::navigationPanelHeightChanged, this, &PanelSettingsDBusClient::updateNavigationPanelHeight); + connect(m_interface, + &OrgKdePlasmashellMobilePanelsInterface::navigationPanelLeftPaddingChanged, + this, + &PanelSettingsDBusClient::updateNavigationPanelLeftPadding); + connect(m_interface, + &OrgKdePlasmashellMobilePanelsInterface::navigationPanelRightPaddingChanged, + this, + &PanelSettingsDBusClient::updateNavigationPanelRightPadding); + + // Initial state fetch + updateStatusBarHeight(); + updateStatusBarLeftPadding(); + updateStatusBarRightPadding(); + updateStatusBarCenterSpacing(); + updateNavigationPanelHeight(); + updateNavigationPanelLeftPadding(); + updateNavigationPanelRightPadding(); +} + +qreal PanelSettingsDBusClient::statusBarHeight() const +{ + return m_statusBarHeight; +} + +qreal PanelSettingsDBusClient::statusBarLeftPadding() const +{ + return m_statusBarLeftPadding; +} + +qreal PanelSettingsDBusClient::statusBarRightPadding() const +{ + return m_statusBarRightPadding; +} + +qreal PanelSettingsDBusClient::statusBarCenterSpacing() const +{ + return m_statusBarCenterSpacing; +} + +qreal PanelSettingsDBusClient::navigationPanelHeight() const +{ + return m_navigationPanelHeight; +} + +qreal PanelSettingsDBusClient::navigationPanelLeftPadding() const +{ + return m_navigationPanelLeftPadding; +} + +qreal PanelSettingsDBusClient::navigationPanelRightPadding() const +{ + return m_navigationPanelRightPadding; +} + +void PanelSettingsDBusClient::updateStatusBarHeight() +{ + auto reply = m_interface->statusBarHeight(); + auto watcher = new QDBusPendingCallWatcher(reply, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { + QDBusPendingReply reply = *watcher; + qreal statusBarHeight = reply.argumentAt<0>(); + + if (statusBarHeight != m_statusBarHeight) { + m_statusBarHeight = statusBarHeight; + Q_EMIT statusBarHeightChanged(); + } + + watcher->deleteLater(); + }); +} + +void PanelSettingsDBusClient::updateStatusBarLeftPadding() +{ + auto reply = m_interface->statusBarLeftPadding(); + auto watcher = new QDBusPendingCallWatcher(reply, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { + QDBusPendingReply reply = *watcher; + qreal statusBarLeftPadding = reply.argumentAt<0>(); + + if (statusBarLeftPadding != m_statusBarLeftPadding) { + m_statusBarLeftPadding = statusBarLeftPadding; + Q_EMIT statusBarLeftPaddingChanged(); + } + + watcher->deleteLater(); + }); +} + +void PanelSettingsDBusClient::updateStatusBarRightPadding() +{ + auto reply = m_interface->statusBarRightPadding(); + auto watcher = new QDBusPendingCallWatcher(reply, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { + QDBusPendingReply reply = *watcher; + qreal statusBarRightPadding = reply.argumentAt<0>(); + + if (statusBarRightPadding != m_statusBarRightPadding) { + m_statusBarRightPadding = statusBarRightPadding; + Q_EMIT statusBarRightPaddingChanged(); + } + + watcher->deleteLater(); + }); +} + +void PanelSettingsDBusClient::updateStatusBarCenterSpacing() +{ + auto reply = m_interface->statusBarCenterSpacing(); + auto watcher = new QDBusPendingCallWatcher(reply, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { + QDBusPendingReply reply = *watcher; + qreal statusBarCenterSpacing = reply.argumentAt<0>(); + + if (statusBarCenterSpacing != m_statusBarCenterSpacing) { + m_statusBarCenterSpacing = statusBarCenterSpacing; + Q_EMIT statusBarCenterSpacingChanged(); + } + + watcher->deleteLater(); + }); +} + +void PanelSettingsDBusClient::updateNavigationPanelHeight() +{ + auto reply = m_interface->navigationPanelHeight(); + auto watcher = new QDBusPendingCallWatcher(reply, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { + QDBusPendingReply reply = *watcher; + qreal navigationPanelHeight = reply.argumentAt<0>(); + + if (navigationPanelHeight != m_navigationPanelHeight) { + m_navigationPanelHeight = navigationPanelHeight; + Q_EMIT navigationPanelHeightChanged(); + } + + watcher->deleteLater(); + }); +} + +void PanelSettingsDBusClient::updateNavigationPanelLeftPadding() +{ + auto reply = m_interface->navigationPanelLeftPadding(); + auto watcher = new QDBusPendingCallWatcher(reply, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { + QDBusPendingReply reply = *watcher; + qreal navigationPanelLeftPadding = reply.argumentAt<0>(); + + if (navigationPanelLeftPadding != m_navigationPanelLeftPadding) { + m_navigationPanelLeftPadding = navigationPanelLeftPadding; + Q_EMIT navigationPanelLeftPaddingChanged(); + } + + watcher->deleteLater(); + }); +} + +void PanelSettingsDBusClient::updateNavigationPanelRightPadding() +{ + auto reply = m_interface->navigationPanelRightPadding(); + auto watcher = new QDBusPendingCallWatcher(reply, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { + QDBusPendingReply reply = *watcher; + qreal navigationPanelRightPadding = reply.argumentAt<0>(); + + if (navigationPanelRightPadding != m_navigationPanelRightPadding) { + m_navigationPanelRightPadding = navigationPanelRightPadding; + Q_EMIT navigationPanelRightPaddingChanged(); + } + + watcher->deleteLater(); + }); +} diff --git a/components/mobileshellstate/panelsettingsdbusclient.h b/components/mobileshellstate/panelsettingsdbusclient.h new file mode 100644 index 00000000..d9eb8b3d --- /dev/null +++ b/components/mobileshellstate/panelsettingsdbusclient.h @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "plasmashellmobilepanelsinterface.h" + +#include +#include +#include +#include + +class PanelSettingsDBusClient : public QObject +{ + Q_OBJECT + QML_ELEMENT + + // Client must set the screen they want to get details for + Q_PROPERTY(QString screenName READ screenName WRITE setScreenName NOTIFY screenNameChanged) + + Q_PROPERTY(qreal statusBarHeight READ statusBarHeight NOTIFY statusBarHeightChanged) + Q_PROPERTY(qreal statusBarLeftPadding READ statusBarLeftPadding NOTIFY statusBarLeftPaddingChanged) + Q_PROPERTY(qreal statusBarRightPadding READ statusBarRightPadding NOTIFY statusBarRightPaddingChanged) + Q_PROPERTY(qreal statusBarCenterSpacing READ statusBarCenterSpacing NOTIFY statusBarCenterSpacingChanged) + + Q_PROPERTY(qreal navigationPanelHeight READ navigationPanelHeight NOTIFY navigationPanelHeightChanged) + Q_PROPERTY(qreal navigationPanelLeftPadding READ navigationPanelLeftPadding NOTIFY navigationPanelLeftPaddingChanged) + Q_PROPERTY(qreal navigationPanelRightPadding READ navigationPanelRightPadding NOTIFY navigationPanelRightPaddingChanged) + +public: + explicit PanelSettingsDBusClient(QObject *parent = nullptr); + + void connectToDBus(); + + QString screenName() const; + void setScreenName(const QString &screenName); + + qreal statusBarHeight() const; + qreal statusBarLeftPadding() const; + qreal statusBarRightPadding() const; + qreal statusBarCenterSpacing() const; + + qreal navigationPanelHeight() const; + qreal navigationPanelLeftPadding() const; + qreal navigationPanelRightPadding() const; + +Q_SIGNALS: + void screenNameChanged(); + void statusBarHeightChanged(); + void statusBarLeftPaddingChanged(); + void statusBarRightPaddingChanged(); + void statusBarCenterSpacingChanged(); + void navigationPanelHeightChanged(); + void navigationPanelLeftPaddingChanged(); + void navigationPanelRightPaddingChanged(); + +private Q_SLOTS: + void updateStatusBarHeight(); + void updateStatusBarLeftPadding(); + void updateStatusBarRightPadding(); + void updateStatusBarCenterSpacing(); + void updateNavigationPanelHeight(); + void updateNavigationPanelLeftPadding(); + void updateNavigationPanelRightPadding(); + +private: + void connectSignals(); + + OrgKdePlasmashellMobilePanelsInterface *m_interface; + QDBusServiceWatcher *m_watcher; + + QString m_screenName; + + qreal m_statusBarHeight = -1; + qreal m_statusBarLeftPadding = 0; + qreal m_statusBarRightPadding = 0; + qreal m_statusBarCenterSpacing = 0; + qreal m_navigationPanelHeight = -1; + qreal m_navigationPanelLeftPadding = 0; + qreal m_navigationPanelRightPadding = 0; + + bool m_connected = false; +}; diff --git a/components/mobileshellstate/panelsettingsdbusobjectmanager.cpp b/components/mobileshellstate/panelsettingsdbusobjectmanager.cpp new file mode 100644 index 00000000..7db52ce8 --- /dev/null +++ b/components/mobileshellstate/panelsettingsdbusobjectmanager.cpp @@ -0,0 +1,268 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "panelsettingsdbusobjectmanager.h" +#include "panelsadaptor.h" + +#include +#include +#include + +using namespace Qt::Literals::StringLiterals; + +const QString CONFIG_FILE = u"plasmamobilerc"_s; +const QString PANELS_CONFIG_GROUP = u"Panels"_s; + +// Orientations +const QString TOP_CONFIG_GROUP = u"WhenOnTop"_s; +const QString LEFT_CONFIG_GROUP = u"WhenOnLeft"_s; +const QString RIGHT_CONFIG_GROUP = u"WhenOnRight"_s; +const QString BOTTOM_CONFIG_GROUP = u"WhenOnBottom"_s; + +QString mapRotationToTopPosition(KScreen::Output::Rotation rotation) +{ + switch (rotation) { + case KScreen::Output::Rotation::Left: + return RIGHT_CONFIG_GROUP; + case KScreen::Output::Rotation::Inverted: + return BOTTOM_CONFIG_GROUP; + case KScreen::Output::Rotation::Right: + return LEFT_CONFIG_GROUP; + default: + return TOP_CONFIG_GROUP; + } +} + +QString mapRotationToBottomPosition(KScreen::Output::Rotation rotation) +{ + switch (rotation) { + case KScreen::Output::Rotation::Left: + return LEFT_CONFIG_GROUP; + case KScreen::Output::Rotation::Inverted: + return TOP_CONFIG_GROUP; + case KScreen::Output::Rotation::Right: + return RIGHT_CONFIG_GROUP; + default: + return BOTTOM_CONFIG_GROUP; + } +} + +PanelSettingsDBusObjectManager::PanelSettingsDBusObjectManager(QObject *parent) + : QObject{parent} +{ +} + +void PanelSettingsDBusObjectManager::registerObjects() +{ + if (m_initialized) { + return; + } + + m_initialized = true; + + // Fetch kscreen config + connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, [this](auto *op) { + m_kscreenConfig = qobject_cast(op)->config(); + if (!m_kscreenConfig) { + return; + } + + KScreen::ConfigMonitor::instance()->addConfig(m_kscreenConfig); + + // Listen to all new screens and create a new output + connect(m_kscreenConfig.data(), &KScreen::Config::outputAdded, this, [this](const auto &output) { + if (!output) { + return; + } + + auto *obj = new PanelSettingsDBusObject{this}; + obj->registerObject(output); + m_dbusObjects.push_back(obj); + }); + + // Remove the corresponding object when a screen is removed + connect(m_kscreenConfig.data(), &KScreen::Config::outputRemoved, this, [this](const auto outputId) { + for (int i = 0; i < m_dbusObjects.size(); ++i) { + if (m_dbusObjects[i]->outputId() == outputId) { + m_dbusObjects[i]->deleteLater(); + m_dbusObjects.remove(i); + break; + } + } + }); + + // Add all current screens as dbus objects + for (KScreen::OutputPtr output : m_kscreenConfig->outputs()) { + if (!output) { + continue; + } + + auto *obj = new PanelSettingsDBusObject{this}; + obj->registerObject(output); + m_dbusObjects.push_back(obj); + } + }); +} + +PanelSettingsDBusObject::PanelSettingsDBusObject(QObject *parent) + : QObject{parent} + , m_config{KSharedConfig::openConfig(CONFIG_FILE)} +{ + // Listen to config changes and reload + m_configWatcher = KConfigWatcher::create(m_config); + connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void { + Q_UNUSED(names) + Q_UNUSED(group) + updateFields(); + }); +} + +void PanelSettingsDBusObject::registerObject(KScreen::OutputPtr output) +{ + m_output = output; + + if (!m_output) { + return; + } + m_outputId = m_output->id(); + m_outputName = m_output->name(); + + // Listen to when the rotation or scale changes to refresh the fields + connect(m_output.data(), &KScreen::Output::rotationChanged, this, &PanelSettingsDBusObject::updateFields); + connect(m_output.data(), &KScreen::Output::scaleChanged, this, &PanelSettingsDBusObject::updateFields); + updateFields(); + + new PlasmashellMobilePanelsAdaptor{this}; + + // Register the screen DBus object + QString objectName = m_output->name().replace("-", ""); // DBus doesn't allow dashes + QDBusConnection::sessionBus().registerObject(QStringLiteral("/Mobile/Panels/") + objectName, this); +} + +void PanelSettingsDBusObject::updateFields() +{ + if (!m_output) { + return; + } + + auto group = KConfigGroup{m_config, PANELS_CONFIG_GROUP}; + auto topGroup = KConfigGroup{&group, mapRotationToTopPosition(m_output->rotation())}; + auto bottomGroup = KConfigGroup{&group, mapRotationToBottomPosition(m_output->rotation())}; + + // Divide values by the display's scale for scaling independent sizing + setStatusBarHeight(topGroup.readEntry(u"statusBarHeight"_s, -1.0) / m_output->scale()); + setStatusBarLeftPadding(topGroup.readEntry(u"statusBarLeftPadding"_s, 0.0) / m_output->scale()); + setStatusBarRightPadding(topGroup.readEntry(u"statusBarRightPadding"_s, 0.0) / m_output->scale()); + setStatusBarCenterSpacing(topGroup.readEntry(u"statusBarCenterSpacing"_s, 0.0) / m_output->scale()); + setNavigationPanelHeight(bottomGroup.readEntry(u"navigationPanelHeight"_s, -1.0) / m_output->scale()); + setNavigationPanelLeftPadding(bottomGroup.readEntry(u"navigationPanelLeftPadding"_s, 0.0) / m_output->scale()); + setNavigationPanelRightPadding(bottomGroup.readEntry(u"navigationPanelRightPadding"_s, 0.0) / m_output->scale()); +} + +int PanelSettingsDBusObject::outputId() const +{ + return m_outputId; +} + +QString PanelSettingsDBusObject::outputName() const +{ + return m_outputName; +} + +qreal PanelSettingsDBusObject::statusBarHeight() const +{ + return m_statusBarHeight; +} + +void PanelSettingsDBusObject::setStatusBarHeight(qreal statusBarHeight) +{ + if (statusBarHeight == m_statusBarHeight) { + return; + } + m_statusBarHeight = statusBarHeight; + Q_EMIT statusBarHeightChanged(); +} + +qreal PanelSettingsDBusObject::statusBarLeftPadding() const +{ + return m_statusBarLeftPadding; +} + +void PanelSettingsDBusObject::setStatusBarLeftPadding(qreal statusBarLeftPadding) +{ + if (statusBarLeftPadding == m_statusBarLeftPadding) { + return; + } + m_statusBarLeftPadding = statusBarLeftPadding; + Q_EMIT statusBarLeftPaddingChanged(); +} + +qreal PanelSettingsDBusObject::statusBarRightPadding() const +{ + return m_statusBarRightPadding; +} + +void PanelSettingsDBusObject::setStatusBarRightPadding(qreal statusBarRightPadding) +{ + if (statusBarRightPadding == m_statusBarRightPadding) { + return; + } + m_statusBarRightPadding = statusBarRightPadding; + Q_EMIT statusBarRightPaddingChanged(); +} + +qreal PanelSettingsDBusObject::statusBarCenterSpacing() const +{ + return m_statusBarCenterSpacing; +} + +void PanelSettingsDBusObject::setStatusBarCenterSpacing(qreal statusBarCenterSpacing) +{ + if (statusBarCenterSpacing == m_statusBarCenterSpacing) { + return; + } + m_statusBarCenterSpacing = statusBarCenterSpacing; + Q_EMIT statusBarCenterSpacingChanged(); +} + +qreal PanelSettingsDBusObject::navigationPanelHeight() const +{ + return m_navigationPanelHeight; +} + +void PanelSettingsDBusObject::setNavigationPanelHeight(qreal navigationPanelHeight) +{ + if (navigationPanelHeight == m_navigationPanelHeight) { + return; + } + m_navigationPanelHeight = navigationPanelHeight; + Q_EMIT navigationPanelHeightChanged(); +} + +qreal PanelSettingsDBusObject::navigationPanelLeftPadding() const +{ + return m_navigationPanelLeftPadding; +} + +void PanelSettingsDBusObject::setNavigationPanelLeftPadding(qreal navigationPanelLeftPadding) +{ + if (navigationPanelLeftPadding == m_navigationPanelLeftPadding) { + return; + } + m_navigationPanelLeftPadding = navigationPanelLeftPadding; + Q_EMIT navigationPanelLeftPaddingChanged(); +} + +qreal PanelSettingsDBusObject::navigationPanelRightPadding() const +{ + return m_navigationPanelRightPadding; +} + +void PanelSettingsDBusObject::setNavigationPanelRightPadding(qreal navigationPanelRightPadding) +{ + if (navigationPanelRightPadding == m_navigationPanelRightPadding) { + return; + } + m_navigationPanelRightPadding = navigationPanelRightPadding; + Q_EMIT navigationPanelRightPaddingChanged(); +} diff --git a/components/mobileshellstate/panelsettingsdbusobjectmanager.h b/components/mobileshellstate/panelsettingsdbusobjectmanager.h new file mode 100644 index 00000000..5b375e6d --- /dev/null +++ b/components/mobileshellstate/panelsettingsdbusobjectmanager.h @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include + +class PanelSettingsDBusObject; + +class PanelSettingsDBusObjectManager : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + PanelSettingsDBusObjectManager(QObject *parent = nullptr); + + // called by QML + Q_INVOKABLE void registerObjects(); + +private: + bool m_initialized = false; + QList m_dbusObjects; + KScreen::ConfigPtr m_kscreenConfig{nullptr}; +}; + +class PanelSettingsDBusObject : public QObject +{ + Q_OBJECT + // HACK: org.kde.plasmashell prefix seems to bug out and not compile with the qt macro, use this for now + Q_CLASSINFO("D-Bus Interface", "org.kde.plasmashellMobilePanels") + +public: + PanelSettingsDBusObject(QObject *parent = nullptr); + + void registerObject(KScreen::OutputPtr output); + + int outputId() const; + QString outputName() const; + +Q_SIGNALS: + Q_SCRIPTABLE void statusBarHeightChanged(); + Q_SCRIPTABLE void statusBarLeftPaddingChanged(); + Q_SCRIPTABLE void statusBarRightPaddingChanged(); + Q_SCRIPTABLE void statusBarCenterSpacingChanged(); + Q_SCRIPTABLE void navigationPanelHeightChanged(); + Q_SCRIPTABLE void navigationPanelLeftPaddingChanged(); + Q_SCRIPTABLE void navigationPanelRightPaddingChanged(); + +public Q_SLOTS: + Q_SCRIPTABLE qreal statusBarHeight() const; + Q_SCRIPTABLE qreal statusBarLeftPadding() const; + Q_SCRIPTABLE qreal statusBarRightPadding() const; + Q_SCRIPTABLE qreal statusBarCenterSpacing() const; + + Q_SCRIPTABLE qreal navigationPanelHeight() const; + Q_SCRIPTABLE qreal navigationPanelLeftPadding() const; + Q_SCRIPTABLE qreal navigationPanelRightPadding() const; + +private: + void updateFields(); + + void setStatusBarHeight(qreal statusBarHeight); + void setStatusBarLeftPadding(qreal statusBarLeftPadding); + void setStatusBarRightPadding(qreal statusBarRightPadding); + void setStatusBarCenterSpacing(qreal statusBarCenterSpacing); + + void setNavigationPanelHeight(qreal navigationPanelHeight); + void setNavigationPanelLeftPadding(qreal navigationPanelLeftPadding); + void setNavigationPanelRightPadding(qreal navigationPanelRightPadding); + + int m_outputId = -1; + QString m_outputName; + + qreal m_statusBarHeight = -1; + qreal m_statusBarLeftPadding = 0; + qreal m_statusBarRightPadding = 0; + qreal m_statusBarCenterSpacing = 0; + qreal m_navigationPanelHeight = -1; + qreal m_navigationPanelLeftPadding = 0; + qreal m_navigationPanelRightPadding = 0; + + KSharedConfig::Ptr m_config; + KConfigWatcher::Ptr m_configWatcher; + KScreen::OutputPtr m_output; +}; diff --git a/components/quicksettingsplugin/quicksettingsconfig.cpp b/components/quicksettingsplugin/quicksettingsconfig.cpp index 1aba34da..e40a378b 100644 --- a/components/quicksettingsplugin/quicksettingsconfig.cpp +++ b/components/quicksettingsplugin/quicksettingsconfig.cpp @@ -13,7 +13,7 @@ const QString QUICKSETTINGS_CONFIG_GROUP = QStringLiteral("QuickSettings"); QuickSettingsConfig::QuickSettingsConfig(QObject *parent) : QObject{parent} - , m_config{KSharedConfig::openConfig(CONFIG_FILE, KConfig::SimpleConfig)} + , m_config{KSharedConfig::openConfig(CONFIG_FILE)} { m_configWatcher = KConfigWatcher::create(m_config); diff --git a/components/shellsettingsplugin/CMakeLists.txt b/components/shellsettingsplugin/CMakeLists.txt index 1074bf51..504a22df 100644 --- a/components/shellsettingsplugin/CMakeLists.txt +++ b/components/shellsettingsplugin/CMakeLists.txt @@ -2,7 +2,10 @@ # SPDX-License-Identifier: GPL-2.0-or-later ecm_add_qml_module(shellsettingsplugin URI org.kde.plasma.private.mobileshell.shellsettingsplugin GENERATE_PLUGIN_SOURCE) -target_sources(shellsettingsplugin PRIVATE kwinsettings.cpp mobileshellsettings.cpp) +target_sources(shellsettingsplugin PRIVATE + kwinsettings.cpp + mobileshellsettings.cpp +) target_link_libraries(shellsettingsplugin PRIVATE Qt::Qml @@ -14,6 +17,7 @@ target_link_libraries(shellsettingsplugin PRIVATE KF6::Package KF6::KIOGui KF6::JobWidgets + KF6::Screen ) ecm_finalize_qml_module(shellsettingsplugin) diff --git a/components/shellsettingsplugin/kwinsettings.cpp b/components/shellsettingsplugin/kwinsettings.cpp index e6c2aa40..def45ae3 100644 --- a/components/shellsettingsplugin/kwinsettings.cpp +++ b/components/shellsettingsplugin/kwinsettings.cpp @@ -11,7 +11,7 @@ const QString WAYLAND_CONFIG_GROUP = QStringLiteral("Wayland"); KWinSettings::KWinSettings(QObject *parent) : QObject{parent} - , m_config{KSharedConfig::openConfig(CONFIG_FILE, KConfig::SimpleConfig)} + , m_config{KSharedConfig::openConfig(CONFIG_FILE)} { m_configWatcher = KConfigWatcher::create(m_config); connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void { diff --git a/components/shellsettingsplugin/mobileshellsettings.cpp b/components/shellsettingsplugin/mobileshellsettings.cpp index 624f9077..8ce0ad5e 100644 --- a/components/shellsettingsplugin/mobileshellsettings.cpp +++ b/components/shellsettingsplugin/mobileshellsettings.cpp @@ -23,7 +23,7 @@ const QString QUICKSETTINGS_CONFIG_GROUP = QStringLiteral("QuickSettings"); MobileShellSettings::MobileShellSettings(QObject *parent) : QObject{parent} - , m_config{KSharedConfig::openConfig(CONFIG_FILE, KConfig::SimpleConfig)} + , m_config{KSharedConfig::openConfig(CONFIG_FILE)} { m_configWatcher = KConfigWatcher::create(m_config); connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void { diff --git a/components/wallpaperimageplugin/wallpaperplugin.cpp b/components/wallpaperimageplugin/wallpaperplugin.cpp index 8713683f..fe159e08 100644 --- a/components/wallpaperimageplugin/wallpaperplugin.cpp +++ b/components/wallpaperimageplugin/wallpaperplugin.cpp @@ -23,8 +23,8 @@ WallpaperPlugin::WallpaperPlugin(QObject *parent) : QObject{parent} , m_homescreenConfig{new QQmlPropertyMap{this}} , m_lockscreenConfig{new QQmlPropertyMap{this}} - , m_homescreenConfigFile{KSharedConfig::openConfig("plasma-org.kde.plasma.mobileshell-appletsrc", KConfig::SimpleConfig)} - , m_lockscreenConfigFile{KSharedConfig::openConfig("kscreenlockerrc", KConfig::SimpleConfig)} + , m_homescreenConfigFile{KSharedConfig::openConfig("plasma-org.kde.plasma.mobileshell-appletsrc")} + , m_lockscreenConfigFile{KSharedConfig::openConfig("kscreenlockerrc")} { m_lockscreenConfigWatcher = KConfigWatcher::create(m_lockscreenConfigFile); diff --git a/containments/panel/qml/main.qml b/containments/panel/qml/main.qml index fb6e0961..749b5ff3 100644 --- a/containments/panel/qml/main.qml +++ b/containments/panel/qml/main.qml @@ -64,11 +64,18 @@ ContainmentItem { } } + readonly property real panelHeight: MobileShell.Constants.topPanelHeight + onPanelHeightChanged: setWindowProperties() + function setWindowProperties() { if (root.panel) { root.panel.floating = false; root.panel.maximize(); // maximize first, then we can apply offsets (otherwise they are overridden) - root.panel.thickness = MobileShell.Constants.topPanelHeight; + + // HACK: set thickness twice, sometimes it doesn't set the first time?? + root.panel.thickness = root.panelHeight; + root.panel.thickness = root.panelHeight; + root.panel.visibilityMode = ShellSettings.Settings.autoHidePanelsEnabled ? 3 : 0; MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay) root.updateTouchArea(); @@ -86,18 +93,21 @@ ContainmentItem { } } + Connections { + target: root.panel + + function onThicknessChanged() { + if (root.panel.thickness !== root.panelHeight) { + root.panel.thickness = root.panelHeight; + } + } + } + // Overlay the panel over the lockscreen when brought up LockscreenOverlay { window: root.Window.window } - // Enforce thickness of panel - Binding { - target: panel // assumed to be plasma-workspace "PanelView" component - property: "thickness" - value: MobileShell.Constants.topPanelHeight - } - Connections { target: ShellSettings.Settings diff --git a/containments/taskpanel/qml/NavigationPanelComponent.qml b/containments/taskpanel/qml/NavigationPanelComponent.qml index a2879488..2dd16f3e 100644 --- a/containments/taskpanel/qml/NavigationPanelComponent.qml +++ b/containments/taskpanel/qml/NavigationPanelComponent.qml @@ -30,6 +30,14 @@ MobileShell.NavigationPanel { foregroundColorGroup: opaqueBar ? Kirigami.Theme.Window : Kirigami.Theme.Complementary shadow: !opaqueBar + MobileShellState.PanelSettingsDBusClient { + id: panelSettings + screenName: Screen.name + } + + leftPadding: panelSettings.navigationPanelLeftPadding + rightPadding: panelSettings.navigationPanelRightPadding + TaskManager.VirtualDesktopInfo { id: virtualDesktopInfo } @@ -61,7 +69,7 @@ MobileShell.NavigationPanel { enabled: true iconSource: "mobile-task-switcher" - iconSizeFactor: 0.75 + shrinkSize: 4 onTriggered: { Plasmoid.triggerTaskSwitcher(); @@ -74,7 +82,6 @@ MobileShell.NavigationPanel { enabled: true iconSource: "start-here-kde" - iconSizeFactor: 1 onTriggered: { MobileShellState.ShellDBusClient.openHomeScreen(); @@ -88,7 +95,7 @@ MobileShell.NavigationPanel { enabled: Keyboards.KWinVirtualKeyboard.visible || WindowPlugin.WindowUtil.hasCloseableActiveWindow iconSource: Keyboards.KWinVirtualKeyboard.visible ? "go-down-symbolic" : "mobile-close-app" // mobile-close-app (from plasma-frameworks) seems to have fewer margins than icons from breeze-icons - iconSizeFactor: Keyboards.KWinVirtualKeyboard.visible ? 1 : 0.75 + shrinkSize: Keyboards.KWinVirtualKeyboard.visible ? 0 : 4 onTriggered: { if (Keyboards.KWinVirtualKeyboard.visible) { @@ -108,7 +115,7 @@ MobileShell.NavigationPanel { visible: RotationPlugin.RotationUtil.showRotationButton enabled: true iconSource: "rotation-allowed-symbolic" - iconSizeFactor: 0.75 + shrinkSize: 4 onTriggered: { RotationPlugin.RotationUtil.rotateToSuggestedRotation(); @@ -121,7 +128,7 @@ MobileShell.NavigationPanel { (Keyboards.KWinVirtualKeyboard.available && !Keyboards.KWinVirtualKeyboard.activeClientSupportsTextInput) enabled: true iconSource: "input-keyboard-virtual-symbolic" - iconSizeFactor: 0.75 + shrinkSize: 4 onTriggered: { if (Keyboards.KWinVirtualKeyboard.active) { diff --git a/containments/taskpanel/qml/main.qml b/containments/taskpanel/qml/main.qml index 243eb821..ecaa9115 100644 --- a/containments/taskpanel/qml/main.qml +++ b/containments/taskpanel/qml/main.qml @@ -39,6 +39,7 @@ ContainmentItem { readonly property bool inLandscape: MobileShell.Constants.navigationPanelOnSide(Screen.width, Screen.height) readonly property real navigationPanelHeight: MobileShell.Constants.navigationPanelThickness + onNavigationPanelHeightChanged: setWindowProperties() readonly property real intendedWindowThickness: navigationPanelHeight readonly property real intendedWindowLength: inLandscape ? Screen.height : Screen.width diff --git a/devices/CMakeLists.txt b/devices/CMakeLists.txt new file mode 100644 index 00000000..dc3fb880 --- /dev/null +++ b/devices/CMakeLists.txt @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2025 Devin Lin +# SPDX-License-Identifier: BSD-2-Clause + +install(DIRECTORY configs/ DESTINATION ${KDE_INSTALL_DATADIR}/plasma-mobile-device-presets) diff --git a/devices/README.md b/devices/README.md new file mode 100644 index 00000000..455b58b7 --- /dev/null +++ b/devices/README.md @@ -0,0 +1,32 @@ + + +This folder is where device-specific information is set. + +### Usage as a distro + +As a distribution, you can ship the device preset with the image by installing a file. + +In `/etc/xdg/plasmamobilerc`, write: + +```toml +[Device] +device=oneplus-enchilada # replace with the device id +``` + +This should be a file name that exists in `/usr/share/plasma-mobile-device-presets` (which are the files in the `configs` folder). + +### Adding a new device config + +See [spec.conf](spec.conf) for more details on the specification. If a setting is omitted in the file, then the system will use the default value. + +Add a new config in [configs](configs) with the device id. + +In order to test your changes, install the file to `/usr/share/plasma-mobile-device-presets/your-device-id.conf`. Then run envmanager to apply the changes to the running system: + +``` +$ plasma-mobile-envmanager --apply-settings +$ plasmashell --replace +``` diff --git a/devices/configs/default.conf b/devices/configs/default.conf new file mode 100644 index 00000000..e525d3bb --- /dev/null +++ b/devices/configs/default.conf @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 Devin Lin +# SPDX-License-Identifier: GPL-2.0-or-later + +[Device] +name=Default diff --git a/devices/configs/google-sargo.conf b/devices/configs/google-sargo.conf new file mode 100644 index 00000000..610096bf --- /dev/null +++ b/devices/configs/google-sargo.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2025 Devin Lin +# SPDX-License-Identifier: GPL-2.0-or-later + +[Device] +name=Pixel 3a + +# No tweaks needed diff --git a/devices/configs/oneplus-enchilada.conf b/devices/configs/oneplus-enchilada.conf new file mode 100644 index 00000000..58e14709 --- /dev/null +++ b/devices/configs/oneplus-enchilada.conf @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2025 Devin Lin +# SPDX-License-Identifier: GPL-2.0-or-later + +[Device] +name=OnePlus 6 + +# Match panel heights with the screen's curved area + +[Panels] +statusBarHeight=80 +leftPadding=12 +rightPadding=12 + +[Panels][Top] +centerSpacing=64 diff --git a/devices/spec.conf b/devices/spec.conf new file mode 100644 index 00000000..585f41ba --- /dev/null +++ b/devices/spec.conf @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: 2025 Devin Lin +# SPDX-License-Identifier: GPL-2.0-or-later + +[Device] +# The name of the device. +name=My Phone + +# Dimensions for a panel (fallback for any of the options below) +[Panels] +statusBarHeight= +navigationPanelHeight= +centerSpacing= +leftPadding= +rightPadding= + +# Dimensions for a panel if it is mounted on the top of the device +[Panels][Top] +statusBarHeight= # The status bar height in pixels if it is mounted on the top side of the device (from its natural orientation). +navigationPanelHeight= # The navigation bar height in pixels if it is mounted on the top side of the device (from its natural orientation). +centerSpacing= # The amount of space in pixels to reserve in the center for panels mounted on the top side of the device (for notches, punch holes). +leftPadding= # The left padding in pixels of panels mounted on the top side of the device (from its natural orientation). +rightPadding= # The right padding in pixels of panels mounted on the top side of the device (from its natural orientation). + +# Dimensions for a panel if it is mounted on the bottom of the device +[Panels][Bottom] +statusBarHeight= +navigationPanelHeight= +centerSpacing= +leftPadding= +rightPadding= + +# Dimensions for a panel if it is mounted on the left of the device (landscape) +[Panels][Left] +statusBarHeight= +navigationPanelHeight= +centerSpacing= +leftPadding= +rightPadding= + +# Dimensions for a panel if it is mounted on the right of the device (landscape) +[Panels][Right] +statusBarHeight= +navigationPanelHeight= +centerSpacing= +leftPadding= +rightPadding= + diff --git a/envmanager/CMakeLists.txt b/envmanager/CMakeLists.txt index fd0a255d..180145c5 100644 --- a/envmanager/CMakeLists.txt +++ b/envmanager/CMakeLists.txt @@ -5,6 +5,7 @@ set(plasma-mobile-envmanager_SRCS main.cpp settings.cpp utils.cpp + devicepresets.cpp config.h ) diff --git a/envmanager/devicepresets.cpp b/envmanager/devicepresets.cpp new file mode 100644 index 00000000..3117e380 --- /dev/null +++ b/envmanager/devicepresets.cpp @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "devicepresets.h" + +#include +#include +#include +#include + +using namespace Qt::Literals::StringLiterals; + +const QString GENERAL_CONFIG_FILE = u"plasmamobilerc"_s; +const QString WRITE_TO_CONFIG_FILE = u"plasma-mobile/plasmamobilerc"_s; +const QString DEVICE_CONFIG_GROUP = u"Device"_s; + +DevicePresets::DevicePresets(QObject *parent) + : QObject{parent} + , m_mobileConfig{KSharedConfig::openConfig(WRITE_TO_CONFIG_FILE, KConfig::SimpleConfig)} +{ +} + +void setKey(KConfigGroup &fallbackGroup, KConfigGroup &fromGroup, KConfigGroup &toGroup, QString fromKey, QString toKey) +{ + if (fromGroup.hasKey(fromKey)) { + toGroup.writeEntry(toKey, fromGroup.readEntry(fromKey), KConfigGroup::Notify); + } else if (fallbackGroup.hasKey(fromKey)) { + toGroup.writeEntry(toKey, fallbackGroup.readEntry(fromKey), KConfigGroup::Notify); + } else { + toGroup.deleteEntry(toKey, KConfigGroup::Notify); + } +} + +void DevicePresets::initialize() +{ + // Open mobile config to read from all locations (/etc/xdg/plasmamobilerc, ~/.config/plasmamobilerc, etc.) + auto mobileConfig = KSharedConfig::openConfig(GENERAL_CONFIG_FILE); + auto deviceGroup = KConfigGroup{mobileConfig, DEVICE_CONFIG_GROUP}; + + // Read device id + const QString device = deviceGroup.readEntry(u"device"_s, {}); + + QString presetFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, u"plasma-mobile-device-presets/"_s + device + ".conf"); + if (!QFile{presetFile}.exists()) { + presetFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, u"plasma-mobile-device-presets/default.conf"_s); + if (!QFile{presetFile}.exists()) { + qWarning() << "Failed to find any device preset file"; + return; + } + } + + // Open preset file /usr/share/plasma-mobile-device-presets/device.conf + auto presetConfig = KSharedConfig::openConfig(QFileInfo{presetFile}.absoluteFilePath(), KConfig::SimpleConfig); + if (!presetConfig) { + return; + } + + // Write presets to ~/.config/plasma-mobile/plasmamobilerc + // This is then read by components/mobileshellstate (PanelSettingsDBusObjectManager) + auto presetPanelsGroup = KConfigGroup{presetConfig, u"Panels"_s}; + auto mobilePanelsGroup = KConfigGroup{m_mobileConfig, u"Panels"_s}; + + // + QList> groupPairs = {{u"Top"_s, u"WhenOnTop"_s}, + {u"Bottom"_s, u"WhenOnBottom"_s}, + {u"Left"_s, u"WhenOnLeft"_s}, + {u"Right"_s, u"WhenOnRight"_s}}; + + // Convert preset file settings into plasmamobilerc ones + for (const auto &p : groupPairs) { + auto presetGroup = KConfigGroup{&presetPanelsGroup, p.first}; + auto writeGroup = KConfigGroup{&mobilePanelsGroup, p.second}; + + // Try to read the value from presetGroup first (ex. [Panels][Top] statusBarHeight=...) + // If it doesn't exist, then fallback to parent group (ex. [Panels] statusBarHeight=...) + setKey(presetPanelsGroup, presetGroup, writeGroup, u"statusBarHeight"_s, u"statusBarHeight"_s); + setKey(presetPanelsGroup, presetGroup, writeGroup, u"navigationPanelHeight"_s, u"navigationPanelHeight"_s); + setKey(presetPanelsGroup, presetGroup, writeGroup, u"leftPadding"_s, u"statusBarLeftPadding"_s); + setKey(presetPanelsGroup, presetGroup, writeGroup, u"leftPadding"_s, u"navigationPanelLeftPadding"_s); + setKey(presetPanelsGroup, presetGroup, writeGroup, u"rightPadding"_s, u"statusBarRightPadding"_s); + setKey(presetPanelsGroup, presetGroup, writeGroup, u"rightPadding"_s, u"navigationPanelRightPadding"_s); + setKey(presetPanelsGroup, presetGroup, writeGroup, u"centerSpacing"_s, u"statusBarCenterSpacing"_s); + + writeGroup.sync(); + } + + m_mobileConfig->sync(); +} diff --git a/envmanager/devicepresets.h b/envmanager/devicepresets.h new file mode 100644 index 00000000..0d5c57e9 --- /dev/null +++ b/envmanager/devicepresets.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2025 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include +#include + +class DevicePresets : public QObject +{ + Q_OBJECT + +public: + DevicePresets(QObject *parent = nullptr); + + void initialize(); + +private: + KSharedConfig::Ptr m_mobileConfig; +}; diff --git a/envmanager/settings.cpp b/envmanager/settings.cpp index 24fb11d4..5eda53bf 100644 --- a/envmanager/settings.cpp +++ b/envmanager/settings.cpp @@ -4,6 +4,7 @@ #include "settings.h" #include "config.h" +#include "devicepresets.h" #include "utils.h" #include @@ -115,8 +116,12 @@ void Settings::applyMobileConfiguration() setOptionsImmutable(true, MOBILE_KSMSERVERRC_FILE, KSMSERVER_SETTINGS); } - // save our changes + // Save our changes m_mobileConfig->sync(); + + // Setup device configs + DevicePresets devicePresets; + devicePresets.initialize(); } void Settings::writeKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap> &settings) diff --git a/initialstart/settings.cpp b/initialstart/settings.cpp index 138f840a..e415cd3f 100644 --- a/initialstart/settings.cpp +++ b/initialstart/settings.cpp @@ -11,7 +11,7 @@ const QString INITIAL_START_CONFIG_GROUP = QStringLiteral("InitialStart"); Settings::Settings(QObject *parent) : QObject{parent} - , m_mobileConfig{KSharedConfig::openConfig(CONFIG_FILE, KConfig::SimpleConfig)} + , m_mobileConfig{KSharedConfig::openConfig(CONFIG_FILE)} , m_isMobilePlatform{KRuntimePlatform::runtimePlatform().contains(QStringLiteral("phone"))} { } diff --git a/kwin/mobiletaskswitcher/package/contents/ui/TaskSwitcher.qml b/kwin/mobiletaskswitcher/package/contents/ui/TaskSwitcher.qml index 13232d96..1abaa633 100644 --- a/kwin/mobiletaskswitcher/package/contents/ui/TaskSwitcher.qml +++ b/kwin/mobiletaskswitcher/package/contents/ui/TaskSwitcher.qml @@ -493,10 +493,18 @@ FocusScope { isVertical: MobileShell.Constants.navigationPanelOnSide(root.width, root.height) + MobileShellState.PanelSettingsDBusClient { + id: panelSettings + screenName: Screen.name + } + + leftPadding: panelSettings.navigationPanelLeftPadding + rightPadding: panelSettings.navigationPanelRightPadding + leftAction: MobileShell.NavigationPanelAction { enabled: true iconSource: "mobile-task-switcher" - iconSizeFactor: 0.75 + shrinkSize: 4 onTriggered: { if (taskList.count === 0) { @@ -519,7 +527,6 @@ FocusScope { middleAction: MobileShell.NavigationPanelAction { enabled: true iconSource: "start-here-kde" - iconSizeFactor: 1 onTriggered: root.hide() } @@ -527,7 +534,7 @@ FocusScope { rightAction: MobileShell.NavigationPanelAction { enabled: true iconSource: "mobile-close-app" - iconSizeFactor: 0.75 + shrinkSize: 4 onTriggered: { taskList.getTaskAt(root.state.currentTaskIndex).closeApp(); diff --git a/layout-templates/org.kde.plasma.mobile.defaultNavigationPanel/contents/layout.js b/layout-templates/org.kde.plasma.mobile.defaultNavigationPanel/contents/layout.js index 9050f41c..0a501f72 100644 --- a/layout-templates/org.kde.plasma.mobile.defaultNavigationPanel/contents/layout.js +++ b/layout-templates/org.kde.plasma.mobile.defaultNavigationPanel/contents/layout.js @@ -3,4 +3,3 @@ const bottomPanel = new Panel("org.kde.plasma.mobile.taskpanel") bottomPanel.location = "bottom"; -bottomPanel.height = 2 * gridUnit; diff --git a/layout-templates/org.kde.plasma.mobile.defaultStatusBar/contents/layout.js b/layout-templates/org.kde.plasma.mobile.defaultStatusBar/contents/layout.js index d9f14cfa..eeabcd2f 100644 --- a/layout-templates/org.kde.plasma.mobile.defaultStatusBar/contents/layout.js +++ b/layout-templates/org.kde.plasma.mobile.defaultStatusBar/contents/layout.js @@ -3,4 +3,3 @@ const panel = new Panel("org.kde.plasma.mobile.panel"); panel.location = "top"; -panel.height = 1.25 * gridUnit; // HACK: supposed to be gridUnit + smallSpacing, but it doesn't seem to give the correct number diff --git a/shell/contents/views/Desktop.qml b/shell/contents/views/Desktop.qml index f5578b98..fc4154f0 100644 --- a/shell/contents/views/Desktop.qml +++ b/shell/contents/views/Desktop.qml @@ -33,6 +33,7 @@ Rectangle { // HACK: we need to initialize the DBus server somewhere in plasmashell, it might as well be here... MobileShellState.ShellDBusObject.registerObject(); + MobileShellState.PanelSettingsDBusObjectManager.registerObjects(); // Initialize the volume osd, and volume keys. // Initialize notification popups.