volumeosd: Extract out singleton so that it's only loaded once in plasmashell

This commit is contained in:
Devin Lin 2023-03-19 21:10:14 -07:00
parent 10cb65fc9c
commit 0e4b1da9a1
18 changed files with 209 additions and 150 deletions

View file

@ -74,6 +74,8 @@ void MobileShellPlugin::registerTypes(const char *uri)
// /volumeosd // /volumeosd
qmlRegisterType(resolvePath("volumeosd/VolumeOSD.qml"), uri, 1, 0, "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 // /widgets
qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget"); qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget");

View file

@ -6,6 +6,8 @@ import QtQuick
import org.kde.plasma.private.volume import org.kde.plasma.private.volume
QtObject { QtObject {
id: root
property SinkModel paSinkModel: SinkModel {} property SinkModel paSinkModel: SinkModel {}
// whether the audio icon should be visible in the status bar // whether the audio icon should be visible in the status bar
@ -25,6 +27,9 @@ QtObject {
// step that increments when adjusting the volume // step that increments when adjusting the volume
readonly property int volumeStep: Math.round(5 * PulseAudio.NormalVolume / 100.0) 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) { function isDummyOutput(output) {
return output && output.name === dummyOutputName; return output && output.name === dummyOutputName;
} }
@ -87,4 +92,33 @@ QtObject {
} }
return icon; 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);
}
}
}
} }

View file

@ -22,7 +22,7 @@ import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
import "../dataproviders" as DataProviders import "../dataproviders" as DataProviders
Window { NanoShell.FullScreenOverlay {
id: window id: window
required property int volume required property int volume

View file

@ -0,0 +1,70 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
SPDX-FileCopyrightText: 2019 Aditya Mehra <Aix.m@outlook.com>
SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
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()
}
}
}

View file

@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// 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;
}
}

View file

@ -53,6 +53,8 @@
<file>qml/volumeosd/PopupCard.qml</file> <file>qml/volumeosd/PopupCard.qml</file>
<file>qml/volumeosd/StreamListItem.qml</file> <file>qml/volumeosd/StreamListItem.qml</file>
<file>qml/volumeosd/VolumeOSD.qml</file> <file>qml/volumeosd/VolumeOSD.qml</file>
<file>qml/volumeosd/VolumeOSDProvider.qml</file>
<file>qml/volumeosd/VolumeOSDProviderLoader.qml</file>
<file>qml/statusbar/indicators/BatteryIndicator.qml</file> <file>qml/statusbar/indicators/BatteryIndicator.qml</file>
<file>qml/statusbar/indicators/BluetoothIndicator.qml</file> <file>qml/statusbar/indicators/BluetoothIndicator.qml</file>

View file

@ -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}) 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}) add_library(mobileshellstateplugin SHARED ${mobileshellstateplugin_SRCS} ${RESOURCES})
target_link_libraries(mobileshellstateplugin target_link_libraries(mobileshellstateplugin

View file

@ -17,11 +17,10 @@ void MobileShellStatePlugin::registerTypes(const char *uri)
{ {
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.mobileshell.state")); Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.mobileshell.state"));
qmlRegisterType<ShellDBusObject>(uri, 1, 0, "ShellDBusObject");
qmlRegisterSingletonType<ShellDBusClient>(uri, 1, 0, "ShellDBusClient", [](QQmlEngine *, QJSEngine *) -> QObject * { qmlRegisterSingletonType<ShellDBusClient>(uri, 1, 0, "ShellDBusClient", [](QQmlEngine *, QJSEngine *) -> QObject * {
return ShellDBusClient::self(); return ShellDBusClient::self();
}); });
qmlRegisterSingletonType<ShellDBusObject>(uri, 1, 0, "ShellDBusObject", [](QQmlEngine *, QJSEngine *) -> QObject * {
// / return ShellDBusObject::self();
qmlRegisterSingletonType(resolvePath("AudioProvider.qml"), uri, 1, 0, "AudioProvider"); });
} }

View file

@ -1,98 +0,0 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
SPDX-FileCopyrightText: 2019 Aditya Mehra <Aix.m@outlook.com>
SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
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()
}
}
}
}

View file

@ -1,10 +0,0 @@
<!--
- Copyright 2022 Devin Lin <devin@kde.org>
- SPDX-License-Identifier: GPL-2.0-or-later
-->
<RCC>
<qresource prefix="/org/kde/plasma/private/mobileshell/state/">
<file>qml/AudioProvider.qml</file>
</qresource>
</RCC>

