From 158af43fd469b5211ad7b2a23745b57cb031b238 Mon Sep 17 00:00:00 2001 From: Devin Lin Date: Thu, 16 Mar 2023 07:21:01 +0000 Subject: [PATCH] audio: Refactor applet and extract singleton to MobileShellState The eventual goal is to have as few singletons with state as possible in the mobileshell component when it is imported into components such as the lockscreen. This doesn't fully accomplish it, but moves the audio provider singleton to MobileShellState, which will eventually need to be prevented from importing into non plasmashell processes. This also disables the sound feedback when changing volume, since it can be a source of lag when showing the applet. --- components/mobileshell/mobileshellplugin.cpp | 5 +- .../qml/dataproviders/AudioInfo.qml | 90 ++++++++ .../qml/dataproviders/AudioProvider.qml | 195 ------------------ .../qml/dataproviders/BatteryInfo.qml | 13 +- .../statusbar/indicators/VolumeIndicator.qml | 3 +- .../{osd/volume => volumeosd}/AudioApplet.qml | 32 +-- .../volume => volumeosd}/DeviceListItem.qml | 0 .../volume => volumeosd}/ListItemBase.qml | 7 - .../{osd/volume => volumeosd}/PopupCard.qml | 0 .../volume => volumeosd}/StreamListItem.qml | 0 .../{osd/volume => volumeosd}/VolumeOSD.qml | 73 ++++--- .../qml/{osd/volume => volumeosd}/icon.js | 0 components/mobileshell/resources.qrc | 16 +- components/mobileshellstate/README.md | 10 + .../mobileshellstateplugin.cpp | 1 + .../mobileshellstate/qml/AudioProvider.qml | 98 +++++++++ .../qml/HomeScreenControls.qml | 14 -- components/mobileshellstate/qml/Shell.qml | 5 - components/mobileshellstate/resources.qrc | 1 + .../folio/package/contents/ui/main.qml | 3 +- .../package/contents/ui/HomeScreen.qml | 3 +- .../halcyon/package/contents/ui/main.qml | 4 +- .../panel/package/contents/ui/main.qml | 2 +- quicksettings/audio/contents/ui/main.qml | 9 +- 24 files changed, 283 insertions(+), 301 deletions(-) create mode 100644 components/mobileshell/qml/dataproviders/AudioInfo.qml delete mode 100644 components/mobileshell/qml/dataproviders/AudioProvider.qml rename components/mobileshell/qml/{osd/volume => volumeosd}/AudioApplet.qml (89%) rename components/mobileshell/qml/{osd/volume => volumeosd}/DeviceListItem.qml (100%) rename components/mobileshell/qml/{osd/volume => volumeosd}/ListItemBase.qml (96%) rename components/mobileshell/qml/{osd/volume => volumeosd}/PopupCard.qml (100%) rename components/mobileshell/qml/{osd/volume => volumeosd}/StreamListItem.qml (100%) rename components/mobileshell/qml/{osd/volume => volumeosd}/VolumeOSD.qml (88%) rename components/mobileshell/qml/{osd/volume => volumeosd}/icon.js (100%) create mode 100644 components/mobileshellstate/README.md create mode 100644 components/mobileshellstate/qml/AudioProvider.qml diff --git a/components/mobileshell/mobileshellplugin.cpp b/components/mobileshell/mobileshellplugin.cpp index 11c0bd98..dfebafb5 100644 --- a/components/mobileshell/mobileshellplugin.cpp +++ b/components/mobileshell/mobileshellplugin.cpp @@ -76,10 +76,10 @@ void MobileShellPlugin::registerTypes(const char *uri) qmlRegisterType(resolvePath("components/VelocityCalculator.qml"), uri, 1, 0, "VelocityCalculator"); // /dataproviders + qmlRegisterType(resolvePath("dataproviders/AudioInfo.qml"), uri, 1, 0, "AudioInfo"); qmlRegisterType(resolvePath("dataproviders/BatteryInfo.qml"), uri, 1, 0, "BatteryInfo"); qmlRegisterType(resolvePath("dataproviders/BluetoothInfo.qml"), uri, 1, 0, "BluetoothInfo"); qmlRegisterType(resolvePath("dataproviders/SignalStrengthInfo.qml"), uri, 1, 0, "SignalStrengthInfo"); - qmlRegisterSingletonType(resolvePath("dataproviders/AudioProvider.qml"), uri, 1, 0, "AudioProvider"); // /homescreen qmlRegisterType(resolvePath("homescreen/HomeScreen.qml"), uri, 1, 0, "HomeScreen"); @@ -92,6 +92,9 @@ void MobileShellPlugin::registerTypes(const char *uri) // /statusbar qmlRegisterType(resolvePath("statusbar/StatusBar.qml"), uri, 1, 0, "StatusBar"); + // /volumeosd + qmlRegisterType(resolvePath("volumeosd/VolumeOSD.qml"), uri, 1, 0, "VolumeOSD"); + // /widgets qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget"); qmlRegisterType(resolvePath("widgets/mediacontrols/MediaControlsWidget.qml"), uri, 1, 0, "MediaControlsWidget"); diff --git a/components/mobileshell/qml/dataproviders/AudioInfo.qml b/components/mobileshell/qml/dataproviders/AudioInfo.qml new file mode 100644 index 00000000..433ae769 --- /dev/null +++ b/components/mobileshell/qml/dataproviders/AudioInfo.qml @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2023 Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later + +import QtQuick + +import org.kde.plasma.private.volume + +QtObject { + property SinkModel paSinkModel: SinkModel {} + + // whether the audio icon should be visible in the status bar + readonly property bool isVisible: paSinkModel.preferredSink && paSinkModel.preferredSink.muted + + // the icon that should be displayed in the status bar + readonly property string icon: paSinkModel.preferredSink && !isDummyOutput(paSinkModel.preferredSink) + ? iconName(paSinkModel.preferredSink.volume, paSinkModel.preferredSink.muted) + : iconName(0, true) + + // the name of the audio device when it isn't valid + readonly property string dummyOutputName: "auto_null" + + // the maximum volume amount + readonly property int maxVolumeValue: Math.round(100 * PulseAudio.NormalVolume / 100.0) + + // step that increments when adjusting the volume + readonly property int volumeStep: Math.round(5 * PulseAudio.NormalVolume / 100.0) + + function isDummyOutput(output) { + return output && output.name === dummyOutputName; + } + + function boundVolume(volume) { + return Math.max(PulseAudio.MinimalVolume, Math.min(volume, maxVolumeValue)); + } + + function volumePercent(volume, max){ + if (!max) { + max = PulseAudio.NormalVolume; + } + return Math.round(volume / max * 100.0); + } + + function increaseVolume() { + if (!paSinkModel.preferredSink || isDummyOutput(paSinkModel.preferredSink)) { + return; + } + + var volume = boundVolume(paSinkModel.preferredSink.volume + volumeStep); + var percent = volumePercent(volume, maxVolumeValue); + paSinkModel.preferredSink.muted = percent == 0; + paSinkModel.preferredSink.volume = volume; + } + + function decreaseVolume() { + if (!paSinkModel.preferredSink || isDummyOutput(paSinkModel.preferredSink)) { + return; + } + + var volume = boundVolume(paSinkModel.preferredSink.volume - volumeStep); + var percent = volumePercent(volume, maxVolumeValue); + paSinkModel.preferredSink.muted = percent == 0; + paSinkModel.preferredSink.volume = volume; + } + + function muteVolume() { + if (!paSinkModel.preferredSink || isDummyOutput(paSinkModel.preferredSink)) { + return; + } + + paSinkModel.preferredSink.muted = !paSinkModel.preferredSink.muted; + } + + function iconName(volume, muted, prefix) { + if (!prefix) { + prefix = "audio-volume"; + } + var icon = null; + var percent = volume / maxVolumeValue; + if (percent <= 0.0 || muted) { + icon = prefix + "-muted"; + } else if (percent <= 0.25) { + icon = prefix + "-low"; + } else if (percent <= 0.75) { + icon = prefix + "-medium"; + } else { + icon = prefix + "-high"; + } + return icon; + } +} diff --git a/components/mobileshell/qml/dataproviders/AudioProvider.qml b/components/mobileshell/qml/dataproviders/AudioProvider.qml deleted file mode 100644 index e43ba666..00000000 --- a/components/mobileshell/qml/dataproviders/AudioProvider.qml +++ /dev/null @@ -1,195 +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 2.2 -import QtQuick.Layouts 1.4 -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.private.volume 0.1 - -import "../osd/volume" - -pragma Singleton - -QtObject { - /** - * 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 - - readonly property bool isVisible: paSinkModel.preferredSink && paSinkModel.preferredSink.muted - - readonly property string icon: paSinkModel.preferredSink && !isDummyOutput(paSinkModel.preferredSink) - ? iconName(paSinkModel.preferredSink.volume, paSinkModel.preferredSink.muted) - : iconName(0, true) - - readonly property int maxVolumeValue: Math.round(100 * PulseAudio.NormalVolume / 100.0) - readonly property int volumeStep: Math.round(5 * PulseAudio.NormalVolume / 100.0) - - property int volumeValue - - readonly property string dummyOutputName: "auto_null" - - function showVolumeOverlay() { - osd.showOverlay(); - } - - function iconName(volume, muted, prefix) { - if (!prefix) { - prefix = "audio-volume"; - } - var icon = null; - var percent = volume / maxVolumeValue; - if (percent <= 0.0 || muted) { - icon = prefix + "-muted"; - } else if (percent <= 0.25) { - icon = prefix + "-low"; - } else if (percent <= 0.75) { - icon = prefix + "-medium"; - } else { - icon = prefix + "-high"; - } - return icon; - } - - function isDummyOutput(output) { - return output && output.name === dummyOutputName; - } - - function boundVolume(volume) { - return Math.max(PulseAudio.MinimalVolume, Math.min(volume, maxVolumeValue)); - } - - function volumePercent(volume, max){ - if(!max) { - max = PulseAudio.NormalVolume; - } - return Math.round(volume / max * 100.0); - } - - function playFeedback(sinkIndex) { - if (sinkIndex == undefined) { - sinkIndex = paSinkModel.preferredSink.index; - } - feedback.play(sinkIndex) - } - - function increaseVolume() { - if (!paSinkModel.preferredSink || isDummyOutput(paSinkModel.preferredSink)) { - return; - } - - var volume = boundVolume(paSinkModel.preferredSink.volume + volumeStep); - var percent = volumePercent(volume, maxVolumeValue); - paSinkModel.preferredSink.muted = percent == 0; - paSinkModel.preferredSink.volume = volume; - volumeValue = percent; - osd.showOverlay(); - playFeedback(); - - } - - function decreaseVolume() { - if (!paSinkModel.preferredSink || isDummyOutput(paSinkModel.preferredSink)) { - return; - } - - var volume = boundVolume(paSinkModel.preferredSink.volume - volumeStep); - var percent = volumePercent(volume, maxVolumeValue); - paSinkModel.preferredSink.muted = percent == 0; - paSinkModel.preferredSink.volume = volume; - volumeValue = percent; - osd.showOverlay(); - playFeedback(); - } - - function muteVolume() { - if (!paSinkModel.preferredSink || isDummyOutput(paSinkModel.preferredSink)) { - return; - } - - var toMute = !paSinkModel.preferredSink.muted; - paSinkModel.preferredSink.muted = toMute; - - volumeValue = toMute ? 0 : volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue); - osd.showOverlay(); - - if (!toMute) { - playFeedback(); - } - } - - property var updateVolume: Connections { - target: paSinkModel.preferredSink - - function onVolumeChanged() { - var percent = volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue); - volumeValue = percent; - } - } - property var updateVolumeOnSinkChange: Connections { - target: paSinkModel - - function onPreferredSinkChanged() { - if (paSinkModel.preferredSink) { - var percent = volumePercent(paSinkModel.preferredSink.volume, maxVolumeValue); - volumeValue = percent; - } - } - } - - property SinkModel paSinkModel: SinkModel {} - - property var osd: VolumeOSD { - volume: volumeValue - } - - property VolumeFeedback feedback: VolumeFeedback {} - - // only bind the global shortcuts when told to - property var actionCollection: Loader { - active: bindShortcuts - - sourceComponent: GlobalActionCollection { - // KGlobalAccel cannot transition from kmix to something else, so if - // the user had a custom shortcut set for kmix those would get lost. - // To avoid this we hijack kmix name and actions. Entirely mental but - // best we can do to not cause annoyance for the user. - // The display name actually is updated to whatever registered last - // though, so as far as user visible strings go we should be fine. - // As of 2015-07-21: - // componentName: kmix - // actions: increase_volume, decrease_volume, mute - name: "kmix" - displayName: i18n("Audio") - - GlobalAction { - objectName: "increase_volume" - text: i18n("Increase Volume") - shortcut: Qt.Key_VolumeUp - onTriggered: increaseVolume() - } - - GlobalAction { - objectName: "decrease_volume" - text: i18n("Decrease Volume") - shortcut: Qt.Key_VolumeDown - onTriggered: decreaseVolume() - } - - GlobalAction { - objectName: "mute" - text: i18n("Mute") - shortcut: Qt.Key_VolumeMute - onTriggered: muteVolume() - } - } - } -} diff --git a/components/mobileshell/qml/dataproviders/BatteryInfo.qml b/components/mobileshell/qml/dataproviders/BatteryInfo.qml index 878999bd..bf564072 100644 --- a/components/mobileshell/qml/dataproviders/BatteryInfo.qml +++ b/components/mobileshell/qml/dataproviders/BatteryInfo.qml @@ -5,15 +5,14 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ -import QtQuick 2.6 -import QtQuick.Layouts 1.4 +import QtQuick +import QtQuick.Layouts -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.plasma5support 2.0 as P5Support -import org.kde.plasma.components 3.0 as PlasmaComponents -import org.kde.plasma.workspace.components 2.0 as PW +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.plasma5support as P5Support +import org.kde.plasma.workspace.components as PW -Item { +QtObject { property bool isVisible: pmSource.data["Battery"]["Has Cumulative"] property int percent: pmSource.data["Battery"]["Percent"] property bool pluggedIn: pmSource.data["AC Adapter"] ? pmSource.data["AC Adapter"]["Plugged in"] : false diff --git a/components/mobileshell/qml/statusbar/indicators/VolumeIndicator.qml b/components/mobileshell/qml/statusbar/indicators/VolumeIndicator.qml index daedfa7b..1c1e576e 100644 --- a/components/mobileshell/qml/statusbar/indicators/VolumeIndicator.qml +++ b/components/mobileshell/qml/statusbar/indicators/VolumeIndicator.qml @@ -8,13 +8,14 @@ import QtQuick 2.2 import QtQuick.Layouts 1.4 + import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.private.volume 0.1 import org.kde.plasma.private.mobileshell 1.0 as MobileShell PlasmaCore.IconItem { id: paIcon - readonly property var provider: MobileShell.AudioProvider + readonly property var provider: MobileShell.AudioInfo {} Layout.fillHeight: true Layout.preferredWidth: height diff --git a/components/mobileshell/qml/osd/volume/AudioApplet.qml b/components/mobileshell/qml/volumeosd/AudioApplet.qml similarity index 89% rename from components/mobileshell/qml/osd/volume/AudioApplet.qml rename to components/mobileshell/qml/volumeosd/AudioApplet.qml index fa548d52..693fad51 100644 --- a/components/mobileshell/qml/osd/volume/AudioApplet.qml +++ b/components/mobileshell/qml/volumeosd/AudioApplet.qml @@ -5,42 +5,32 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ -import QtQuick 2.15 -import QtQuick.Controls 2.15 as Controls -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.2 +import QtQuick +import QtQuick.Controls as Controls +import QtQuick.Layouts +import QtQuick.Window import Qt5Compat.GraphicalEffects -import org.kde.plasma.core 2.1 as PlasmaCore +import org.kde.plasma.core as PlasmaCore import org.kde.plasma.components 3.0 as PlasmaComponents -import org.kde.plasma.extras 2.0 as PlasmaExtra -import org.kde.kquickcontrolsaddons 2.0 as KQCAddons +import org.kde.plasma.extras as PlasmaExtra +import org.kde.plasma.private.mobileshell as MobileShell +import org.kde.kquickcontrolsaddons as KQCAddons -import org.kde.plasma.private.volume 0.1 +import org.kde.plasma.private.volume -// adapted version of https://invent.kde.org/plasma/plasma-pa/-/blob/master/applet/contents/ui/main.qml - -// most audio functions are in VolumeProvider.qml (which will be a parent) // capture presses on the audio applet so it doesn't close the overlay ColumnLayout { spacing: 0 - // pulseaudio models - - function isDummyOutput(output) { - return output && output.name === dummyOutputName; - } - - SinkModel { - id: paSinkModel - } + required property MobileShell.AudioInfo audioInfo PulseObjectFilterModel { id: paSinkFilterModel sortRole: "SortByDefault" sortOrder: Qt.DescendingOrder filterOutInactiveDevices: true - sourceModel: paSinkModel + sourceModel: audioInfo.paSinkModel } SourceModel { diff --git a/components/mobileshell/qml/osd/volume/DeviceListItem.qml b/components/mobileshell/qml/volumeosd/DeviceListItem.qml similarity index 100% rename from components/mobileshell/qml/osd/volume/DeviceListItem.qml rename to components/mobileshell/qml/volumeosd/DeviceListItem.qml diff --git a/components/mobileshell/qml/osd/volume/ListItemBase.qml b/components/mobileshell/qml/volumeosd/ListItemBase.qml similarity index 96% rename from components/mobileshell/qml/osd/volume/ListItemBase.qml rename to components/mobileshell/qml/volumeosd/ListItemBase.qml index a6c40bba..9fdcdfaf 100644 --- a/components/mobileshell/qml/osd/volume/ListItemBase.qml +++ b/components/mobileshell/qml/volumeosd/ListItemBase.qml @@ -29,9 +29,6 @@ Controls.ItemDelegate { property alias iconUsesPlasmaTheme: clientIcon.usesPlasmaTheme property string type // sink, source, source-output - // backgroundColor: "transparent" // we use panel background, no need for the same colour to be on top - // activeBackgroundColor: selectButton.visible ? PlasmaCore.Theme.highlightColor : "transparent" - onClicked: { if (selectButton.visible) { model.PulseObject.default = true; @@ -215,10 +212,6 @@ Controls.ItemDelegate { // whereas PA rejected the volume change and is // still at v15 (e.g.). updateTimer.restart(); - - if (baseItem.type == "sink") { - playFeedback(Index); // goes to providers/VolumeProvider.qml - } } } diff --git a/components/mobileshell/qml/osd/volume/PopupCard.qml b/components/mobileshell/qml/volumeosd/PopupCard.qml similarity index 100% rename from components/mobileshell/qml/osd/volume/PopupCard.qml rename to components/mobileshell/qml/volumeosd/PopupCard.qml diff --git a/components/mobileshell/qml/osd/volume/StreamListItem.qml b/components/mobileshell/qml/volumeosd/StreamListItem.qml similarity index 100% rename from components/mobileshell/qml/osd/volume/StreamListItem.qml rename to components/mobileshell/qml/volumeosd/StreamListItem.qml diff --git a/components/mobileshell/qml/osd/volume/VolumeOSD.qml b/components/mobileshell/qml/volumeosd/VolumeOSD.qml similarity index 88% rename from components/mobileshell/qml/osd/volume/VolumeOSD.qml rename to components/mobileshell/qml/volumeosd/VolumeOSD.qml index 40bbf3ad..37492749 100644 --- a/components/mobileshell/qml/osd/volume/VolumeOSD.qml +++ b/components/mobileshell/qml/volumeosd/VolumeOSD.qml @@ -6,10 +6,10 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ -import QtQuick 2.15 -import QtQuick.Controls 2.15 as Controls -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.2 +import QtQuick +import QtQuick.Controls as Controls +import QtQuick.Layouts +import QtQuick.Window import Qt5Compat.GraphicalEffects import org.kde.plasma.core 2.0 as PlasmaCore @@ -20,27 +20,30 @@ import org.kde.plasma.private.nanoshell 2.0 as NanoShell import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState -import org.kde.kirigami 2.12 as Kirigami +import org.kde.kirigami as Kirigami -// this is loaded and managed by indicators/providers/VolumeProvider.qml -NanoShell.FullScreenOverlay { +Window { id: window + + required property int volume + + // used by context menus opened in the applet to not autoclose the osd + property bool suppressActiveClose: false + + // whether the applet is showing all devices + property bool showFullApplet: false + visible: false + color: showFullApplet ? Qt.rgba(0, 0, 0, 0.6) : "transparent" - - property bool suppressActiveClose: false // used by context menus opened in the applet to not autoclose the osd - Behavior on color { ColorAnimation {} } - property int volume: 0 - property bool showFullApplet: false - function showOverlay() { if (!window.visible) { window.showFullApplet = false; - window.showMaximized(); + window.showFullScreen(); hideTimer.restart(); } else if (!window.showFullApplet) { // don't autohide applet when the full applet is showing hideTimer.restart(); @@ -63,15 +66,19 @@ NanoShell.FullScreenOverlay { window.showFullApplet = false; } } + + MobileShell.AudioInfo { + id: audioInfo + } Flickable { id: flickable anchors.fill: parent contentHeight: cards.implicitHeight boundsBehavior: window.showFullApplet ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds - + pressDelay: 50 - + MouseArea { // capture taps behind cards to close anchors.left: parent.left @@ -82,30 +89,30 @@ NanoShell.FullScreenOverlay { hideTimer.stop(); hideTimer.triggered(); } - + ColumnLayout { id: cards width: parent.width anchors.left: parent.left anchors.right: parent.right spacing: 0 - + // osd card PopupCard { id: osd Layout.topMargin: PlasmaCore.Units.largeSpacing Layout.alignment: Qt.AlignHCenter - + contentItem: RowLayout { id: containerLayout spacing: PlasmaCore.Units.smallSpacing anchors.leftMargin: PlasmaCore.Units.smallSpacing * 2 anchors.rightMargin: PlasmaCore.Units.smallSpacing - + PlasmaComponents.ToolButton { - icon.name: !paSinkModel.preferredSink || paSinkModel.preferredSink.muted ? "audio-volume-muted" : "audio-volume-high" - text: !paSinkModel.preferredSink || paSinkModel.preferredSink.muted ? i18n("Unmute") : i18n("Mute") + icon.name: !audioInfo.paSinkModel.preferredSink || audioInfo.paSinkModel.preferredSink.muted ? "audio-volume-muted" : "audio-volume-high" + text: !audioInfo.paSinkModel.preferredSink || audioInfo.paSinkModel.preferredSink.muted ? i18n("Unmute") : i18n("Mute") display: Controls.AbstractButton.IconOnly Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: PlasmaCore.Units.iconSizes.medium @@ -113,7 +120,7 @@ NanoShell.FullScreenOverlay { Layout.rightMargin: PlasmaCore.Units.smallSpacing onClicked: muteVolume() } - + PlasmaComponents.ProgressBar { id: volumeSlider Layout.fillWidth: true @@ -124,7 +131,7 @@ NanoShell.FullScreenOverlay { to: 100 Behavior on value { NumberAnimation { duration: PlasmaCore.Units.shortDuration } } } - + // Get the width of a three-digit number so we can size the label // to the maximum width to avoid the progress bar resizing itself TextMetrics { @@ -140,7 +147,7 @@ NanoShell.FullScreenOverlay { Layout.rightMargin: PlasmaCore.Units.smallSpacing level: 3 text: i18nc("Percentage value", "%1%", window.volume) - + // Display a subtle visual indication that the volume might be // dangerously high // ------------------------------------------------ @@ -156,7 +163,7 @@ NanoShell.FullScreenOverlay { } } } - + PlasmaComponents.ToolButton { icon.name: "configure" text: i18n("Open audio settings") @@ -167,16 +174,16 @@ NanoShell.FullScreenOverlay { Layout.preferredWidth: PlasmaCore.Units.iconSizes.medium Layout.preferredHeight: PlasmaCore.Units.iconSizes.medium Layout.rightMargin: PlasmaCore.Units.smallSpacing - + Behavior on opacity { NumberAnimation { duration: PlasmaCore.Units.shortDuration } } - + onClicked: { let coords = mapToItem(flickable, 0, 0); MobileShellState.Shell.openAppLaunchAnimation("audio-volume-high", i18n("Audio Settings"), coords.x, coords.y, PlasmaCore.Units.iconSizes.medium); MobileShell.ShellUtil.executeCommand("plasma-open-settings kcm_pulseaudio"); } } - + PlasmaComponents.ToolButton { icon.name: window.showFullApplet ? "arrow-up" : "arrow-down" text: i18n("Toggle showing audio streams") @@ -203,14 +210,16 @@ NanoShell.FullScreenOverlay { Layout.topMargin: PlasmaCore.Units.largeSpacing Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: cards.width - + + audioInfo: audioInfo + opacity: window.showFullApplet ? 1 : 0 visible: opacity !== 0 - transform: Translate { + transform: Translate { y: window.showFullApplet ? 0 : -PlasmaCore.Units.gridUnit Behavior on y { NumberAnimation {} } } - + Behavior on opacity { NumberAnimation {} } } } diff --git a/components/mobileshell/qml/osd/volume/icon.js b/components/mobileshell/qml/volumeosd/icon.js similarity index 100% rename from components/mobileshell/qml/osd/volume/icon.js rename to components/mobileshell/qml/volumeosd/icon.js diff --git a/components/mobileshell/resources.qrc b/components/mobileshell/resources.qrc index f67f6a6d..5cb84865 100644 --- a/components/mobileshell/resources.qrc +++ b/components/mobileshell/resources.qrc @@ -37,7 +37,7 @@ qml/dataproviders/BatteryInfo.qml qml/dataproviders/BluetoothInfo.qml qml/dataproviders/SignalStrengthInfo.qml - qml/dataproviders/AudioProvider.qml + qml/dataproviders/AudioInfo.qml qml/homescreen/HomeScreen.qml @@ -46,13 +46,13 @@ qml/navigationpanel/NavigationPanelAction.qml qml/navigationpanel/NavigationPanelButton.qml - qml/osd/volume/AudioApplet.qml - qml/osd/volume/DeviceListItem.qml - qml/osd/volume/icon.js - qml/osd/volume/ListItemBase.qml - qml/osd/volume/PopupCard.qml - qml/osd/volume/StreamListItem.qml - qml/osd/volume/VolumeOSD.qml + qml/volumeosd/AudioApplet.qml + qml/volumeosd/DeviceListItem.qml + qml/volumeosd/icon.js + qml/volumeosd/ListItemBase.qml + qml/volumeosd/PopupCard.qml + qml/volumeosd/StreamListItem.qml + qml/volumeosd/VolumeOSD.qml qml/statusbar/indicators/BatteryIndicator.qml qml/statusbar/indicators/BluetoothIndicator.qml diff --git a/components/mobileshellstate/README.md b/components/mobileshellstate/README.md new file mode 100644 index 00000000..3166de2e --- /dev/null +++ b/components/mobileshellstate/README.md @@ -0,0 +1,10 @@ + + +# MobileShell State Plugin + +This plugin stores the state of the mobile shell. + +It should ONLY be imported by components in the plasmashell process (otherwise it won't be able to get the state). diff --git a/components/mobileshellstate/mobileshellstateplugin.cpp b/components/mobileshellstate/mobileshellstateplugin.cpp index 44d459b7..8c9b9304 100644 --- a/components/mobileshellstate/mobileshellstateplugin.cpp +++ b/components/mobileshellstate/mobileshellstateplugin.cpp @@ -16,6 +16,7 @@ void MobileShellStatePlugin::registerTypes(const char *uri) Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.mobileshell.state")); // / + qmlRegisterSingletonType(resolvePath("AudioProvider.qml"), uri, 1, 0, "AudioProvider"); qmlRegisterSingletonType(resolvePath("HomeScreenControls.qml"), uri, 1, 0, "HomeScreenControls"); qmlRegisterSingletonType(resolvePath("Shell.qml"), uri, 1, 0, "Shell"); qmlRegisterSingletonType(resolvePath("TopPanelControls.qml"), uri, 1, 0, "TopPanelControls"); diff --git a/components/mobileshellstate/qml/AudioProvider.qml b/components/mobileshellstate/qml/AudioProvider.qml new file mode 100644 index 00000000..2584af1b --- /dev/null +++ b/components/mobileshellstate/qml/AudioProvider.qml @@ -0,0 +1,98 @@ +/* + 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/qml/HomeScreenControls.qml b/components/mobileshellstate/qml/HomeScreenControls.qml index 530148c0..cf68a416 100644 --- a/components/mobileshellstate/qml/HomeScreenControls.qml +++ b/components/mobileshellstate/qml/HomeScreenControls.qml @@ -5,9 +5,6 @@ import QtQuick 2.12 import QtQuick.Window 2.2 -import org.kde.plasma.private.mobileshell as MobileShell -import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin - pragma Singleton /** @@ -26,15 +23,4 @@ QtObject { property var taskSwitcher property QtObject homeScreenWindow property bool taskSwitcherVisible: false - - // this state is updated from WindowUtil - property bool homeScreenVisible: true - - property var windowListener: Connections { - target: WindowPlugin.WindowUtil - - function onAllWindowsMinimizedChanged() { - root.homeScreenVisible = WindowPlugin.WindowUtil.allWindowsMinimized - } - } } diff --git a/components/mobileshellstate/qml/Shell.qml b/components/mobileshellstate/qml/Shell.qml index 11db8c99..cd35efaf 100644 --- a/components/mobileshellstate/qml/Shell.qml +++ b/components/mobileshellstate/qml/Shell.qml @@ -12,11 +12,6 @@ pragma Singleton QtObject { id: delegate - /** - * Whether the homescreen is currently visible. - */ - readonly property bool homeScreenVisible: HomeScreenControls.homeScreenVisible - /** * Whether the action drawer is currently open. */ diff --git a/components/mobileshellstate/resources.qrc b/components/mobileshellstate/resources.qrc index 7695d3fc..728f27b2 100644 --- a/components/mobileshellstate/resources.qrc +++ b/components/mobileshellstate/resources.qrc @@ -7,6 +7,7 @@ qml/HomeScreenControls.qml qml/Shell.qml qml/TopPanelControls.qml + qml/AudioProvider.qml diff --git a/containments/homescreens/folio/package/contents/ui/main.qml b/containments/homescreens/folio/package/contents/ui/main.qml index a74d2a7e..cf3d8cdc 100644 --- a/containments/homescreens/folio/package/contents/ui/main.qml +++ b/containments/homescreens/folio/package/contents/ui/main.qml @@ -16,6 +16,7 @@ import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState import org.kde.private.mobile.homescreen.folio 1.0 as Folio +import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin MobileShell.HomeScreen { id: root @@ -43,7 +44,7 @@ MobileShell.HomeScreen { // - minimize windows (only if we are in an app) // - open app drawer // - close app drawer and, if necessary, restore windows - if (!plasmoid.nativeInterface.showingDesktop && !MobileShellState.Shell.homeScreenVisible + if (!plasmoid.nativeInterface.showingDesktop && !WindowPlugin.WindowUtil.allWindowsMinimized || MobileShellState.Shell.actionDrawerVisible || searchWidget.isOpen ) { diff --git a/containments/homescreens/halcyon/package/contents/ui/HomeScreen.qml b/containments/homescreens/halcyon/package/contents/ui/HomeScreen.qml index e909768e..9169adf6 100644 --- a/containments/homescreens/halcyon/package/contents/ui/HomeScreen.qml +++ b/containments/homescreens/halcyon/package/contents/ui/HomeScreen.qml @@ -14,6 +14,7 @@ import org.kde.draganddrop as DragDrop import org.kde.kirigami as Kirigami import org.kde.plasma.private.mobileshell.state as MobileShellState +import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin Item { id: root @@ -45,7 +46,7 @@ Item { target: MobileShellState.HomeScreenControls function onHomeScreenVisibleChanged(){ - if (MobileShellState.HomeScreenControls.homeScreenVisible) { + if (WindowPlugin.WindowUtil.allWindowsMinimized) { swipeView.focusChild(); } } diff --git a/containments/homescreens/halcyon/package/contents/ui/main.qml b/containments/homescreens/halcyon/package/contents/ui/main.qml index 0574691b..8b6ef4a4 100644 --- a/containments/homescreens/halcyon/package/contents/ui/main.qml +++ b/containments/homescreens/halcyon/package/contents/ui/main.qml @@ -46,9 +46,7 @@ MobileShell.HomeScreen { // - minimize windows (only if we are in an app) // - open app drawer // - close app drawer and, if necessary, restore windows - if (!WindowPlugin.WindowUtil.showDesktop && !MobileShellState.Shell.homeScreenVisible - || search.isOpen - ) { + if (!WindowPlugin.WindowUtil.showDesktop && !WindowPlugin.WindowUtil.allWindowsMinimized || search.isOpen) { // Always close action drawer if (MobileShellState.Shell.actionDrawerVisible) { MobileShellState.Shell.closeActionDrawer(); diff --git a/containments/panel/package/contents/ui/main.qml b/containments/panel/package/contents/ui/main.qml index 66ddcd5f..ad9debfd 100644 --- a/containments/panel/package/contents/ui/main.qml +++ b/containments/panel/package/contents/ui/main.qml @@ -90,7 +90,7 @@ Item { Component.onCompleted: { // we want to bind global volume shortcuts here - MobileShell.AudioProvider.bindShortcuts = true; + MobileShellState.AudioProvider.bindShortcuts = true; } TaskManager.VirtualDesktopInfo { diff --git a/quicksettings/audio/contents/ui/main.qml b/quicksettings/audio/contents/ui/main.qml index 431d3311..099501ac 100644 --- a/quicksettings/audio/contents/ui/main.qml +++ b/quicksettings/audio/contents/ui/main.qml @@ -1,17 +1,18 @@ // SPDX-FileCopyrightText: 2022 Devin Lin // SPDX-License-Identifier: LGPL-2.0-or-later -import QtQuick 2.15 +import QtQuick -import org.kde.plasma.private.mobileshell 1.0 as MobileShell +import org.kde.plasma.private.mobileshell as MobileShell +import org.kde.plasma.private.mobileshell.state as MobileShellState MobileShell.QuickSetting { text: i18n("Sound") icon: "audio-speakers-symbolic" - status: i18n("%1%", MobileShell.AudioProvider.volumeValue) + status: i18n("%1%", MobileShellState.AudioProvider.volumeValue) enabled: false settingsCommand: "plasma-open-settings kcm_pulseaudio" function toggle() { - MobileShell.AudioProvider.showVolumeOverlay() + MobileShellState.AudioProvider.showVolumeOverlay() } }