You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libcutefish/mpris/mprismanager.cpp

607 lines
21 KiB
C++

// -*- c++ -*-
/*!
*
* Copyright (C) 2015 Jolla Ltd.
*
* Contact: Valerio Valerio <valerio.valerio@jolla.com>
* Author: Andres Gomez <andres.gomez@jolla.com>
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "mprismanager.h"
#include "mpriscontroller.h"
#include <qqmlinfo.h>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QtCore/QSignalMapper>
static const QString mprisNameSpace = QStringLiteral("org.mpris.MediaPlayer2.*");
static const QString dBusService = QStringLiteral("org.freedesktop.DBus");
static const QString dBusObjectPath = QStringLiteral("/org/freedesktop/DBus");
static const QString dBusInterface = QStringLiteral("org.freedesktop.DBus");
static const QString dBusNameOwnerChangedSignal = QStringLiteral("NameOwnerChanged");
static inline QDBusConnection getDBusConnection()
{
#ifdef USE_SYSTEM_DBUS
return QDBusConnection::systemBus();
#else
return QDBusConnection::sessionBus();
#endif
}
MprisManager::MprisManager(QObject *parent)
: QObject(parent)
, m_singleService(false)
, m_playbackStatusMapper(new QSignalMapper(this))
{
QDBusConnection connection = getDBusConnection();
if (!connection.isConnected()) {
qmlInfo(this) << "Failed attempting to connect to DBus";
return;
}
connection.connect(dBusService, dBusObjectPath, dBusInterface, dBusNameOwnerChangedSignal,
QStringList(), QString(),
this, SLOT(onNameOwnerChanged(QString, QString, QString)));
QStringList serviceNames = connection.interface()->registeredServiceNames();
QStringList::const_iterator i = serviceNames.constBegin();
while (i != serviceNames.constEnd()) {
QRegExp rx(mprisNameSpace);
rx.setPatternSyntax(QRegExp::Wildcard);
if (rx.exactMatch(*i)) {
onServiceAppeared(*i);
}
++i;
}
}
MprisManager::~MprisManager()
{
}
// Mpris2 Root Interface
bool MprisManager::quit() const
{
return checkController(Q_FUNC_INFO) && m_currentController->quit();
}
bool MprisManager::raise() const
{
return checkController(Q_FUNC_INFO) && m_currentController->raise();
}
// Mpris2 Player Interface
bool MprisManager::next() const
{
return checkController(Q_FUNC_INFO) && m_currentController->next();
}
bool MprisManager::openUri(const QUrl &uri) const
{
return checkController(Q_FUNC_INFO) && m_currentController->openUri(uri);
}
bool MprisManager::pause() const
{
return checkController(Q_FUNC_INFO) && m_currentController->pause();
}
bool MprisManager::play() const
{
return checkController(Q_FUNC_INFO) && m_currentController->play();
}
bool MprisManager::playPause() const
{
return checkController(Q_FUNC_INFO) && m_currentController->playPause();
}
bool MprisManager::previous() const
{
return checkController(Q_FUNC_INFO) && m_currentController->previous();
}
bool MprisManager::seek(qlonglong offset) const
{
return checkController(Q_FUNC_INFO) && m_currentController->seek(offset);
}
bool MprisManager::setPosition(qlonglong position) const
{
return checkController(Q_FUNC_INFO) && m_currentController->setPosition(position);
}
bool MprisManager::setPosition(const QString &trackId, qlonglong position) const
{
return checkController(Q_FUNC_INFO) && m_currentController->setPosition(trackId, position);
}
bool MprisManager::stop() const
{
return checkController(Q_FUNC_INFO) && m_currentController->stop();
}
// Slots
bool MprisManager::singleService() const
{
return m_singleService;
}
void MprisManager::setSingleService(bool single)
{
if (m_singleService == single) {
return;
}
m_singleService = single;
Q_EMIT singleServiceChanged();
}
QString MprisManager::currentService() const
{
return m_currentController.isNull() ? QString() : m_currentController->service();
}
void MprisManager::setCurrentService(const QString &service)
{
if (!m_currentController.isNull() && m_currentController->service() == service) {
return;
}
QRegExp rx(mprisNameSpace);
rx.setPatternSyntax(QRegExp::Wildcard);
if (!rx.exactMatch(service)) {
qmlInfo(this) << service << "is not a proper Mpris2 service";
return;
}
QSharedPointer<MprisController> controller = availableController(service);
if (controller.isNull()) {
controller = QSharedPointer<MprisController>(new MprisController(service, getDBusConnection(), this));
} else {
m_availableControllers.move(m_availableControllers.indexOf(controller), 0);
}
setCurrentController(controller);
}
QStringList MprisManager::availableServices() const
{
QStringList result;
QList< QSharedPointer<MprisController> >::const_iterator i = m_availableControllers.constBegin();
while (i != m_availableControllers.constEnd()) {
const QSharedPointer<MprisController> controller = *i;
result.append(controller->service());
++i;
}
return result;
}
// Mpris2 Root Interface
bool MprisManager::canQuit() const
{
return checkController(Q_FUNC_INFO) && m_currentController->canQuit();
}
bool MprisManager::canRaise() const
{
return checkController(Q_FUNC_INFO) && m_currentController->canRaise();
}
bool MprisManager::canSetFullscreen() const
{
return checkController(Q_FUNC_INFO) && m_currentController->canSetFullscreen();
}
QString MprisManager::desktopEntry() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->desktopEntry() : QString();
}
bool MprisManager::fullscreen() const
{
return checkController(Q_FUNC_INFO) && m_currentController->fullscreen();
}
void MprisManager::setFullscreen(bool fullscreen)
{
if (checkController(Q_FUNC_INFO)) {
m_currentController->setFullscreen(fullscreen);
}
}
bool MprisManager::hasTrackList() const
{
return checkController(Q_FUNC_INFO) && m_currentController->hasTrackList();
}
QString MprisManager::identity() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->identity() : QString();
}
QStringList MprisManager::supportedUriSchemes() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->supportedUriSchemes() : QStringList();
}
QStringList MprisManager::supportedMimeTypes() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->supportedMimeTypes() : QStringList();
}
// Mpris2 Player Interface
bool MprisManager::canControl() const
{
return checkController(Q_FUNC_INFO) && m_currentController->canControl();
}
bool MprisManager::canGoNext() const
{
return checkController(Q_FUNC_INFO) && m_currentController->canGoNext();
}
bool MprisManager::canGoPrevious() const
{
return checkController(Q_FUNC_INFO) && m_currentController->canGoPrevious();
}
bool MprisManager::canPause() const
{
return checkController(Q_FUNC_INFO) && m_currentController->canPause();
}
bool MprisManager::canPlay() const
{
return checkController(Q_FUNC_INFO) && m_currentController->canPlay();
}
bool MprisManager::canSeek() const
{
return checkController(Q_FUNC_INFO) && m_currentController->canSeek();
}
Mpris::LoopStatus MprisManager::loopStatus() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->loopStatus() : Mpris::None;
}
void MprisManager::setLoopStatus(Mpris::LoopStatus loopStatus)
{
if (checkController(Q_FUNC_INFO)) {
m_currentController->setLoopStatus(loopStatus);
}
}
double MprisManager::maximumRate() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->maximumRate() : 1;
}
QVariantMap MprisManager::metadata() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->metadata() : QVariantMap();
}
double MprisManager::minimumRate() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->minimumRate() : 1;
}
Mpris::PlaybackStatus MprisManager::playbackStatus() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->playbackStatus() : Mpris::Stopped;
}
qlonglong MprisManager::position() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->position() : 0;
}
void MprisManager::requestPosition() const
{
if (checkController(Q_FUNC_INFO)) {
m_currentController->requestPosition();
}
}
double MprisManager::rate() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->rate() : 1;
}
void MprisManager::setRate(double rate)
{
if (checkController(Q_FUNC_INFO)) {
m_currentController->setRate(rate);
}
}
bool MprisManager::shuffle() const
{
return checkController(Q_FUNC_INFO) && m_currentController->shuffle();
}
void MprisManager::setShuffle(bool shuffle)
{
if (checkController(Q_FUNC_INFO)) {
m_currentController->setShuffle(shuffle);
}
}
double MprisManager::volume() const
{
return checkController(Q_FUNC_INFO) ? m_currentController->volume() : 0;
}
void MprisManager::setVolume(double volume)
{
if (checkController(Q_FUNC_INFO)) {
m_currentController->setVolume(volume);
}
}
// Private
void MprisManager::onNameOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner)
{
// Unfortunately, QDBus doesn't allow flexible signal watchers.
// Would it allow them, we could pass the filter "arg0namespace='org.mpris.MediaPlayer2'"
// Therefore, we will receive here all the name changes in the
// bus, not just the ones for our name space of interest, and we
// will have to filter on our own :(
QRegExp rx(mprisNameSpace);
rx.setPatternSyntax(QRegExp::Wildcard);
if (!rx.exactMatch(service)) {
return;
}
if (oldOwner.isEmpty()) {
onServiceAppeared(service);
return;
}
if (newOwner.isEmpty()) {
onServiceVanished(service);
return;
}
// Service changed owner. Nothing to do ...
}
void MprisManager::onServiceAppeared(const QString &service)
{
QSharedPointer<MprisController> controller = availableController(service);
if (!controller.isNull()) {
Q_ASSERT(m_availableControllers.contains(controller));
m_availableControllers.removeOne(controller);
m_otherPlayingControllers.removeOne(controller);
} else {
if (!m_currentController.isNull() && service == m_currentController->service()) {
controller = m_currentController;
} else {
controller = QSharedPointer<MprisController>(new MprisController(service, getDBusConnection(), this));
}
connect(controller.data(), SIGNAL(playbackStatusChanged()), m_playbackStatusMapper, SLOT(map()));
m_playbackStatusMapper->setMapping(controller.data(), controller->service());
connect(m_playbackStatusMapper, SIGNAL(mapped(QString)), this, SLOT(onAvailableControllerPlaybackStatusChanged(QString)));
}
if (m_currentController.isNull()) {
setCurrentController(controller);
}
if (controller == m_currentController) {
m_availableControllers.prepend(controller);
Q_EMIT availableServicesChanged();
return;
}
if (!m_singleService
&& m_currentController->playbackStatus() != Mpris::Playing) {
m_availableControllers.prepend(controller);
setCurrentController(controller);
} else {
m_availableControllers.insert(1, controller);
if (controller->playbackStatus() == Mpris::Playing) {
m_otherPlayingControllers.prepend(controller);
}
}
Q_EMIT availableServicesChanged();
}
void MprisManager::onServiceVanished(const QString &service)
{
QSharedPointer<MprisController> controller = availableController(service);
if (!controller.isNull()) {
Q_ASSERT(m_availableControllers.contains(controller));
m_availableControllers.removeOne(controller);
m_otherPlayingControllers.removeOne(controller);
}
if (!m_currentController.isNull() && service == m_currentController->service()) {
if (m_singleService) {
Q_EMIT availableServicesChanged();
return;
}
if (!m_availableControllers.isEmpty()) {
setCurrentController(m_availableControllers[0]);
} else {
setCurrentController(QSharedPointer<MprisController>());
}
}
Q_EMIT availableServicesChanged();
}
void MprisManager::onAvailableControllerPlaybackStatusChanged(const QString &service)
{
QSharedPointer<MprisController> controller = availableController(service);
Q_ASSERT(!controller.isNull());
if (m_currentController == controller) {
if (m_currentController->playbackStatus() == Mpris::Playing) {
return;
}
if (!m_otherPlayingControllers.isEmpty()) {
QSharedPointer<MprisController> currentController = m_otherPlayingControllers.takeFirst();
m_availableControllers.move(m_availableControllers.indexOf(currentController), 0);
setCurrentController(currentController);
}
} else {
if (controller->playbackStatus() != Mpris::Playing) {
m_otherPlayingControllers.removeOne(controller);
return;
}
if (!m_singleService
&& m_currentController->playbackStatus() != Mpris::Playing) {
setCurrentController(controller);
} else {
m_availableControllers.move(m_availableControllers.indexOf(controller), 1);
m_otherPlayingControllers.removeOne(controller); // Just in case, shouldn't be needed
m_otherPlayingControllers.prepend(controller);
}
}
}
QSharedPointer<MprisController> MprisManager::availableController(const QString &service)
{
QList< QSharedPointer<MprisController> >::iterator i = m_availableControllers.begin();
while (i != m_availableControllers.end()) {
QSharedPointer<MprisController> controller = *i;
if (!controller.isNull() && controller->service() == service) {
return controller;
}
++i;
}
return QSharedPointer<MprisController>();
}
void MprisManager::setCurrentController(QSharedPointer<MprisController> controller)
{
if (controller == m_currentController) {
return;
}
if (!m_currentController.isNull()) {
// Mpris Root Interface
disconnect(m_currentController.data(), &MprisController::canQuitChanged, this, &MprisManager::canQuitChanged);
disconnect(m_currentController.data(), &MprisController::canRaiseChanged, this, &MprisManager::canRaiseChanged);
disconnect(m_currentController.data(), &MprisController::canSetFullscreenChanged, this, &MprisManager::canSetFullscreenChanged);
disconnect(m_currentController.data(), &MprisController::desktopEntryChanged, this, &MprisManager::desktopEntryChanged);
disconnect(m_currentController.data(), &MprisController::fullscreenChanged, this, &MprisManager::fullscreenChanged);
disconnect(m_currentController.data(), &MprisController::hasTrackListChanged, this, &MprisManager::hasTrackListChanged);
disconnect(m_currentController.data(), &MprisController::identityChanged, this, &MprisManager::identityChanged);
disconnect(m_currentController.data(), &MprisController::supportedUriSchemesChanged, this, &MprisManager::supportedUriSchemesChanged);
disconnect(m_currentController.data(), &MprisController::supportedMimeTypesChanged, this, &MprisManager::supportedMimeTypesChanged);
// Mpris Player Interface
disconnect(m_currentController.data(), &MprisController::canControlChanged, this, &MprisManager::canControlChanged);
disconnect(m_currentController.data(), &MprisController::canGoNextChanged, this, &MprisManager::canGoNextChanged);
disconnect(m_currentController.data(), &MprisController::canGoPreviousChanged, this, &MprisManager::canGoPreviousChanged);
disconnect(m_currentController.data(), &MprisController::canPauseChanged, this, &MprisManager::canPauseChanged);
disconnect(m_currentController.data(), &MprisController::canPlayChanged, this, &MprisManager::canPlayChanged);
disconnect(m_currentController.data(), &MprisController::canSeekChanged, this, &MprisManager::canSeekChanged);
disconnect(m_currentController.data(), &MprisController::loopStatusChanged, this, &MprisManager::loopStatusChanged);
disconnect(m_currentController.data(), &MprisController::maximumRateChanged, this, &MprisManager::maximumRateChanged);
disconnect(m_currentController.data(), &MprisController::metadataChanged, this, &MprisManager::metadataChanged);
disconnect(m_currentController.data(), &MprisController::minimumRateChanged, this, &MprisManager::minimumRateChanged);
disconnect(m_currentController.data(), &MprisController::playbackStatusChanged, this, &MprisManager::playbackStatusChanged);
disconnect(m_currentController.data(), &MprisController::positionChanged, this, &MprisManager::positionChanged);
disconnect(m_currentController.data(), &MprisController::rateChanged, this, &MprisManager::rateChanged);
disconnect(m_currentController.data(), &MprisController::shuffleChanged, this, &MprisManager::shuffleChanged);
disconnect(m_currentController.data(), &MprisController::volumeChanged, this, &MprisManager::volumeChanged);
disconnect(m_currentController.data(), &MprisController::seeked, this, &MprisManager::seeked);
if (m_currentController->playbackStatus() == Mpris::Playing) {
m_otherPlayingControllers.prepend(m_currentController);
}
}
m_currentController = controller;
if (!m_currentController.isNull()) {
// Mpris Root Interface
connect(m_currentController.data(), &MprisController::canQuitChanged, this, &MprisManager::canQuitChanged);
connect(m_currentController.data(), &MprisController::canRaiseChanged, this, &MprisManager::canRaiseChanged);
connect(m_currentController.data(), &MprisController::canSetFullscreenChanged, this, &MprisManager::canSetFullscreenChanged);
connect(m_currentController.data(), &MprisController::desktopEntryChanged, this, &MprisManager::desktopEntryChanged);
connect(m_currentController.data(), &MprisController::fullscreenChanged, this, &MprisManager::fullscreenChanged);
connect(m_currentController.data(), &MprisController::hasTrackListChanged, this, &MprisManager::hasTrackListChanged);
connect(m_currentController.data(), &MprisController::identityChanged, this, &MprisManager::identityChanged);
connect(m_currentController.data(), &MprisController::supportedUriSchemesChanged, this, &MprisManager::supportedUriSchemesChanged);
connect(m_currentController.data(), &MprisController::supportedMimeTypesChanged, this, &MprisManager::supportedMimeTypesChanged);
connect(m_currentController.data(), &MprisController::canControlChanged, this, &MprisManager::canControlChanged);
// Mpris Player Interface
connect(m_currentController.data(), &MprisController::canGoNextChanged, this, &MprisManager::canGoNextChanged);
connect(m_currentController.data(), &MprisController::canGoPreviousChanged, this, &MprisManager::canGoPreviousChanged);
connect(m_currentController.data(), &MprisController::canPauseChanged, this, &MprisManager::canPauseChanged);
connect(m_currentController.data(), &MprisController::canPlayChanged, this, &MprisManager::canPlayChanged);
connect(m_currentController.data(), &MprisController::canSeekChanged, this, &MprisManager::canSeekChanged);
connect(m_currentController.data(), &MprisController::loopStatusChanged, this, &MprisManager::loopStatusChanged);
connect(m_currentController.data(), &MprisController::maximumRateChanged, this, &MprisManager::maximumRateChanged);
connect(m_currentController.data(), &MprisController::metadataChanged, this, &MprisManager::metadataChanged);
connect(m_currentController.data(), &MprisController::minimumRateChanged, this, &MprisManager::minimumRateChanged);
connect(m_currentController.data(), &MprisController::playbackStatusChanged, this, &MprisManager::playbackStatusChanged);
connect(m_currentController.data(), &MprisController::rateChanged, this, &MprisManager::rateChanged);
connect(m_currentController.data(), &MprisController::shuffleChanged, this, &MprisManager::shuffleChanged);
connect(m_currentController.data(), &MprisController::volumeChanged, this, &MprisManager::volumeChanged);
connect(m_currentController.data(), &MprisController::seeked, this, &MprisManager::seeked);
if (m_currentController->playbackStatus() == Mpris::Playing) {
m_otherPlayingControllers.removeOne(m_currentController);
}
}
Q_EMIT currentServiceChanged();
}
bool MprisManager::checkController(const char *callerName) const
{
if (m_currentController.isNull()) {
qWarning() << callerName << "None service available/selected";
return false;
}
return true;
}