View file

@ -44,6 +44,7 @@ void ShellDBusClient::connectSignals()
connect(m_interface, &OrgKdePlasmashellInterface::closeAppLaunchAnimationRequested, this, &ShellDBusClient::closeAppLaunchAnimationRequested); connect(m_interface, &OrgKdePlasmashellInterface::closeAppLaunchAnimationRequested, this, &ShellDBusClient::closeAppLaunchAnimationRequested);
connect(m_interface, &OrgKdePlasmashellInterface::openHomeScreenRequested, this, &ShellDBusClient::openHomeScreenRequested); connect(m_interface, &OrgKdePlasmashellInterface::openHomeScreenRequested, this, &ShellDBusClient::openHomeScreenRequested);
connect(m_interface, &OrgKdePlasmashellInterface::resetHomeScreenPositionRequested, this, &ShellDBusClient::resetHomeScreenPositionRequested); connect(m_interface, &OrgKdePlasmashellInterface::resetHomeScreenPositionRequested, this, &ShellDBusClient::resetHomeScreenPositionRequested);
connect(m_interface, &OrgKdePlasmashellInterface::showVolumeOSDRequested, this, &ShellDBusClient::showVolumeOSDRequested);
updateIsActionDrawerOpen(); updateIsActionDrawerOpen();
updateDoNotDisturb(); updateDoNotDisturb();
@ -99,6 +100,11 @@ void ShellDBusClient::resetHomeScreenPosition()
m_interface->resetHomeScreenPosition(); m_interface->resetHomeScreenPosition();
} }
void ShellDBusClient::showVolumeOSD()
{
m_interface->showVolumeOSD();
}
void ShellDBusClient::updateDoNotDisturb() void ShellDBusClient::updateDoNotDisturb()
{ {
m_doNotDisturb = m_interface->doNotDisturb(); m_doNotDisturb = m_interface->doNotDisturb();

View file

@ -33,6 +33,7 @@ public:
Q_INVOKABLE void openHomeScreen(); Q_INVOKABLE void openHomeScreen();
Q_INVOKABLE void resetHomeScreenPosition(); Q_INVOKABLE void resetHomeScreenPosition();
Q_INVOKABLE void showVolumeOSD();
Q_SIGNALS: Q_SIGNALS:
void isActionDrawerOpenChanged(); void isActionDrawerOpenChanged();
@ -43,6 +44,7 @@ Q_SIGNALS:
void closeAppLaunchAnimationRequested(); void closeAppLaunchAnimationRequested();
void openHomeScreenRequested(); void openHomeScreenRequested();
void resetHomeScreenPositionRequested(); void resetHomeScreenPositionRequested();
void showVolumeOSDRequested();
private Q_SLOTS: private Q_SLOTS:
void updateDoNotDisturb(); void updateDoNotDisturb();

View file

@ -9,8 +9,21 @@
ShellDBusObject::ShellDBusObject(QObject *parent) ShellDBusObject::ShellDBusObject(QObject *parent)
: 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() bool ShellDBusObject::doNotDisturb()
@ -68,3 +81,8 @@ void ShellDBusObject::resetHomeScreenPosition()
{ {
Q_EMIT resetHomeScreenPositionRequested(); Q_EMIT resetHomeScreenPositionRequested();
} }
void ShellDBusObject::showVolumeOSD()
{
Q_EMIT showVolumeOSDRequested();
}

View file

@ -13,6 +13,10 @@ class ShellDBusObject : public QObject
public: public:
ShellDBusObject(QObject *parent = nullptr); ShellDBusObject(QObject *parent = nullptr);
static ShellDBusObject *self();
// called by QML
Q_INVOKABLE void registerObject();
Q_SIGNALS: Q_SIGNALS:
Q_SCRIPTABLE void doNotDisturbChanged(); Q_SCRIPTABLE void doNotDisturbChanged();
@ -23,11 +27,13 @@ Q_SIGNALS:
Q_SCRIPTABLE void closeAppLaunchAnimationRequested(); Q_SCRIPTABLE void closeAppLaunchAnimationRequested();
Q_SCRIPTABLE void openHomeScreenRequested(); Q_SCRIPTABLE void openHomeScreenRequested();
Q_SCRIPTABLE void resetHomeScreenPositionRequested(); Q_SCRIPTABLE void resetHomeScreenPositionRequested();
Q_SCRIPTABLE void showVolumeOSDRequested();
public Q_SLOTS: public Q_SLOTS:
Q_SCRIPTABLE bool doNotDisturb(); Q_SCRIPTABLE bool doNotDisturb();
Q_SCRIPTABLE void setDoNotDisturb(bool value); Q_SCRIPTABLE void setDoNotDisturb(bool value);
// TODO: Account for multiple action drawers?
Q_SCRIPTABLE bool isActionDrawerOpen(); Q_SCRIPTABLE bool isActionDrawerOpen();
Q_SCRIPTABLE void setIsActionDrawerOpen(bool value); Q_SCRIPTABLE void setIsActionDrawerOpen(bool value);
@ -39,8 +45,11 @@ public Q_SLOTS:
Q_SCRIPTABLE void openHomeScreen(); Q_SCRIPTABLE void openHomeScreen();
Q_SCRIPTABLE void resetHomeScreenPosition(); Q_SCRIPTABLE void resetHomeScreenPosition();
Q_SCRIPTABLE void showVolumeOSD();
private: private:
bool m_initialized = false;
bool m_doNotDisturb = false; bool m_doNotDisturb = false;
bool m_isActionDrawerOpen = false; bool m_isActionDrawerOpen = false;
}; };

