diff --git a/applets/mediacontroller/contents/ui/main.qml b/applets/mediacontroller/contents/ui/main.qml index 66257447f..d64ec281c 100644 --- a/applets/mediacontroller/contents/ui/main.qml +++ b/applets/mediacontroller/contents/ui/main.qml @@ -1,272 +1,281 @@ /*************************************************************************** * Copyright 2013 Sebastian Kügler * * Copyright 2014 Kai Uwe Broulik * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.0 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras Item { id: root property var currentMetadata: mpris2Source.currentData ? mpris2Source.currentData.Metadata : undefined property string track: { if (!currentMetadata) { return "" } var xesamTitle = currentMetadata["xesam:title"] if (xesamTitle) { return xesamTitle } // if no track title is given, print out the file name var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" if (!xesamUrl) { return "" } var lastSlashPos = xesamUrl.lastIndexOf('/') if (lastSlashPos < 0) { return "" } var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) return decodeURIComponent(lastUrlPart) } property string artist: currentMetadata ? currentMetadata["xesam:artist"] || "" : "" property string albumArt: currentMetadata ? currentMetadata["mpris:artUrl"] || "" : "" readonly property string identity: !root.noPlayer ? mpris2Source.currentData.Identity || mpris2Source.current : "" property bool noPlayer: mpris2Source.sources.length <= 1 readonly property bool canControl: !root.noPlayer && mpris2Source.currentData.CanControl readonly property bool canGoPrevious: canControl && mpris2Source.currentData.CanGoPrevious readonly property bool canGoNext: canControl && mpris2Source.currentData.CanGoNext readonly property bool canPlay: canControl && mpris2Source.currentData.CanPlay readonly property bool canPause: canControl && mpris2Source.currentData.CanPause Plasmoid.switchWidth: units.gridUnit * 14 Plasmoid.switchHeight: units.gridUnit * 10 Plasmoid.icon: albumArt ? albumArt : "media-playback-playing" Plasmoid.toolTipMainText: i18n("No media playing") Plasmoid.toolTipSubText: identity Plasmoid.toolTipTextFormat: Text.PlainText Plasmoid.status: PlasmaCore.Types.PassiveStatus Plasmoid.onContextualActionsAboutToShow: { plasmoid.clearActions() if (root.noPlayer) { return } if (mpris2Source.currentData.CanRaise) { var icon = mpris2Source.currentData["Desktop Icon Name"] || "" plasmoid.setAction("open", i18nc("Open player window or bring it to the front if already open", "Open"), icon) } if (canControl) { plasmoid.setAction("previous", i18nc("Play previous track", "Previous Track"), Qt.application.layoutDirection === Qt.RightToLeft ? "media-skip-forward" : "media-skip-backward"); plasmoid.action("previous").enabled = Qt.binding(function() { return root.canGoPrevious }) // if CanPause, toggle the menu entry between Play & Pause, otherwise always use Play if (root.state == "playing" && root.canPause) { plasmoid.setAction("pause", i18nc("Pause playback", "Pause"), "media-playback-pause") plasmoid.action("pause").enabled = Qt.binding(function() { return root.state === "playing" && root.canPause; }); } else { plasmoid.setAction("play", i18nc("Start playback", "Play"), "media-playback-start") plasmoid.action("play").enabled = Qt.binding(function() { return root.state !== "playing" && root.canPlay; }); } plasmoid.setAction("next", i18nc("Play next track", "Next Track"), Qt.application.layoutDirection === Qt.RightToLeft ? "media-skip-backward" : "media-skip-forward") plasmoid.action("next").enabled = Qt.binding(function() { return root.canGoNext }) plasmoid.setAction("stop", i18nc("Stop playback", "Stop"), "media-playback-stop") plasmoid.action("stop").enabled = Qt.binding(function() { return root.state === "playing" || root.state === "paused"; }) } if (mpris2Source.currentData.CanQuit) { plasmoid.setActionSeparator("quitseparator"); plasmoid.setAction("quit", i18nc("Quit player", "Quit"), "application-exit") } } // HACK Some players like Amarok take quite a while to load the next track // this avoids having the plasmoid jump between popup and panel onStateChanged: { if (state != "") { plasmoid.status = PlasmaCore.Types.ActiveStatus } else { updatePlasmoidStatusTimer.restart() } } Timer { id: updatePlasmoidStatusTimer interval: 3000 onTriggered: { if (state != "") { plasmoid.status = PlasmaCore.Types.ActiveStatus } else { plasmoid.status = PlasmaCore.Types.PassiveStatus } } } Plasmoid.fullRepresentation: ExpandedRepresentation {} Plasmoid.compactRepresentation: PlasmaCore.IconItem { source: root.state === "playing" ? "media-playback-playing" : root.state === "paused" ? "media-playback-paused" : "media-playback-stopped" active: compactMouse.containsMouse MouseArea { id: compactMouse anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton + + onWheel: { + var service = mpris2Source.serviceForSource(mpris2Source.current) + var operation = service.operationDescription("ChangeVolume") + operation.delta = (wheel.angleDelta.y / 120) * 0.03 + operation.showOSD = true + service.startOperationCall(operation) + } + onClicked: { switch (mouse.button) { case Qt.MiddleButton: root.togglePlaying() break case Qt.BackButton: root.action_previous() break case Qt.ForwardButton: root.action_next() break default: plasmoid.expanded = !plasmoid.expanded } } } } PlasmaCore.DataSource { id: mpris2Source readonly property string multiplexSource: "@multiplex" property string current: multiplexSource readonly property var currentData: data[current] engine: "mpris2" connectedSources: current onSourceRemoved: { // if player is closed, reset to multiplex source if (source === current) { current = multiplexSource } } } Component.onCompleted: { mpris2Source.serviceForSource("@multiplex").enableGlobalShortcuts(); } function togglePlaying() { if (root.state === "playing") { if (root.canPause) { root.action_pause(); } } else { if (root.canPlay) { root.action_play(); } } } function action_open() { serviceOp(mpris2Source.current, "Raise"); } function action_quit() { serviceOp(mpris2Source.current, "Quit"); } function action_play() { serviceOp(mpris2Source.current, "Play"); } function action_pause() { serviceOp(mpris2Source.current, "Pause"); } function action_playPause() { serviceOp(mpris2Source.current, "PlayPause"); } function action_previous() { serviceOp(mpris2Source.current, "Previous"); } function action_next() { serviceOp(mpris2Source.current, "Next"); } function action_stop() { serviceOp(mpris2Source.current, "Stop"); } function serviceOp(src, op) { var service = mpris2Source.serviceForSource(src); var operation = service.operationDescription(op); return service.startOperationCall(operation); } states: [ State { name: "playing" when: !root.noPlayer && mpris2Source.currentData.PlaybackStatus === "Playing" PropertyChanges { target: plasmoid icon: albumArt ? albumArt : "media-playback-playing" toolTipMainText: track toolTipSubText: artist ? i18nc("by Artist (player name)", "by %1 (%2)", artist, identity) : identity } }, State { name: "paused" when: !root.noPlayer && mpris2Source.currentData.PlaybackStatus === "Paused" PropertyChanges { target: plasmoid icon: albumArt ? albumArt : "media-playback-paused" toolTipMainText: track toolTipSubText: artist ? i18nc("by Artist (paused, player name)", "by %1 (paused, %2)", artist, identity) : i18nc("Paused (player name)", "Paused (%1)", identity) } } ] } diff --git a/dataengines/mpris2/mpris2.operations b/dataengines/mpris2/mpris2.operations index 3dce59c70..5e5205c53 100644 --- a/dataengines/mpris2/mpris2.operations +++ b/dataengines/mpris2/mpris2.operations @@ -1,50 +1,58 @@ + + + + + + + + diff --git a/dataengines/mpris2/multiplexedservice.cpp b/dataengines/mpris2/multiplexedservice.cpp index 75fe1ee8e..b5f3992e4 100644 --- a/dataengines/mpris2/multiplexedservice.cpp +++ b/dataengines/mpris2/multiplexedservice.cpp @@ -1,150 +1,172 @@ /* * Copyright 2012 Alex Merry * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ #include "multiplexedservice.h" #include "multiplexer.h" #include "playercontrol.h" #include #include #include #include MultiplexedService::MultiplexedService(Multiplexer *multiplexer, QObject *parent) : Plasma::Service(parent) { setObjectName(Multiplexer::sourceName + QLatin1String(" controller")); setName(QStringLiteral("mpris2")); setDestination(Multiplexer::sourceName); connect(multiplexer, &Multiplexer::activePlayerChanged, this, &MultiplexedService::activePlayerChanged); activePlayerChanged(multiplexer->activePlayer()); } Plasma::ServiceJob* MultiplexedService::createJob(const QString& operation, QMap& parameters) { if (m_control) { return m_control.data()->createJob(operation, parameters); } return nullptr; } void MultiplexedService::updateEnabledOperations() { if (m_control) { foreach (const QString &op, operationNames()) { setOperationEnabled(op, m_control.data()->isOperationEnabled(op)); } } else { foreach (const QString &op, operationNames()) { setOperationEnabled(op, false); } } } void MultiplexedService::activePlayerChanged(PlayerContainer *container) { delete m_control.data(); if (container) { m_control = new PlayerControl(container, container->getDataEngine()); connect(m_control.data(), &PlayerControl::enabledOperationsChanged, this, &MultiplexedService::updateEnabledOperations); } updateEnabledOperations(); } void MultiplexedService::enableGlobalShortcuts() { if (m_actionCollection) { return; } m_actionCollection = new KActionCollection(this, QStringLiteral("mediacontrol")); m_actionCollection->setComponentDisplayName(i18nc("Name for global shortcuts category", "Media Controller")); QAction *playPauseAction = m_actionCollection->addAction(QStringLiteral("playpausemedia")); playPauseAction->setText(i18n("Play/Pause media playback")); KGlobalAccel::setGlobalShortcut(playPauseAction, Qt::Key_MediaPlay); connect(playPauseAction, &QAction::triggered, this, [this] { if (m_control) { auto playerInterface = m_control->playerInterface(); if (playerInterface->canControl()) { if (playerInterface->playbackStatus() == QLatin1String("Playing")) { if (playerInterface->canPause()) { playerInterface->Pause(); } } else { if (playerInterface->canPlay()) { playerInterface->Play(); } } } } } ); QAction *nextAction = m_actionCollection->addAction(QStringLiteral("nextmedia")); nextAction->setText(i18n("Media playback next")); KGlobalAccel::setGlobalShortcut(nextAction, Qt::Key_MediaNext); connect(nextAction, &QAction::triggered, this, [this] { if (m_control) { auto playerInterface = m_control->playerInterface(); if (playerInterface->canControl() && playerInterface->canGoNext()) { playerInterface->Next(); } } } ); QAction *previousAction = m_actionCollection->addAction(QStringLiteral("previousmedia")); previousAction->setText(i18n("Media playback previous")); KGlobalAccel::setGlobalShortcut(previousAction, Qt::Key_MediaPrevious); connect(previousAction, &QAction::triggered, this, [this] { if (m_control) { auto playerInterface = m_control->playerInterface(); if (playerInterface->canControl() && playerInterface->canGoPrevious()) { playerInterface->Previous(); } } } ); QAction *stopAction = m_actionCollection->addAction(QStringLiteral("stopmedia")); stopAction->setText(i18n("Stop media playback")); KGlobalAccel::setGlobalShortcut(stopAction, Qt::Key_MediaStop); connect(stopAction, &QAction::triggered, this, [this] { if (m_control) { auto playerInterface = m_control->playerInterface(); if (playerInterface->canControl()) { playerInterface->Stop(); } } } ); + + QAction *volumeupAction = m_actionCollection->addAction(QStringLiteral("mediavolumeup")); + volumeupAction->setText(i18n("Media volume up")); + KGlobalAccel::setGlobalShortcut(volumeupAction, QKeySequence()); + connect(volumeupAction, &QAction::triggered, this, + [this] { + if (m_control && m_control->playerInterface()->canControl()) { + m_control->changeVolume(0.05, true); + } + } + ); + + QAction *volumedownAction = m_actionCollection->addAction(QStringLiteral("mediavolumedown")); + volumedownAction->setText(i18n("Media volume down")); + KGlobalAccel::setGlobalShortcut(volumedownAction, QKeySequence()); + connect(volumedownAction, &QAction::triggered, this, + [this] { + if (m_control && m_control->playerInterface()->canControl()) { + m_control->changeVolume(-0.05, true); + } + } + ); } diff --git a/dataengines/mpris2/playeractionjob.cpp b/dataengines/mpris2/playeractionjob.cpp index 8e91ea368..936f9adc3 100644 --- a/dataengines/mpris2/playeractionjob.cpp +++ b/dataengines/mpris2/playeractionjob.cpp @@ -1,194 +1,214 @@ /* * Copyright 2008 Alex Merry * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ #include "playeractionjob.h" #include #include #include #define TRANSLATION_DOMAIN "plasma_engine_mpris2" #include #include #include #include #include "debug.h" PlayerActionJob::PlayerActionJob(const QString& operation, QMap& parameters, PlayerControl* parent) : ServiceJob(parent->name() + ": " + operation, operation, parameters, parent) , m_controller(parent) { } void PlayerActionJob::start() { const QString operation(operationName()); if (!m_controller) { setError(Failed); emitResult(); return; } qCDebug(MPRIS2) << "Trying to perform the action" << operationName(); if (!m_controller->isOperationEnabled(operation)) { setError(Denied); emitResult(); return; } if (operation == QLatin1String("Quit") || operation == QLatin1String("Raise") || operation == QLatin1String("SetFullscreen")) { listenToCall(m_controller->rootInterface()->asyncCall(operation)); } else if (operation == QLatin1String("Play") || operation == QLatin1String("Pause") || operation == QLatin1String("PlayPause") || operation == QLatin1String("Stop") || operation == QLatin1String("Next") || operation == QLatin1String("Previous")) { listenToCall(m_controller->playerInterface()->asyncCall(operation)); } else if (operation == QLatin1String("Seek")) { if (parameters().value(QStringLiteral("microseconds")).canConvert()) { listenToCall(m_controller->playerInterface()->Seek(parameters()[QStringLiteral("microseconds")].toLongLong())); } else { setErrorText(QStringLiteral("microseconds")); setError(MissingArgument); emitResult(); } } else if (operation == QLatin1String("SetPosition")) { if (parameters().value(QStringLiteral("microseconds")).canConvert()) { listenToCall(m_controller->playerInterface()->SetPosition( m_controller->trackId(), parameters()[QStringLiteral("microseconds")].toLongLong())); } else { setErrorText(QStringLiteral("microseconds")); setError(MissingArgument); emitResult(); } } else if (operation == QLatin1String("OpenUri")) { if (parameters().value(QStringLiteral("uri")).canConvert()) { listenToCall(m_controller->playerInterface()->OpenUri( QString::fromLatin1(parameters()[QStringLiteral("uri")].toUrl().toEncoded()))); } else { qCDebug(MPRIS2) << "uri was of type" << parameters().value(QStringLiteral("uri")).userType(); setErrorText(QStringLiteral("uri")); setError(MissingArgument); emitResult(); } } else if (operation == QLatin1String("SetLoopStatus")) { if (parameters().value(QStringLiteral("status")).type() == QVariant::String) { setDBusProperty(m_controller->playerInterface()->interface(), QStringLiteral("LoopStatus"), QDBusVariant(parameters()[QStringLiteral("status")])); } else { setErrorText(QStringLiteral("status")); setError(MissingArgument); emitResult(); } } else if (operation == QLatin1String("SetShuffle")) { if (parameters().value(QStringLiteral("on")).type() == QVariant::Bool) { setDBusProperty(m_controller->playerInterface()->interface(), QStringLiteral("Shuffle"), QDBusVariant(parameters()[QStringLiteral("on")])); } else { setErrorText(QStringLiteral("on")); setError(MissingArgument); emitResult(); } } else if (operation == QLatin1String("SetRate")) { if (parameters().value(QStringLiteral("rate")).type() == QVariant::Double) { setDBusProperty(m_controller->playerInterface()->interface(), QStringLiteral("Rate"), QDBusVariant(parameters()[QStringLiteral("rate")])); } else { setErrorText(QStringLiteral("rate")); setError(MissingArgument); emitResult(); } } else if (operation == QLatin1String("SetVolume")) { if (parameters().value(QStringLiteral("level")).type() == QVariant::Double) { setDBusProperty(m_controller->playerInterface()->interface(), QStringLiteral("Volume"), QDBusVariant(parameters()[QStringLiteral("level")])); } else { setErrorText(QStringLiteral("level")); setError(MissingArgument); emitResult(); } + } else if (operation == QLatin1String("ChangeVolume")) { + if (parameters().value(QStringLiteral("delta")).type() != QVariant::Double) { + setErrorText(QStringLiteral("delta")); + setError(MissingArgument); + emitResult(); + return; + } + if (parameters().value(QStringLiteral("showOSD")).type() != QVariant::Bool) { + setErrorText(QStringLiteral("showOSD")); + setError(MissingArgument); + emitResult(); + return; + } + + m_controller->changeVolume( + parameters()[QStringLiteral("delta")].toDouble(), + parameters()[QStringLiteral("showOSD")].toBool() + ); + setError(NoError); + emitResult(); } else if (operation == QLatin1String("GetPosition")) { m_controller->updatePosition(); } else { setError(UnknownOperation); emitResult(); } } void PlayerActionJob::listenToCall(const QDBusPendingCall& call) { QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &PlayerActionJob::callFinished); } void PlayerActionJob::callFinished(QDBusPendingCallWatcher* watcher) { QDBusPendingReply result = *watcher; watcher->deleteLater(); if (result.isError()) { // FIXME: try to be a bit cleverer with the error message? setError(Failed); setErrorText(result.error().message()); } else { setError(NoError); } emitResult(); } void PlayerActionJob::setDBusProperty(const QString& iface, const QString& propName, const QDBusVariant& value) { if (!m_controller) { setError(Failed); emitResult(); return; } listenToCall( m_controller->propertiesInterface()->Set(iface, propName, value) ); } QString PlayerActionJob::errorString() const { if (error() == Denied) { const QString name = m_controller ? m_controller->name() : QString(); return i18n("The media player '%1' cannot perform the action '%2'.", name, operationName()); } else if (error() == Failed) { return i18n("Attempting to perform the action '%1' failed with the message '%2'.", operationName(), errorText()); } else if (error() == MissingArgument) { return i18n("The argument '%1' for the action '%2' is missing or of the wrong type.", operationName(), errorText()); } else if (error() == UnknownOperation) { return i18n("The operation '%1' is unknown.", operationName()); } return i18n("Unknown error."); } // vim: sw=4 sts=4 et tw=100 diff --git a/dataengines/mpris2/playercontrol.cpp b/dataengines/mpris2/playercontrol.cpp index 2adad62b2..0cfefcc95 100644 --- a/dataengines/mpris2/playercontrol.cpp +++ b/dataengines/mpris2/playercontrol.cpp @@ -1,100 +1,126 @@ /* * Copyright 2008 Alex Merry * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ #include "playercontrol.h" #include "playeractionjob.h" #include "playercontainer.h" #include #include #include #include PlayerControl::PlayerControl(PlayerContainer* container, QObject* parent) : Plasma::Service(parent) , m_container(container) { setObjectName(container->objectName() + QLatin1String(" controller")); setName(QStringLiteral("mpris2")); setDestination(container->objectName()); connect(container, &Plasma::DataContainer::dataUpdated, this, &PlayerControl::updateEnabledOperations); connect(container, &QObject::destroyed, this, &PlayerControl::containerDestroyed); updateEnabledOperations(); } void PlayerControl::updateEnabledOperations() { PlayerContainer::Caps caps = PlayerContainer::NoCaps; if (m_container) caps = m_container->capabilities(); setOperationEnabled(QStringLiteral("Quit"), caps & PlayerContainer::CanQuit); setOperationEnabled(QStringLiteral("Raise"), caps & PlayerContainer::CanRaise); setOperationEnabled(QStringLiteral("SetFullscreen"), caps & PlayerContainer::CanSetFullscreen); setOperationEnabled(QStringLiteral("Play"), caps & PlayerContainer::CanPlay); setOperationEnabled(QStringLiteral("Pause"), caps & PlayerContainer::CanPause); setOperationEnabled(QStringLiteral("PlayPause"), caps & (PlayerContainer::CanPlay | PlayerContainer::CanPause)); setOperationEnabled(QStringLiteral("Stop"), caps & PlayerContainer::CanStop); setOperationEnabled(QStringLiteral("Next"), caps & PlayerContainer::CanGoNext); setOperationEnabled(QStringLiteral("Previous"), caps & PlayerContainer::CanGoPrevious); setOperationEnabled(QStringLiteral("Seek"), caps & PlayerContainer::CanSeek); setOperationEnabled(QStringLiteral("SetPosition"), caps & PlayerContainer::CanSeek); setOperationEnabled(QStringLiteral("OpenUri"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("SetVolume"), caps & PlayerContainer::CanControl); + setOperationEnabled(QStringLiteral("ChangeVolume"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("SetLoopStatus"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("SetRate"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("SetShuffle"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("GetPosition"), true); emit enabledOperationsChanged(); } QDBusObjectPath PlayerControl::trackId() const { QVariant mprisTrackId = m_container->data().value(QStringLiteral("Metadata")).toMap().value(QStringLiteral("mpris:trackid")); if (mprisTrackId.canConvert()) { return mprisTrackId.value(); } QString mprisTrackIdString = mprisTrackId.toString(); if (!mprisTrackIdString.isEmpty()) { return QDBusObjectPath(mprisTrackIdString); } return QDBusObjectPath(); } void PlayerControl::containerDestroyed() { m_container = nullptr; } +void PlayerControl::changeVolume(double delta, bool showOSD) { + const double volume = playerInterface()->volume(); + const double newVolume = qBound(0.0, volume + delta, qMax(volume, 1.0)); + playerInterface()->setVolume(newVolume); + + if (showOSD) { + const auto& data = m_container->data(); + + QDBusMessage msg = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.plasmashell"), + QStringLiteral("/org/kde/osdService"), + QStringLiteral("org.kde.osdService"), + QStringLiteral("mediaPlayerVolumeChanged") + ); + + msg.setArguments({ + (int)(100 * newVolume), + data.value("Identity", ""), + data.value("Desktop Icon Name", "") + }); + + QDBusConnection::sessionBus().asyncCall(msg); + } +} + Plasma::ServiceJob* PlayerControl::createJob(const QString& operation, QMap& parameters) { if (!m_container) return nullptr; return new PlayerActionJob(operation, parameters, this); } // vim: sw=4 sts=4 et tw=100 diff --git a/dataengines/mpris2/playercontrol.h b/dataengines/mpris2/playercontrol.h index 861a064e2..3b172fb3d 100644 --- a/dataengines/mpris2/playercontrol.h +++ b/dataengines/mpris2/playercontrol.h @@ -1,63 +1,65 @@ /* * Copyright 2008 Alex Merry * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ #ifndef PLAYERCONTROL_H #define PLAYERCONTROL_H #include "playercontainer.h" #include #include class OrgFreedesktopDBusPropertiesInterface; class OrgMprisMediaPlayer2Interface; class OrgMprisMediaPlayer2PlayerInterface; class PlayerControl : public Plasma::Service { Q_OBJECT public: PlayerControl(PlayerContainer* container, QObject* parent); OrgMprisMediaPlayer2Interface* rootInterface() const { return m_container->rootInterface(); } OrgMprisMediaPlayer2PlayerInterface* playerInterface() const { return m_container->playerInterface(); } OrgFreedesktopDBusPropertiesInterface* propertiesInterface() const { return m_container->propertiesInterface(); } void updatePosition() const { m_container->updatePosition(); } QDBusObjectPath trackId() const; Plasma::ServiceJob* createJob(const QString& operation, QMap& parameters) override; + void changeVolume(double delta, bool showOSD); + Q_SIGNALS: void enabledOperationsChanged(); private Q_SLOTS: void updateEnabledOperations(); void containerDestroyed(); private: PlayerContainer *m_container; }; #endif // PLAYERCONTROL_H