From 0e4b1da9a1a0c4072d2bd4ef0efeb47e0d1fe19a Mon Sep 17 00:00:00 2001 From: Devin Lin Date: Sun, 19 Mar 2023 21:10:14 -0700 Subject: [PATCH] volumeosd: Extract out singleton so that it's only loaded once in plasmashell --- components/mobileshell/mobileshellplugin.cpp | 2 + .../qml/dataproviders/AudioInfo.qml | 34 +++++++ .../mobileshell/qml/volumeosd/VolumeOSD.qml | 2 +- .../qml/volumeosd/VolumeOSDProvider.qml | 70 +++++++++++++ .../qml/volumeosd/VolumeOSDProviderLoader.qml | 22 +++++ components/mobileshell/resources.qrc | 2 + components/mobileshellstate/CMakeLists.txt | 1 - .../mobileshellstateplugin.cpp | 7 +- .../mobileshellstate/qml/AudioProvider.qml | 98 ------------------- components/mobileshellstate/resources.qrc | 10 -- .../mobileshellstate/shelldbusclient.cpp | 6 ++ components/mobileshellstate/shelldbusclient.h | 2 + .../mobileshellstate/shelldbusobject.cpp | 22 ++++- components/mobileshellstate/shelldbusobject.h | 9 ++ .../folio/package/contents/ui/main.qml | 2 +- .../panel/package/contents/ui/main.qml | 12 ++- quicksettings/audio/contents/ui/main.qml | 7 +- shell/contents/views/Desktop.qml | 51 +++++----- 18 files changed, 209 insertions(+), 150 deletions(-) create mode 100644 components/mobileshell/qml/volumeosd/VolumeOSDProvider.qml create mode 100644 components/mobileshell/qml/volumeosd/VolumeOSDProviderLoader.qml delete mode 100644 components/mobileshellstate/qml/AudioProvider.qml delete mode 100644 components/mobileshellstate/resources.qrc diff --git a/components/mobileshell/mobileshellplugin.cpp b/components/mobileshell/mobileshellplugin.cpp index 97dc0e59..7bb7fc22 100644 --- a/components/mobileshell/mobileshellplugin.cpp +++ b/components/mobileshell/mobileshellplugin.cpp @@ -74,6 +74,8 @@ void MobileShellPlugin::registerTypes(const char *uri) // /volumeosd qmlRegisterType(resolvePath("volumeosd/VolumeOSD.qml"), uri, 1, 0, "VolumeOSD"); + qmlRegisterType(resolvePath("volumeosd/VolumeOSDProvider.qml"), uri, 1, 0, "VolumeOSDProvider"); + qmlRegisterSingletonType(resolvePath("volumeosd/VolumeOSDProviderLoader.qml"), uri, 1, 0, "VolumeOSDProviderLoader"); // /widgets qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget"); diff --git a/components/mobileshell/qml/dataproviders/AudioInfo.qml b/components/mobileshell/qml/dataproviders/AudioInfo.qml index 596926a7..b8a324c8 100644 --- a/components/mobileshell/qml/dataproviders/AudioInfo.qml +++ b/components/mobileshell/qml/dataproviders/AudioInfo.qml @@ -6,6 +6,8 @@ import QtQuick import org.kde.plasma.private.volume QtObject { + id: root + property SinkModel paSinkModel: SinkModel {} // whether the audio icon should be visible in the status bar @@ -25,6 +27,9 @@ QtObject { // step that increments when adjusting the volume readonly property int volumeStep: Math.round(5 * PulseAudio.NormalVolume / 100.0) + // The current audio volume (updated by connecting to sinks) + property int volumeValue + function isDummyOutput(output) { return output && output.name === dummyOutputName; } @@ -87,4 +92,33 @@ QtObject { } return icon; } + + // emitted when the volume changed, but not due to sink switching + signal volumeChanged() + + property var updateVolume: Connections { + target: root.paSinkModel ? (root.paSinkModel.preferredSink ? root.paSinkModel.preferredSink : null) : null + enabled: target !== null + + function onVolumeChanged() { + root.volumeValue = root.volumePercent(root.paSinkModel.preferredSink.volume, root.maxVolumeValue); + root.volumeChanged(); + } + + function onMutedChanged() { + root.volumeValue = root.paSinkModel.preferredSink.muted ? 0 : root.volumePercent(root.paSinkModel.preferredSink.volume, root.maxVolumeValue); + root.volumeChanged(); + } + } + + property var updateVolumeOnSinkChange: Connections { + target: root.paSinkModel ? root.paSinkModel : null + enabled: target !== null + + function onPreferredSinkChanged() { + if (root.paSinkModel.preferredSink) { + root.volumeValue = root.volumePercent(root.paSinkModel.preferredSink.volume, root.maxVolumeValue); + } + } + } } diff --git a/components/mobileshell/qml/volumeosd/VolumeOSD.qml b/components/mobileshell/qml/volumeosd/VolumeOSD.qml index 289b54e9..a8f4daf8 100644 --- a/components/mobileshell/qml/volumeosd/VolumeOSD.qml +++ b/components/mobileshell/qml/volumeosd/VolumeOSD.qml @@ -22,7 +22,7 @@ import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState import "../dataproviders" as DataProviders -Window { +NanoShell.FullScreenOverlay { id: window required property int volume diff --git a/components/mobileshell/qml/volumeosd/VolumeOSDProvider.qml b/components/mobileshell/qml/volumeosd/VolumeOSDProvider.qml new file mode 100644 index 00000000..06ec8aee --- /dev/null +++ b/components/mobileshell/qml/volumeosd/VolumeOSDProvider.qml @@ -0,0 +1,70 @@ +/* + SPDX-FileCopyrightText: 2021 Devin Lin + SPDX-FileCopyrightText: 2019 Aditya Mehra + SPDX-FileCopyrightText: 2014-2015 Harald Sitter + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +import QtQuick +import QtQuick.Layouts + +import org.kde.plasma.private.volume 0.1 as VolumeLib +import org.kde.plasma.private.mobileshell.state as MobileShellState + +import "../dataproviders" as DataProviders + +/** + * This imports the volume OSD and also sets up keyboard/hardware button bindings. + */ +QtObject { + id: component + + function showVolumeOverlay() { + osd.showOverlay(); + } + + property var audioInfo: DataProviders.AudioInfo { + onVolumeChanged: { + component.osd.showOverlay(); + } + } + + property var apiListener: Connections { + target: MobileShellState.ShellDBusClient + + function onShowVolumeOSDRequested() { + component.showVolumeOverlay(); + } + } + + property var osd: VolumeOSD { + volume: component.audioInfo.volumeValue + } + + property var actionCollection: VolumeLib.GlobalActionCollection { + name: "kmix" + displayName: i18n("Audio") + + VolumeLib.GlobalAction { + objectName: "increase_volume" + text: i18n("Increase Volume") + shortcut: Qt.Key_VolumeUp + onTriggered: component.audioInfo.increaseVolume() + } + + VolumeLib.GlobalAction { + objectName: "decrease_volume" + text: i18n("Decrease Volume") + shortcut: Qt.Key_VolumeDown + onTriggered: component.audioInfo.decreaseVolume() + } + + VolumeLib.GlobalAction { + objectName: "mute" + text: i18n("Mute") + shortcut: Qt.Key_VolumeMute + onTriggered: component.audioInfo.muteVolume() + } + } +} diff --git a/components/mobileshell/qml/volumeosd/VolumeOSDProviderLoader.qml b/components/mobileshell/qml/volumeosd/VolumeOSDProviderLoader.qml new file mode 100644 index 00000000..8fff5cbf --- /dev/null +++ b/components/mobileshell/qml/volumeosd/VolumeOSDProviderLoader.qml @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Devin Lin +// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + +import QtQuick + +pragma Singleton + +/** + * This wraps the VolumeOSDProvider component so that we can avoid loading side + * effects from imports (since this is a singleton and initialized immediately on import). + */ +Loader { + id: root + source: "qrc:/org/kde/plasma/private/mobileshell/qml/volumeosd/VolumeOSDProvider.qml" + + // WARNING: only call this load from within the plasmashell process, because + // multiple bindings of the shortcut may break it entirely (hardware volume keys) + function load() { + root.active = true; + } +} + diff --git a/components/mobileshell/resources.qrc b/components/mobileshell/resources.qrc index bec3a550..73eccb66 100644 --- a/components/mobileshell/resources.qrc +++ b/components/mobileshell/resources.qrc @@ -53,6 +53,8 @@ qml/volumeosd/PopupCard.qml qml/volumeosd/StreamListItem.qml qml/volumeosd/VolumeOSD.qml + qml/volumeosd/VolumeOSDProvider.qml + qml/volumeosd/VolumeOSDProviderLoader.qml qml/statusbar/indicators/BatteryIndicator.qml qml/statusbar/indicators/BluetoothIndicator.qml diff --git a/components/mobileshellstate/CMakeLists.txt b/components/mobileshellstate/CMakeLists.txt index 7a749cc4..a4c797be 100644 --- a/components/mobileshellstate/CMakeLists.txt +++ b/components/mobileshellstate/CMakeLists.txt @@ -22,7 +22,6 @@ qt_add_dbus_interface(mobileshellstateplugin_SRCS ${CMAKE_CURRENT_BINARY_DIR}/or install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.Mobile.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) -qt_add_resources(RESOURCES resources.qrc) add_library(mobileshellstateplugin SHARED ${mobileshellstateplugin_SRCS} ${RESOURCES}) target_link_libraries(mobileshellstateplugin diff --git a/components/mobileshellstate/mobileshellstateplugin.cpp b/components/mobileshellstate/mobileshellstateplugin.cpp index 3b5c51e0..6b951c6d 100644 --- a/components/mobileshellstate/mobileshellstateplugin.cpp +++ b/components/mobileshellstate/mobileshellstateplugin.cpp @@ -17,11 +17,10 @@ void MobileShellStatePlugin::registerTypes(const char *uri) { Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.mobileshell.state")); - qmlRegisterType(uri, 1, 0, "ShellDBusObject"); qmlRegisterSingletonType(uri, 1, 0, "ShellDBusClient", [](QQmlEngine *, QJSEngine *) -> QObject * { return ShellDBusClient::self(); }); - - // / - qmlRegisterSingletonType(resolvePath("AudioProvider.qml"), uri, 1, 0, "AudioProvider"); + qmlRegisterSingletonType(uri, 1, 0, "ShellDBusObject", [](QQmlEngine *, QJSEngine *) -> QObject * { + return ShellDBusObject::self(); + }); } diff --git a/components/mobileshellstate/qml/AudioProvider.qml b/components/mobileshellstate/qml/AudioProvider.qml deleted file mode 100644 index 2584af1b..00000000 --- a/components/mobileshellstate/qml/AudioProvider.qml +++ /dev/null @@ -1,98 +0,0 @@ -/* - SPDX-FileCopyrightText: 2021 Devin Lin - SPDX-FileCopyrightText: 2019 Aditya Mehra - SPDX-FileCopyrightText: 2014-2015 Harald Sitter - - SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL -*/ - -import QtQuick -import QtQuick.Layouts - -import org.kde.plasma.core as PlasmaCore -import org.kde.plasma.private.volume 0.1 -import org.kde.plasma.private.mobileshell as MobileShell - -pragma Singleton - -QtObject { - id: root - - /** - * Whether or not to bind the volume global key shortcuts. - * We should never bind the shortcut multiple times in the shell, or else they may not work at all. - * - * We only set this to true when loaded for the panel containment (and NOT in the lockscreen). - */ - property bool bindShortcuts: false - - property int volumeValue - - function showVolumeOverlay() { - osd.showOverlay(); - } - - property var audioInfo: MobileShell.AudioInfo {} - - property var updateVolume: Connections { - target: audioInfo.paSinkModel.preferredSink - - function onVolumeChanged() { - var percent = audioInfo.volumePercent(audioInfo.paSinkModel.preferredSink.volume, audioInfo.maxVolumeValue); - volumeValue = percent; - - osd.showOverlay(); - } - - function onMutedChanged() { - volumeValue = audioInfo.paSinkModel.preferredSink.muted ? 0 : audioInfo.volumePercent(audioInfo.paSinkModel.preferredSink.volume, audioInfo.maxVolumeValue); - osd.showOverlay(); - } - } - - property var updateVolumeOnSinkChange: Connections { - target: audioInfo.paSinkModel - - function onPreferredSinkChanged() { - if (audioInfo.paSinkModel.preferredSink) { - var percent = audioInfo.volumePercent(audioInfo.paSinkModel.preferredSink.volume, audioInfo.maxVolumeValue); - volumeValue = percent; - } - } - } - - property var osd: MobileShell.VolumeOSD { - volume: volumeValue - } - - // only bind the global shortcuts when told to - property var actionCollection: Loader { - active: bindShortcuts - - sourceComponent: GlobalActionCollection { - name: "kmix" - displayName: i18n("Audio") - - GlobalAction { - objectName: "increase_volume" - text: i18n("Increase Volume") - shortcut: Qt.Key_VolumeUp - onTriggered: root.audioInfo.increaseVolume() - } - - GlobalAction { - objectName: "decrease_volume" - text: i18n("Decrease Volume") - shortcut: Qt.Key_VolumeDown - onTriggered: root.audioInfo.decreaseVolume() - } - - GlobalAction { - objectName: "mute" - text: i18n("Mute") - shortcut: Qt.Key_VolumeMute - onTriggered: root.audioInfo.muteVolume() - } - } - } -} diff --git a/components/mobileshellstate/resources.qrc b/components/mobileshellstate/resources.qrc deleted file mode 100644 index 7653d82f..00000000 --- a/components/mobileshellstate/resources.qrc +++ /dev/null @@ -1,10 +0,0 @@ - - - - qml/AudioProvider.qml - - - diff --git a/components/mobileshellstate/shelldbusclient.cpp b/components/mobileshellstate/shelldbusclient.cpp index 5f07dfb2..ead21e36 100644 --- a/components/mobileshellstate/shelldbusclient.cpp +++ b/components/mobileshellstate/shelldbusclient.cpp @@ -44,6 +44,7 @@ void ShellDBusClient::connectSignals() connect(m_interface, &OrgKdePlasmashellInterface::closeAppLaunchAnimationRequested, this, &ShellDBusClient::closeAppLaunchAnimationRequested); connect(m_interface, &OrgKdePlasmashellInterface::openHomeScreenRequested, this, &ShellDBusClient::openHomeScreenRequested); connect(m_interface, &OrgKdePlasmashellInterface::resetHomeScreenPositionRequested, this, &ShellDBusClient::resetHomeScreenPositionRequested); + connect(m_interface, &OrgKdePlasmashellInterface::showVolumeOSDRequested, this, &ShellDBusClient::showVolumeOSDRequested); updateIsActionDrawerOpen(); updateDoNotDisturb(); @@ -99,6 +100,11 @@ void ShellDBusClient::resetHomeScreenPosition() m_interface->resetHomeScreenPosition(); } +void ShellDBusClient::showVolumeOSD() +{ + m_interface->showVolumeOSD(); +} + void ShellDBusClient::updateDoNotDisturb() { m_doNotDisturb = m_interface->doNotDisturb(); diff --git a/components/mobileshellstate/shelldbusclient.h b/components/mobileshellstate/shelldbusclient.h index ff1422d2..47b5e49a 100644 --- a/components/mobileshellstate/shelldbusclient.h +++ b/components/mobileshellstate/shelldbusclient.h @@ -33,6 +33,7 @@ public: Q_INVOKABLE void openHomeScreen(); Q_INVOKABLE void resetHomeScreenPosition(); + Q_INVOKABLE void showVolumeOSD(); Q_SIGNALS: void isActionDrawerOpenChanged(); @@ -43,6 +44,7 @@ Q_SIGNALS: void closeAppLaunchAnimationRequested(); void openHomeScreenRequested(); void resetHomeScreenPositionRequested(); + void showVolumeOSDRequested(); private Q_SLOTS: void updateDoNotDisturb(); diff --git a/components/mobileshellstate/shelldbusobject.cpp b/components/mobileshellstate/shelldbusobject.cpp index 289799dd..48ead1ac 100644 --- a/components/mobileshellstate/shelldbusobject.cpp +++ b/components/mobileshellstate/shelldbusobject.cpp @@ -9,8 +9,21 @@ ShellDBusObject::ShellDBusObject(QObject *parent) : QObject{parent} { - new PlasmashellAdaptor{this}; - QDBusConnection::sessionBus().registerObject(QStringLiteral("/Mobile"), this); +} + +ShellDBusObject *ShellDBusObject::self() +{ + static ShellDBusObject *instance = new ShellDBusObject; + return instance; +} + +void ShellDBusObject::registerObject() +{ + if (!m_initialized) { + new PlasmashellAdaptor{this}; + QDBusConnection::sessionBus().registerObject(QStringLiteral("/Mobile"), this); + m_initialized = true; + } } bool ShellDBusObject::doNotDisturb() @@ -68,3 +81,8 @@ void ShellDBusObject::resetHomeScreenPosition() { Q_EMIT resetHomeScreenPositionRequested(); } + +void ShellDBusObject::showVolumeOSD() +{ + Q_EMIT showVolumeOSDRequested(); +} diff --git a/components/mobileshellstate/shelldbusobject.h b/components/mobileshellstate/shelldbusobject.h index a4cc2489..b433400b 100644 --- a/components/mobileshellstate/shelldbusobject.h +++ b/components/mobileshellstate/shelldbusobject.h @@ -13,6 +13,10 @@ class ShellDBusObject : public QObject public: ShellDBusObject(QObject *parent = nullptr); + static ShellDBusObject *self(); + + // called by QML + Q_INVOKABLE void registerObject(); Q_SIGNALS: Q_SCRIPTABLE void doNotDisturbChanged(); @@ -23,11 +27,13 @@ Q_SIGNALS: Q_SCRIPTABLE void closeAppLaunchAnimationRequested(); Q_SCRIPTABLE void openHomeScreenRequested(); Q_SCRIPTABLE void resetHomeScreenPositionRequested(); + Q_SCRIPTABLE void showVolumeOSDRequested(); public Q_SLOTS: Q_SCRIPTABLE bool doNotDisturb(); Q_SCRIPTABLE void setDoNotDisturb(bool value); + // TODO: Account for multiple action drawers? Q_SCRIPTABLE bool isActionDrawerOpen(); Q_SCRIPTABLE void setIsActionDrawerOpen(bool value); @@ -39,8 +45,11 @@ public Q_SLOTS: Q_SCRIPTABLE void openHomeScreen(); Q_SCRIPTABLE void resetHomeScreenPosition(); + Q_SCRIPTABLE void showVolumeOSD(); private: + bool m_initialized = false; + bool m_doNotDisturb = false; bool m_isActionDrawerOpen = false; }; diff --git a/containments/homescreens/folio/package/contents/ui/main.qml b/containments/homescreens/folio/package/contents/ui/main.qml index c7cac778..bc019509 100644 --- a/containments/homescreens/folio/package/contents/ui/main.qml +++ b/containments/homescreens/folio/package/contents/ui/main.qml @@ -116,7 +116,7 @@ MobileShell.HomeScreen { Connections { target: Folio.ApplicationListModel function onLaunchError(msg) { - MobileShellState.Shell.closeAppLaunchAnimation() + MobileShellState.ShellDBusClient.closeAppLaunchAnimation() } } } diff --git a/containments/panel/package/contents/ui/main.qml b/containments/panel/package/contents/ui/main.qml index 0da73d59..7407ac46 100644 --- a/containments/panel/package/contents/ui/main.qml +++ b/containments/panel/package/contents/ui/main.qml @@ -52,7 +52,6 @@ Item { } function onCloseActionDrawerRequested() { - console.log('action drawer close'); drawer.actionDrawer.close(); } @@ -70,10 +69,14 @@ Item { } //END API implementation - + Component.onCompleted: { - // we want to bind global volume shortcuts here - MobileShellState.AudioProvider.bindShortcuts = true; + // register dbus + MobileShellState.ShellDBusObject.registerObject(); + + // HACK: we need to initialize the DBus server somewhere, it might as well be here... + // initialize the volume osd, and volume keys + MobileShell.VolumeOSDProviderLoader.load(); } // top panel component @@ -86,6 +89,7 @@ Item { backgroundColor: !root.showingApp ? "transparent" : root.backgroundColor } + // swiping area for swipe-down drawer MobileShell.ActionDrawerOpenSurface { id: swipeArea actionDrawer: drawer.actionDrawer diff --git a/quicksettings/audio/contents/ui/main.qml b/quicksettings/audio/contents/ui/main.qml index 92a5dbe6..7ac509a4 100644 --- a/quicksettings/audio/contents/ui/main.qml +++ b/quicksettings/audio/contents/ui/main.qml @@ -10,10 +10,13 @@ import org.kde.plasma.private.mobileshell.quicksettingsplugin as QS QS.QuickSetting { text: i18n("Sound") icon: "audio-speakers-symbolic" - status: i18n("%1%", MobileShellState.AudioProvider.volumeValue) + status: i18n("%1%", audioInfo.volumeValue) enabled: false settingsCommand: "plasma-open-settings kcm_pulseaudio" + + property var audioInfo: MobileShell.AudioInfo {} + function toggle() { - MobileShellState.AudioProvider.showVolumeOverlay() + MobileShellState.ShellDBusClient.showVolumeOSD() } } diff --git a/shell/contents/views/Desktop.qml b/shell/contents/views/Desktop.qml index 51c02f58..4daa31bd 100644 --- a/shell/contents/views/Desktop.qml +++ b/shell/contents/views/Desktop.qml @@ -9,9 +9,9 @@ import QtQuick 2.15 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.shell 2.0 as Shell + import org.kde.kquickcontrolsaddons 2.0 import org.kde.kirigami 2.20 as Kirigami -import org.kde.plasma.private.mobileshell.state as MobileShellState Rectangle { id: root @@ -29,29 +29,6 @@ Rectangle { } } - Loader { - id: widgetExplorerStack - z: 99 - asynchronous: true - y: containment ? containment.availableScreenRect.y : 0 - height: containment ? containment.availableScreenRect.height : parent.height - width: parent.width - - onLoaded: { - if (widgetExplorerStack.item) { - item.closed.connect(function() { - widgetExplorerStack.source = "" - }); - - item.topPanelHeight = containment.availableScreenRect.y - item.bottomPanelHeight = root.height - (containment.availableScreenRect.height + containment.availableScreenRect.y) - - item.leftPanelWidth = containment.availableScreenRect.x - item.rightPanelWidth = root.width - (containment.availableScreenRect.width + containment.availableScreenRect.x) - } - } - } - onContainmentChanged: { if (containment == null) { return; @@ -62,10 +39,8 @@ Rectangle { containment.anchors.fill = root; } - // Load shell dbus object - MobileShellState.ShellDBusObject {} - // This is taken from plasma-desktop's shell package, try to keep it in sync + // Handles taking accent color from wallpaper Loader { id: wallpaperColors @@ -108,4 +83,26 @@ Rectangle { onLoaded: item.update() } + Loader { + id: widgetExplorerStack + z: 99 + asynchronous: true + y: containment ? containment.availableScreenRect.y : 0 + height: containment ? containment.availableScreenRect.height : parent.height + width: parent.width + + onLoaded: { + if (widgetExplorerStack.item) { + item.closed.connect(function() { + widgetExplorerStack.source = "" + }); + + item.topPanelHeight = containment.availableScreenRect.y + item.bottomPanelHeight = root.height - (containment.availableScreenRect.height + containment.availableScreenRect.y) + + item.leftPanelWidth = containment.availableScreenRect.x + item.rightPanelWidth = root.width - (containment.availableScreenRect.width + containment.availableScreenRect.x) + } + } + } }