View file

@ -116,7 +116,7 @@ MobileShell.HomeScreen {
Connections { Connections {
target: Folio.ApplicationListModel target: Folio.ApplicationListModel
function onLaunchError(msg) { function onLaunchError(msg) {
MobileShellState.Shell.closeAppLaunchAnimation() MobileShellState.ShellDBusClient.closeAppLaunchAnimation()
} }
} }
} }

View file

@ -52,7 +52,6 @@ Item {
} }
function onCloseActionDrawerRequested() { function onCloseActionDrawerRequested() {
console.log('action drawer close');
drawer.actionDrawer.close(); drawer.actionDrawer.close();
} }
@ -72,8 +71,12 @@ Item {
//END API implementation //END API implementation
Component.onCompleted: { Component.onCompleted: {
// we want to bind global volume shortcuts here // register dbus
MobileShellState.AudioProvider.bindShortcuts = true; 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 // top panel component
@ -86,6 +89,7 @@ Item {
backgroundColor: !root.showingApp ? "transparent" : root.backgroundColor backgroundColor: !root.showingApp ? "transparent" : root.backgroundColor
} }
// swiping area for swipe-down drawer
MobileShell.ActionDrawerOpenSurface { MobileShell.ActionDrawerOpenSurface {
id: swipeArea id: swipeArea
actionDrawer: drawer.actionDrawer actionDrawer: drawer.actionDrawer

View file

@ -10,10 +10,13 @@ import org.kde.plasma.private.mobileshell.quicksettingsplugin as QS
QS.QuickSetting { QS.QuickSetting {
text: i18n("Sound") text: i18n("Sound")
icon: "audio-speakers-symbolic" icon: "audio-speakers-symbolic"
status: i18n("%1%", MobileShellState.AudioProvider.volumeValue) status: i18n("%1%", audioInfo.volumeValue)
enabled: false enabled: false
settingsCommand: "plasma-open-settings kcm_pulseaudio" settingsCommand: "plasma-open-settings kcm_pulseaudio"
property var audioInfo: MobileShell.AudioInfo {}
function toggle() { function toggle() {
MobileShellState.AudioProvider.showVolumeOverlay() MobileShellState.ShellDBusClient.showVolumeOSD()
} }
} }

View file

@ -9,9 +9,9 @@ import QtQuick 2.15
import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.shell 2.0 as Shell import org.kde.plasma.shell 2.0 as Shell
import org.kde.kquickcontrolsaddons 2.0 import org.kde.kquickcontrolsaddons 2.0
import org.kde.kirigami 2.20 as Kirigami import org.kde.kirigami 2.20 as Kirigami
import org.kde.plasma.private.mobileshell.state as MobileShellState
Rectangle { Rectangle {
id: root 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: { onContainmentChanged: {
if (containment == null) { if (containment == null) {
return; return;
@ -62,10 +39,8 @@ Rectangle {
containment.anchors.fill = root; 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 // This is taken from plasma-desktop's shell package, try to keep it in sync
// Handles taking accent color from wallpaper
Loader { Loader {
id: wallpaperColors id: wallpaperColors
@ -108,4 +83,26 @@ Rectangle {
onLoaded: item.update() 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)
}
}
}
} }