diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,7 @@ set(GWENVIEW_SEMANTICINFO_BACKEND_BALOO ON) endif() -find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Widgets Concurrent Svg OpenGL PrintSupport) +find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Widgets Concurrent DBus Svg OpenGL PrintSupport) find_package(Phonon4Qt5 4.6.60 NO_MODULE REQUIRED) include_directories(BEFORE ${PHONON_INCLUDES}) diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -88,6 +88,7 @@ #include #include #include +#include #include #include #include @@ -166,6 +167,7 @@ SaveBar* mSaveBar; bool mStartSlideShowWhenDirListerCompleted; SlideShow* mSlideShow; + Mpris2Service* mMpris2Service; Preloader* mPreloader; bool mPreloadDirectionIsForward; #ifdef KIPI_FOUND @@ -810,6 +812,11 @@ d->setupUndoActions(); d->setupContextManagerItems(); d->setupFullScreenContent(); + + d->mMpris2Service = new Mpris2Service(d->mSlideShow, d->mContextManager, + d->mToggleSlideShowAction, d->mFullScreenAction, + d->mGoToPreviousAction, d->mGoToNextAction, this); + d->updateActions(); updatePreviousNextActions(); d->mSaveBar->initActionDependentWidgets(); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -139,6 +139,10 @@ semanticinfo/sorteddirmodel.cpp memoryutils.cpp mimetypeutils.cpp + mpris2/mpris2service.cpp + mpris2/dbusabstractadaptor.cpp + mpris2/mprismediaplayer2.cpp + mpris2/mprismediaplayer2player.cpp paintutils.cpp placetreemodel.cpp preferredimagemetainfomodel.cpp @@ -238,6 +242,7 @@ target_link_libraries(gwenviewlib Qt5::Concurrent + Qt5::DBus Qt5::Svg Qt5::OpenGL Qt5::PrintSupport diff --git a/lib/mpris2/dbusabstractadaptor.h b/lib/mpris2/dbusabstractadaptor.h new file mode 100644 --- /dev/null +++ b/lib/mpris2/dbusabstractadaptor.h @@ -0,0 +1,74 @@ +/* +Gwenview: an image viewer +Copyright 2018 Friedrich W. H. Kossebau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU 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 General Public License for more details. + +You should have received a copy of the GNU 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. +*/ + +#ifndef DBUSABSTRACTADAPTOR_H +#define DBUSABSTRACTADAPTOR_H + +// Qt +#include +#include + +namespace Gwenview +{ + +/** + * Extension of QDBusAbstractAdaptor for proper signalling of D-Bus object property changes + * + * QDBusAbstractAdaptor seems to fail on mapping QObject properties + * to D-Bus object properties when it comes to signalling changes to a property. + * The NOTIFY entry of Q_PROPERTY is not turned into respective D-Bus signalling of a + * property change. So we have to do this explicitly ourselves, instead of using a normal + * QObject signal and expecting the adaptor to translate it. + * + * To reduce D-Bus traffic, all registered property changes are accumulated and squashed + * between event loops where then the D-Bus signal is emitted. + */ +class DBusAbstractAdaptor : public QDBusAbstractAdaptor +{ + Q_OBJECT + +public: + /** + * Ideally we could query the D-Bus path of the object when used, but no idea yet how to do that. + * So one has to additionally pass here the D-Bus path at which the object is registered + * for which this interface is added. + * + * @param objectDBusPath D-Bus name of the property + * @param parent memory management parent or nullptr + */ + explicit DBusAbstractAdaptor(const QString &objectDBusPath, QObject *parent); + +protected: + /** + * @param propertyName D-Bus name of the property + * @param value the new value of the property + */ + void signalPropertyChange(const QString &propertyName, const QVariant &value); + +private Q_SLOTS: + void emitPropertiesChangeDBusSignal(); + +private: + QVariantMap mChangedProperties; + const QString mObjectPath; +}; + +} + +#endif // DBUSABSTRACTADAPTOR_H diff --git a/lib/mpris2/dbusabstractadaptor.cpp b/lib/mpris2/dbusabstractadaptor.cpp new file mode 100644 --- /dev/null +++ b/lib/mpris2/dbusabstractadaptor.cpp @@ -0,0 +1,74 @@ +/* +Gwenview: an image viewer +Copyright 2018 Friedrich W. H. Kossebau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU 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 General Public License for more details. + +You should have received a copy of the GNU 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. +*/ + +#include "dbusabstractadaptor.h" + +// Qt +#include +#include +#include + +namespace Gwenview +{ + +DBusAbstractAdaptor::DBusAbstractAdaptor(const QString &objectDBusPath, QObject *parent) + : QDBusAbstractAdaptor(parent) + , mObjectPath(objectDBusPath) +{ + Q_ASSERT(!mObjectPath.isEmpty()); +} + +void DBusAbstractAdaptor::signalPropertyChange(const QString &propertyName, const QVariant &value) +{ + const bool firstChange = mChangedProperties.isEmpty(); + + mChangedProperties.insert(propertyName, value); + + if (firstChange) { + // trigger signal emission on next event loop + QMetaObject::invokeMethod(this, "emitPropertiesChangeDBusSignal", Qt::QueuedConnection); + } +} + + +void DBusAbstractAdaptor::emitPropertiesChangeDBusSignal() +{ + if (mChangedProperties.isEmpty()) { + return; + } + + const QMetaObject* metaObject = this->metaObject(); + const int dBusInterfaceNameIndex = metaObject->indexOfClassInfo("D-Bus Interface"); + Q_ASSERT(dBusInterfaceNameIndex >= 0); + const char* dBusInterfaceName = metaObject->classInfo(dBusInterfaceNameIndex).value(); + + QDBusMessage signalMessage = QDBusMessage::createSignal(mObjectPath, + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("PropertiesChanged")); + signalMessage + << dBusInterfaceName + << mChangedProperties + << QStringList(); + + QDBusConnection::sessionBus().send(signalMessage); + + mChangedProperties.clear(); +} + +} diff --git a/lib/mpris2/mpris2service.h b/lib/mpris2/mpris2service.h new file mode 100644 --- /dev/null +++ b/lib/mpris2/mpris2service.h @@ -0,0 +1,51 @@ +/* +Gwenview: an image viewer +Copyright 2018 Friedrich W. H. Kossebau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU 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 General Public License for more details. + +You should have received a copy of the GNU 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. +*/ + +#ifndef MPRIS2SERVICE_H +#define MPRIS2SERVICE_H + +#include +// Qt +#include +#include + +class QAction; + +namespace Gwenview +{ +class SlideShow; +class ContextManager; + +class GWENVIEWLIB_EXPORT Mpris2Service : public QObject +{ + Q_OBJECT + +public: + Mpris2Service(SlideShow* slideShow, ContextManager* contextManager, + QAction* toggleSlideShowAction, QAction* fullScreenAction, + QAction* previousAction, QAction* nextAction, QObject* parent); + ~Mpris2Service() override; + +private: + QString mMpris2ServiceName; +}; + +} + +#endif diff --git a/lib/mpris2/mpris2service.cpp b/lib/mpris2/mpris2service.cpp new file mode 100644 --- /dev/null +++ b/lib/mpris2/mpris2service.cpp @@ -0,0 +1,82 @@ +/* +Gwenview: an image viewer +Copyright 2018 Friedrich W. H. Kossebau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU 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 General Public License for more details. + +You should have received a copy of the GNU 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. +*/ + +#include "mpris2service.h" + +// lib +#include "mprismediaplayer2.h" +#include "mprismediaplayer2player.h" +#include +// Qt +#include +// std +#include + +namespace Gwenview +{ + +inline QString mediaPlayer2ObjectPath() { return QStringLiteral("/org/mpris/MediaPlayer2"); } + + +Mpris2Service::Mpris2Service(SlideShow* slideShow, ContextManager* contextManager, + QAction* toggleSlideShowAction, QAction* fullScreenAction, + QAction* previousAction, QAction* nextAction, + QObject* parent) + : QObject(parent) +{ + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + + // try to register MPRIS presentation object + // to be done before registering the service name, so it is already present + // when controllers react to the service name having appeared + new MprisMediaPlayer2(mediaPlayer2ObjectPath(), fullScreenAction, this); + new MprisMediaPlayer2Player(mediaPlayer2ObjectPath(), slideShow, contextManager, + toggleSlideShowAction, fullScreenAction, previousAction, nextAction, this); + + const bool objectRegistered = sessionBus.registerObject(mediaPlayer2ObjectPath(), this, QDBusConnection::ExportAdaptors); + + // try to register MPRIS presentation service + if (objectRegistered) { + mMpris2ServiceName = QStringLiteral("org.mpris.MediaPlayer2.Gwenview"); + + bool serviceRegistered = QDBusConnection::sessionBus().registerService(mMpris2ServiceName); + + // Perhaps not the first instance? Try again with another name, as specified by MPRIS2 spec: + if (!serviceRegistered) { + mMpris2ServiceName = mMpris2ServiceName + QLatin1String(".instance") + QString::number(getpid()); + serviceRegistered = QDBusConnection::sessionBus().registerService(mMpris2ServiceName); + } + if (!serviceRegistered) { + mMpris2ServiceName.clear(); + sessionBus.unregisterObject(mediaPlayer2ObjectPath()); + } + } +} + + +Mpris2Service::~Mpris2Service() +{ + if (!mMpris2ServiceName.isEmpty()) { + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + sessionBus.unregisterService(mMpris2ServiceName); + sessionBus.unregisterObject(mediaPlayer2ObjectPath()); + } +} + +} diff --git a/lib/mpris2/mprismediaplayer2.h b/lib/mpris2/mprismediaplayer2.h new file mode 100644 --- /dev/null +++ b/lib/mpris2/mprismediaplayer2.h @@ -0,0 +1,81 @@ +/* +Gwenview: an image viewer +Copyright 2018 Friedrich W. H. Kossebau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU 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 General Public License for more details. + +You should have received a copy of the GNU 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. +*/ + +#ifndef MPRISMEDIAPLAYER2_H +#define MPRISMEDIAPLAYER2_H + +#include "dbusabstractadaptor.h" +// Qt +#include + +class QAction; + +namespace Gwenview +{ + +// https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html +class MprisMediaPlayer2 : public DBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2") + + Q_PROPERTY(bool CanQuit READ canQuit CONSTANT) + Q_PROPERTY(bool CanRaise READ canRaise CONSTANT) + Q_PROPERTY(bool CanSetFullscreen READ canSetFullscreen CONSTANT) + + Q_PROPERTY(QString Identity READ identity CONSTANT) + Q_PROPERTY(QString DesktopEntry READ desktopEntry CONSTANT) + + Q_PROPERTY(bool HasTrackList READ hasTrackList CONSTANT) + Q_PROPERTY(bool Fullscreen READ isFullscreen WRITE setFullscreen) + + Q_PROPERTY(QStringList SupportedUriSchemes READ supportedUriSchemes CONSTANT) + Q_PROPERTY(QStringList SupportedMimeTypes READ supportedMimeTypes CONSTANT) + +public: + MprisMediaPlayer2(const QString &objectDBusPath, QAction* fullScreenAction, QObject* parent); + ~MprisMediaPlayer2() override; + +public Q_SLOTS: // D-Bus API + void Quit(); + void Raise(); + +private: + bool canQuit() const; + bool canRaise() const; + bool canSetFullscreen() const; + bool hasTrackList() const; + + QString identity() const; + QString desktopEntry() const; + bool isFullscreen() const; + void setFullscreen(bool isFullscreen); + + QStringList supportedUriSchemes() const; + QStringList supportedMimeTypes() const; + + void onFullScreenActionToggled(bool checked); + +private: + QAction* mFullScreenAction; +}; + +} + +#endif diff --git a/lib/mpris2/mprismediaplayer2.cpp b/lib/mpris2/mprismediaplayer2.cpp new file mode 100644 --- /dev/null +++ b/lib/mpris2/mprismediaplayer2.cpp @@ -0,0 +1,114 @@ +/* +Gwenview: an image viewer +Copyright 2018 Friedrich W. H. Kossebau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU 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 General Public License for more details. + +You should have received a copy of the GNU 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. +*/ + +#include "mprismediaplayer2.h" + +// Qt +#include +#include + +namespace Gwenview +{ + +MprisMediaPlayer2::MprisMediaPlayer2(const QString &objectDBusPath, + QAction* fullScreenAction, + QObject* parent) + : DBusAbstractAdaptor(objectDBusPath, parent) + , mFullScreenAction(fullScreenAction) +{ + connect(mFullScreenAction, &QAction::toggled, + this, &MprisMediaPlayer2::onFullScreenActionToggled); +} + +MprisMediaPlayer2::~MprisMediaPlayer2() +{ +} + +bool MprisMediaPlayer2::canQuit() const +{ + return true; +} + +bool MprisMediaPlayer2::canRaise() const +{ + // spec: "If true, calling Raise will cause the media application to attempt to bring its user interface to the front, + // Which seems to be about the app specific control UI, less about the rendered media (display). + // Could perhaps be supported by pulling in the toppanel when invoked? + return false; +} + +bool MprisMediaPlayer2::canSetFullscreen() const +{ + return true; +} + +bool MprisMediaPlayer2::hasTrackList() const +{ + return false; +} + +void MprisMediaPlayer2::Quit() +{ + QGuiApplication::quit(); +} + +void MprisMediaPlayer2::Raise() +{ +} + +QString MprisMediaPlayer2::identity() const +{ + return QGuiApplication::applicationDisplayName(); +} + +QString MprisMediaPlayer2::desktopEntry() const +{ + return QGuiApplication::desktopFileName(); +} + +QStringList MprisMediaPlayer2::supportedUriSchemes() const +{ + return QStringList(); +} + +QStringList MprisMediaPlayer2::supportedMimeTypes() const +{ + return QStringList(); +} + +bool MprisMediaPlayer2::isFullscreen() const +{ + return mFullScreenAction->isChecked(); +} + +void MprisMediaPlayer2::setFullscreen(bool isFullscreen) +{ + if (isFullscreen == mFullScreenAction->isChecked()) { + return; + } + + mFullScreenAction->trigger(); +} + +void MprisMediaPlayer2::onFullScreenActionToggled(bool checked) +{ + signalPropertyChange("Fullscreen", checked); +} + +} diff --git a/lib/mpris2/mprismediaplayer2player.h b/lib/mpris2/mprismediaplayer2player.h new file mode 100644 --- /dev/null +++ b/lib/mpris2/mprismediaplayer2player.h @@ -0,0 +1,124 @@ +/* +Gwenview: an image viewer +Copyright 2018 Friedrich W. H. Kossebau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU 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 General Public License for more details. + +You should have received a copy of the GNU 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. +*/ + +#ifndef MPRISMEDIAPLAYER2PLAYER_H +#define MPRISMEDIAPLAYER2PLAYER_H + +#include "dbusabstractadaptor.h" +// lib +#include + +class QDBusObjectPath; +class QAction; + +namespace Gwenview +{ +class SlideShow; +class ContextManager; + +// https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html +class MprisMediaPlayer2Player : public DBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2.Player") + + Q_PROPERTY(QString PlaybackStatus READ playbackStatus) + Q_PROPERTY(double Rate READ rate WRITE setRate) + Q_PROPERTY(QVariantMap Metadata READ metadata) + Q_PROPERTY(double Volume READ volume WRITE setVolume) + Q_PROPERTY(qlonglong Position READ position) + Q_PROPERTY(double MinimumRate READ minimumRate CONSTANT) + Q_PROPERTY(double MaximumRate READ maximumRate CONSTANT) + Q_PROPERTY(bool Shuffle READ isShuffle WRITE setShuffle) + + Q_PROPERTY(bool CanControl READ canControl CONSTANT) + Q_PROPERTY(bool CanPlay READ canPlay) + Q_PROPERTY(bool CanPause READ canPause) + Q_PROPERTY(bool CanGoNext READ canGoNext) + Q_PROPERTY(bool CanGoPrevious READ canGoPrevious) + Q_PROPERTY(bool CanSeek READ canSeek CONSTANT) + +public: + MprisMediaPlayer2Player(const QString &objectDBusPath, + SlideShow* slideShow, ContextManager* contextManager, + QAction* toggleSlideShowAction, QAction* fullScreenAction, + QAction* previousAction, QAction* nextAction, QObject* parent); + ~MprisMediaPlayer2Player() override; + +public Q_SLOTS: // D-Bus API + void Next(); + void Previous(); + void Pause(); + void PlayPause(); + void Stop(); + void Play(); + void Seek(qlonglong Offset); + void SetPosition(const QDBusObjectPath& trackId, qlonglong pos); + void OpenUri(const QString& uri); + +Q_SIGNALS: // D-Bus API + void Seeked(qlonglong Position) const; + +private: + QString playbackStatus() const; + double rate() const; + QVariantMap metadata() const; + double volume() const; + qlonglong position() const; + double minimumRate() const; + double maximumRate() const; + bool isShuffle() const; + + bool canGoNext() const; + bool canGoPrevious() const; + bool canPlay() const; + bool canPause() const; + bool canSeek() const; + bool canControl() const; + + void setRate(double newRate); + void setVolume(double volume); + void setShuffle(bool isShuffle); + + void onSlideShowStateChanged(bool running); + void onCurrentUrlChanged(const QUrl& url); + void onRandomActionToggled(bool checked); + void onToggleSlideShowActionChanged(); + void onPreviousActionChanged(); + void onNextActionChanged(); + void onMetaInfoUpdated(); + +private: + SlideShow* mSlideShow; + ContextManager* mContextManager; + QAction* mToggleSlideShowAction; + QAction* mFullScreenAction; + QAction* mPreviousAction; + QAction* mNextAction; + + bool mSlideShowEnabled; + bool mPreviousEnabled; + bool mNextEnabled; + QVariantMap mMetaData; + Document::Ptr mCurrentDocument; +}; + +} + +#endif diff --git a/lib/mpris2/mprismediaplayer2player.cpp b/lib/mpris2/mprismediaplayer2player.cpp new file mode 100644 --- /dev/null +++ b/lib/mpris2/mprismediaplayer2player.cpp @@ -0,0 +1,328 @@ +/* +Gwenview: an image viewer +Copyright 2018 Friedrich W. H. Kossebau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU 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 General Public License for more details. + +You should have received a copy of the GNU 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. +*/ + +#include "mprismediaplayer2player.h" + +// lib +#include +#include +#include +#include +#include +// KF +#include +// Qt +#include +#include +#include + +namespace Gwenview +{ + +static const double MAX_RATE = 1.0; +static const double MIN_RATE = 1.0; + +static inline +QString playbackStatusFromSlideShowState(bool running, bool enabled) +{ + return !enabled ? QStringLiteral("Stopped") : + running ? QStringLiteral("Playing") : + QStringLiteral("Paused"); +} + + +MprisMediaPlayer2Player::MprisMediaPlayer2Player(const QString &objectDBusPath, + SlideShow* slideShow, + ContextManager* contextManager, + QAction* toggleSlideShowAction, QAction* fullScreenAction, + QAction* previousAction, QAction* nextAction, + QObject* parent) + : DBusAbstractAdaptor(objectDBusPath, parent) + , mSlideShow(slideShow) + , mContextManager(contextManager) + , mToggleSlideShowAction(toggleSlideShowAction) + , mFullScreenAction(fullScreenAction) + , mPreviousAction(previousAction) + , mNextAction(nextAction) + , mSlideShowEnabled(mToggleSlideShowAction->isEnabled()) + , mPreviousEnabled(mPreviousAction->isEnabled()) + , mNextEnabled(mNextAction->isEnabled()) +{ + connect(mSlideShow, &SlideShow::stateChanged, + this, &MprisMediaPlayer2Player::onSlideShowStateChanged); + connect(mContextManager, &ContextManager::currentUrlChanged, + this, &MprisMediaPlayer2Player::onCurrentUrlChanged); + connect(mSlideShow->randomAction(), &QAction::toggled, + this, &MprisMediaPlayer2Player::onRandomActionToggled); + connect(mToggleSlideShowAction, &QAction::changed, + this, &MprisMediaPlayer2Player::onToggleSlideShowActionChanged); + connect(mNextAction, &QAction::changed, + this, &MprisMediaPlayer2Player::onNextActionChanged); + connect(mPreviousAction, &QAction::changed, + this, &MprisMediaPlayer2Player::onPreviousActionChanged); +} + +MprisMediaPlayer2Player::~MprisMediaPlayer2Player() +{ +} + +QString MprisMediaPlayer2Player::playbackStatus() const +{ + return playbackStatusFromSlideShowState(mSlideShow->isRunning(), mSlideShowEnabled); +} + +bool MprisMediaPlayer2Player::canGoNext() const +{ + return mNextEnabled; +} + +void MprisMediaPlayer2Player::Next() +{ + mNextAction->trigger(); +} + + +bool MprisMediaPlayer2Player::canGoPrevious() const +{ + return mPreviousEnabled; +} + +void MprisMediaPlayer2Player::Previous() +{ + mPreviousAction->trigger(); +} + +bool MprisMediaPlayer2Player::canPause() const +{ + return mSlideShowEnabled; +} + +void MprisMediaPlayer2Player::Pause() +{ + mSlideShow->stop(); +} + +void MprisMediaPlayer2Player::PlayPause() +{ + mToggleSlideShowAction->trigger(); +} + +void MprisMediaPlayer2Player::Stop() +{ + if (mFullScreenAction->isChecked()) { + mFullScreenAction->trigger(); + } +} + +bool MprisMediaPlayer2Player::canPlay() const +{ + return mSlideShowEnabled; +} + +void MprisMediaPlayer2Player::Play() +{ + if (mSlideShow->isRunning()) { + return; + } + mToggleSlideShowAction->trigger(); +} + +double MprisMediaPlayer2Player::volume() const +{ + return 0; +} + +void MprisMediaPlayer2Player::setVolume(double volume) +{ + Q_UNUSED(volume); +} + +void MprisMediaPlayer2Player::setShuffle(bool isShuffle) +{ + mSlideShow->randomAction()->setChecked(isShuffle); +} + +QVariantMap MprisMediaPlayer2Player::metadata() const +{ + return mMetaData; +} + +qlonglong MprisMediaPlayer2Player::position() const +{ + return 0; +} + +double MprisMediaPlayer2Player::rate() const +{ + return 1.0; +} + +void MprisMediaPlayer2Player::setRate(double newRate) +{ + Q_UNUSED(newRate); +} + +double MprisMediaPlayer2Player::minimumRate() const +{ + return MIN_RATE; +} + +double MprisMediaPlayer2Player::maximumRate() const +{ + return MAX_RATE; +} + +bool MprisMediaPlayer2Player::isShuffle() const +{ + return mSlideShow->randomAction()->isChecked(); +} + + +bool MprisMediaPlayer2Player::canSeek() const +{ + return false; +} + +bool MprisMediaPlayer2Player::canControl() const +{ + return true; +} + +void MprisMediaPlayer2Player::Seek(qlonglong offset) +{ + Q_UNUSED(offset); +} + +void MprisMediaPlayer2Player::SetPosition(const QDBusObjectPath& trackId, qlonglong pos) +{ + Q_UNUSED(trackId); + Q_UNUSED(pos); +} + +void MprisMediaPlayer2Player::OpenUri(const QString& uri) +{ + Q_UNUSED(uri); +} + +void MprisMediaPlayer2Player::onSlideShowStateChanged(bool running) +{ + signalPropertyChange("PlaybackStatus", playbackStatusFromSlideShowState(running, mSlideShowEnabled)); +} + +void MprisMediaPlayer2Player::onCurrentUrlChanged(const QUrl& url) +{ + if (url.isEmpty()) { + mCurrentDocument = Document::Ptr(); + } else { + mCurrentDocument = DocumentFactory::instance()->load(url); + connect(mCurrentDocument.data(), &Document::metaInfoUpdated, + this, &MprisMediaPlayer2Player::onMetaInfoUpdated); + } + + onMetaInfoUpdated(); +} + +void MprisMediaPlayer2Player::onMetaInfoUpdated() +{ + QVariantMap updatedMetaData; + + if (mCurrentDocument) { + const QUrl url = mCurrentDocument->url(); + ImageMetaInfoModel* metaInfoModel = mCurrentDocument->metaInfo(); + + // TODO: we need some unique id mapping to urls. The index in the list is not reliable, + // as images can be added/removed during a running slideshow + const QString slideId = mSlideShow->isRunning() ? QString::number(mSlideShow->indexOfUrl(url)) : QStringLiteral("none"); + const QDBusObjectPath trackId(QLatin1String("/org/kde/gwenview/slidelist/") + slideId); + updatedMetaData.insert(QStringLiteral("mpris:trackid"), + QVariant::fromValue(trackId)); + + // TODO: for videos also get and report the length + if (MimeTypeUtils::urlKind(url) != MimeTypeUtils::KIND_VIDEO) { + // TODO: implement other MPRIS API for position + // convert seconds in microseconds + // const qlonglong duration = qlonglong(mSlideShow->interval() * 1000000); + // updatedMetaData.insert(QStringLiteral("mpris:length"), duration); + } + const QString name = metaInfoModel->getValueForKey(QStringLiteral("General.Name")); + updatedMetaData.insert(QStringLiteral("xesam:title"), name); + const QString comment = metaInfoModel->getValueForKey(QStringLiteral("General.Comment")); + if (!comment.isEmpty()) { + updatedMetaData.insert(QStringLiteral("xesam:comment"), comment); + } + updatedMetaData.insert(QStringLiteral("xesam:url"), url.toString()); + // slight bending of semantics :) + updatedMetaData.insert(QStringLiteral("xesam:album"), i18n("Gwenview Slideshow")); + // TODO: hack, will fail for movies, instead create thumbnails, but which size? + updatedMetaData.insert(QStringLiteral("mpris:artUrl"), url.toString()); + // consider export of other metadata where mapping works + } + + if (updatedMetaData != mMetaData) { + mMetaData = updatedMetaData; + + signalPropertyChange("Metadata", mMetaData); + } +} + +void MprisMediaPlayer2Player::onRandomActionToggled(bool checked) +{ + signalPropertyChange("Shuffle", checked); +} + +void MprisMediaPlayer2Player::onToggleSlideShowActionChanged() +{ + const bool isEnabled = mToggleSlideShowAction->isEnabled(); + if (mSlideShowEnabled == isEnabled) { + return; + } + + mSlideShowEnabled = isEnabled; + + signalPropertyChange("CanPlay", mSlideShowEnabled); + signalPropertyChange("CanPause", mSlideShowEnabled); + signalPropertyChange("PlaybackStatus", playbackStatusFromSlideShowState(mSlideShow->isRunning(), mSlideShowEnabled)); +} + +void MprisMediaPlayer2Player::onNextActionChanged() +{ + const bool isEnabled = mNextAction->isEnabled(); + if (mNextEnabled == isEnabled) { + return; + } + + mNextEnabled = isEnabled; + + signalPropertyChange("CanGoNext", mNextEnabled); +} + + +void MprisMediaPlayer2Player::onPreviousActionChanged() +{ + const bool isEnabled = mPreviousAction->isEnabled(); + if (mPreviousEnabled == isEnabled) { + return; + } + + mPreviousEnabled = isEnabled; + + signalPropertyChange("CanGoPrevious", mPreviousEnabled); +} + +} diff --git a/lib/slideshow.h b/lib/slideshow.h --- a/lib/slideshow.h +++ b/lib/slideshow.h @@ -51,6 +51,9 @@ /** @return true if the slideshow is running */ bool isRunning() const; + int indexOfUrl(const QUrl& url) const; + int interval() const; + public Q_SLOTS: void setInterval(int); void setCurrentUrl(const QUrl &url); diff --git a/lib/slideshow.cpp b/lib/slideshow.cpp --- a/lib/slideshow.cpp +++ b/lib/slideshow.cpp @@ -240,6 +240,11 @@ d->updateTimerInterval(); } +int SlideShow::interval() const +{ + return GwenviewConfig::interval(); +} + void SlideShow::stop() { LOG("Stopping timer"); @@ -282,6 +287,11 @@ } } +int SlideShow::indexOfUrl(const QUrl& url) const +{ + return d->mUrls.indexOf(url); +} + bool SlideShow::isRunning() const { return d->mState != Stopped;