File indexing completed on 2024-05-19 05:38:37

0001 /*
0002     SPDX-FileCopyrightText: 2012 Alex Merry <alex.merry@kdemail.net>
0003     SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "playercontainer.h"
0009 
0010 #include <QDBusConnection>
0011 #include <QDBusObjectPath>
0012 #include <QDBusReply>
0013 #include <QVariantMap>
0014 
0015 #include <KDesktopFile>
0016 
0017 #include "dbusproperties.h"
0018 #include "libkmpris_debug.h"
0019 #include "mprisplayer.h"
0020 #include "mprisroot.h"
0021 
0022 AbstractPlayerContainer::AbstractPlayerContainer(QObject *parent)
0023     : QObject(parent)
0024 {
0025 }
0026 
0027 AbstractPlayerContainer::~AbstractPlayerContainer()
0028 {
0029 }
0030 
0031 bool AbstractPlayerContainer::canControl() const
0032 {
0033     return m_canControl.value();
0034 }
0035 
0036 bool AbstractPlayerContainer::canGoNext() const
0037 {
0038     return m_effectiveCanGoNext.value();
0039 }
0040 
0041 bool AbstractPlayerContainer::canGoPrevious() const
0042 {
0043     return m_effectiveCanGoPrevious.value();
0044 }
0045 
0046 bool AbstractPlayerContainer::canPause() const
0047 {
0048     return m_effectiveCanPause.value();
0049 }
0050 
0051 bool AbstractPlayerContainer::canPlay() const
0052 {
0053     return m_effectiveCanPlay.value();
0054 }
0055 
0056 bool AbstractPlayerContainer::canStop() const
0057 {
0058     return m_effectiveCanStop.value();
0059 }
0060 
0061 bool AbstractPlayerContainer::canSeek() const
0062 {
0063     return m_effectiveCanSeek.value();
0064 }
0065 
0066 LoopStatus::Status AbstractPlayerContainer::loopStatus() const
0067 {
0068     return m_loopStatus.value();
0069 }
0070 
0071 double AbstractPlayerContainer::maximumRate() const
0072 {
0073     return m_maximumRate.value();
0074 }
0075 
0076 double AbstractPlayerContainer::minimumRate() const
0077 {
0078     return m_minimumRate.value();
0079 }
0080 
0081 PlaybackStatus::Status AbstractPlayerContainer::playbackStatus() const
0082 {
0083     return m_playbackStatus.value();
0084 }
0085 
0086 qlonglong AbstractPlayerContainer::position() const
0087 {
0088     return m_position.value();
0089 }
0090 
0091 double AbstractPlayerContainer::rate() const
0092 {
0093     return m_rate.value();
0094 }
0095 
0096 ShuffleStatus::Status AbstractPlayerContainer::shuffle() const
0097 {
0098     return m_shuffle.value();
0099 }
0100 
0101 double AbstractPlayerContainer::volume() const
0102 {
0103     return m_volume.value();
0104 }
0105 
0106 QString AbstractPlayerContainer::track() const
0107 {
0108     return m_track.value();
0109 }
0110 
0111 QString AbstractPlayerContainer::artist() const
0112 {
0113     return m_artist.value();
0114 }
0115 
0116 QString AbstractPlayerContainer::artUrl() const
0117 {
0118     return m_artUrl.value();
0119 }
0120 
0121 QString AbstractPlayerContainer::album() const
0122 {
0123     return m_album.value();
0124 }
0125 
0126 double AbstractPlayerContainer::length() const
0127 {
0128     return m_length;
0129 }
0130 
0131 unsigned AbstractPlayerContainer::instancePid() const
0132 {
0133     return m_instancePid;
0134 }
0135 
0136 unsigned AbstractPlayerContainer::kdePid() const
0137 {
0138     return m_kdePid.value();
0139 }
0140 
0141 bool AbstractPlayerContainer::canQuit() const
0142 {
0143     return m_canQuit.value();
0144 }
0145 
0146 bool AbstractPlayerContainer::canRaise() const
0147 {
0148     return m_canRaise.value();
0149 }
0150 
0151 bool AbstractPlayerContainer::canSetFullscreen() const
0152 {
0153     return m_canSetFullscreen.value();
0154 }
0155 
0156 QString AbstractPlayerContainer::desktopEntry() const
0157 {
0158     return m_desktopEntry;
0159 }
0160 
0161 bool AbstractPlayerContainer::fullscreen() const
0162 {
0163     return m_fullscreen.value();
0164 }
0165 
0166 PlayerContainer::PlayerContainer(const QString &busAddress, QObject *parent)
0167     : AbstractPlayerContainer(parent)
0168     , m_dbusAddress(busAddress)
0169     , m_propsIface(new OrgFreedesktopDBusPropertiesInterface(busAddress, MPRIS2_PATH, QDBusConnection::sessionBus(), this))
0170     , m_playerIface(new OrgMprisMediaPlayer2PlayerInterface(busAddress, MPRIS2_PATH, QDBusConnection::sessionBus(), this))
0171     , m_rootIface(new OrgMprisMediaPlayer2Interface(busAddress, MPRIS2_PATH, QDBusConnection::sessionBus(), this))
0172 {
0173     Q_ASSERT(busAddress.startsWith(MPRIS2_PREFIX));
0174 
0175     // MPRIS specifies, that in case a player supports several instances, each additional
0176     // instance after the first one is supposed to append ".instance<pid>" at the end of
0177     // its dbus address. So instances of media players, which implement this correctly
0178     // can have one D-Bus connection per instance and can be identified by their pids.
0179     if (QDBusReply<unsigned> pidReply = QDBusConnection::sessionBus().interface()->servicePid(busAddress); pidReply.isValid()) {
0180         m_instancePid = pidReply.value();
0181     }
0182 
0183     initBindings();
0184     refresh();
0185 }
0186 
0187 PlayerContainer::~PlayerContainer()
0188 {
0189 }
0190 
0191 void PlayerContainer::setLoopStatus(LoopStatus::Status value)
0192 {
0193     if (m_loopStatus == value) {
0194         return;
0195     }
0196 
0197     QVariant result;
0198     switch (value) {
0199     case LoopStatus::None:
0200         result = QStringLiteral("None");
0201         break;
0202     case LoopStatus::Playlist:
0203         result = QStringLiteral("Playlist");
0204         break;
0205     case LoopStatus::Track:
0206         result = QStringLiteral("Track");
0207         break;
0208     default:
0209         Q_UNREACHABLE();
0210     }
0211     m_propsIface->Set(OrgMprisMediaPlayer2PlayerInterface::staticInterfaceName(), QStringLiteral("LoopStatus"), QDBusVariant(result));
0212     // Emit changed signals in onPropertiesChanged
0213 }
0214 
0215 void PlayerContainer::setPosition(qlonglong value)
0216 {
0217     if (m_position == value) {
0218         return;
0219     }
0220 
0221     m_playerIface->SetPosition(QDBusObjectPath(m_trackId.value()), value);
0222 }
0223 
0224 void PlayerContainer::setRate(double value)
0225 {
0226     if (m_rate == value) {
0227         return;
0228     }
0229 
0230     m_propsIface->Set(OrgMprisMediaPlayer2PlayerInterface::staticInterfaceName(), QStringLiteral("Rate"), QDBusVariant(QVariant(value)));
0231     // Emit changed signals in onPropertiesChanged
0232 }
0233 
0234 void PlayerContainer::setShuffle(ShuffleStatus::Status value)
0235 {
0236     if (m_shuffle == value) {
0237         return;
0238     }
0239 
0240     m_propsIface->Set(OrgMprisMediaPlayer2PlayerInterface::staticInterfaceName(),
0241                       QStringLiteral("Shuffle"),
0242                       QDBusVariant(QVariant(value == ShuffleStatus::On)));
0243     // Emit changed signals in onPropertiesChanged
0244 }
0245 
0246 void PlayerContainer::setVolume(double value)
0247 {
0248     if (m_volume == value) {
0249         return;
0250     }
0251 
0252     m_propsIface->Set(OrgMprisMediaPlayer2PlayerInterface::staticInterfaceName(), QStringLiteral("Volume"), QDBusVariant(QVariant(value)));
0253     // Emit changed signals in onPropertiesChanged
0254 }
0255 
0256 void PlayerContainer::Next()
0257 {
0258     Q_ASSERT_X(m_canGoNext.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0259     if (!m_canGoNext.value()) {
0260         return;
0261     }
0262     m_playerIface->Next();
0263     updatePosition();
0264 }
0265 
0266 void PlayerContainer::OpenUri(const QString &Uri)
0267 {
0268     m_playerIface->OpenUri(Uri);
0269 }
0270 
0271 void PlayerContainer::Pause()
0272 {
0273     Q_ASSERT_X(m_canPause.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0274     if (!m_canPause.value()) {
0275         return;
0276     }
0277     m_playerIface->Pause();
0278 }
0279 
0280 void PlayerContainer::Play()
0281 {
0282     Q_ASSERT_X(m_canPlay.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0283     if (!m_canPlay.value()) {
0284         return;
0285     }
0286     m_playerIface->Play();
0287 }
0288 
0289 void PlayerContainer::PlayPause()
0290 {
0291     Q_ASSERT_X(m_canPlay.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0292     Q_ASSERT_X(m_canPause.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0293     if (!m_canPlay.value() || !m_canPause.value()) {
0294         return;
0295     }
0296     m_playerIface->PlayPause();
0297 }
0298 
0299 void PlayerContainer::Previous()
0300 {
0301     Q_ASSERT_X(m_canGoPrevious.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0302     if (!m_canGoPrevious.value()) {
0303         return;
0304     }
0305     m_playerIface->Previous();
0306     updatePosition();
0307 }
0308 
0309 void PlayerContainer::Seek(qlonglong Offset)
0310 {
0311     Q_ASSERT_X(m_canSeek.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0312     if (!m_canSeek.value()) {
0313         return;
0314     }
0315     m_playerIface->Seek(Offset);
0316 }
0317 
0318 void PlayerContainer::Stop()
0319 {
0320     Q_ASSERT_X(m_canStop.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0321     if (!m_canStop.value()) {
0322         return;
0323     }
0324     m_playerIface->Stop();
0325 }
0326 
0327 void PlayerContainer::setFullscreen(bool value)
0328 {
0329     if (m_fullscreen == value) {
0330         return;
0331     }
0332 
0333     m_propsIface->Set(OrgMprisMediaPlayer2Interface::staticInterfaceName(), QStringLiteral("Fullscreen"), QDBusVariant(QVariant(value)));
0334     // Emit changed signals in onPropertiesChanged
0335 }
0336 
0337 void PlayerContainer::setPlaybackStatus(PlaybackStatus::Status value)
0338 {
0339     if (m_playbackStatus == value) {
0340         return;
0341     }
0342 
0343     switch (value) {
0344     case PlaybackStatus::Playing:
0345         Play();
0346         break;
0347     case PlaybackStatus::Paused:
0348         Pause();
0349         break;
0350     case PlaybackStatus::Stopped:
0351         Stop();
0352         break;
0353     default:
0354 #ifdef __cpp_lib_unreachable
0355         std::unreachable();
0356 #else
0357         Q_UNREACHABLE();
0358 #endif
0359     }
0360 
0361     // Emit changed signals in onPropertiesChanged
0362 }
0363 
0364 bool AbstractPlayerContainer::hasTrackList() const
0365 {
0366     return m_hasTrackList;
0367 }
0368 
0369 QString AbstractPlayerContainer::identity() const
0370 {
0371     return m_identity;
0372 }
0373 
0374 QStringList AbstractPlayerContainer::supportedMimeTypes() const
0375 {
0376     return m_supportedMimeTypes;
0377 }
0378 
0379 QStringList AbstractPlayerContainer::supportedUriSchemes() const
0380 {
0381     return m_supportedUriSchemes;
0382 }
0383 
0384 void PlayerContainer::Quit()
0385 {
0386     Q_ASSERT_X(m_canQuit.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0387     if (!m_canQuit.value()) {
0388         return;
0389     }
0390     m_rootIface->Quit();
0391 }
0392 void PlayerContainer::Raise()
0393 {
0394     Q_ASSERT_X(m_canRaise.value(), Q_FUNC_INFO, qUtf8Printable(identity()));
0395     if (!m_canRaise.value()) {
0396         return;
0397     }
0398     m_rootIface->Raise();
0399 }
0400 
0401 QString AbstractPlayerContainer::iconName() const
0402 {
0403     return m_iconName;
0404 }
0405 
0406 void PlayerContainer::refresh()
0407 {
0408     // despite these calls being async, we should never update values in the
0409     // wrong order (eg: a stale GetAll response overwriting a more recent value
0410     // from a PropertiesChanged signal) due to D-Bus message ordering guarantees.
0411 
0412     QDBusPendingCall async = m_propsIface->GetAll(OrgMprisMediaPlayer2Interface::staticInterfaceName());
0413     auto watcher = new QDBusPendingCallWatcher(async, this);
0414     connect(watcher, &QDBusPendingCallWatcher::finished, this, &PlayerContainer::onGetPropsFinished);
0415     ++m_fetchesPending;
0416 
0417     async = m_propsIface->GetAll(OrgMprisMediaPlayer2PlayerInterface::staticInterfaceName());
0418     watcher = new QDBusPendingCallWatcher(async, this);
0419     connect(watcher, &QDBusPendingCallWatcher::finished, this, &PlayerContainer::onGetPropsFinished);
0420     ++m_fetchesPending;
0421 }
0422 
0423 void PlayerContainer::updatePosition()
0424 {
0425     QDBusPendingCall call = m_propsIface->Get(OrgMprisMediaPlayer2PlayerInterface::staticInterfaceName(), QStringLiteral("Position"));
0426     auto watcher = new QDBusPendingCallWatcher(call, this);
0427     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0428         QDBusPendingReply<QVariant> propsReply = *watcher;
0429         watcher->deleteLater();
0430         if (!propsReply.isValid() && propsReply.error().type() != QDBusError::NotSupported) {
0431             qCDebug(MPRIS2) << m_dbusAddress << "does not implement" << OrgFreedesktopDBusPropertiesInterface::staticInterfaceName()
0432                             << "correctly. Error message was" << propsReply.error().name() << propsReply.error().message();
0433             return;
0434         }
0435 
0436         m_position = propsReply.value().toLongLong();
0437     });
0438 }
0439 
0440 void PlayerContainer::changeVolume(double delta, bool showOSD)
0441 {
0442     // Not relying on property/setProperty to avoid doing blocking DBus calls
0443     const double newVolume = qBound(0.0, m_volume + delta, std::max(m_volume.value(), 1.0));
0444     const double oldVolume = m_volume;
0445 
0446     // Update the container value right away so when calling this method in quick succession
0447     // (mouse wheeling the tray icon) next call already gets the new value
0448     m_volume = newVolume;
0449 
0450     QDBusPendingCall call = m_propsIface->Set(m_playerIface->interface(), QStringLiteral("Volume"), QDBusVariant(newVolume));
0451     auto watcher = new QDBusPendingCallWatcher(call, this);
0452     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, oldVolume, showOSD](QDBusPendingCallWatcher *watcher) {
0453         QDBusPendingReply<void> reply = *watcher;
0454         watcher->deleteLater();
0455         if (!reply.isValid()) {
0456             m_volume = oldVolume;
0457             return;
0458         }
0459 
0460         if (showOSD) {
0461             QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
0462                                                               QStringLiteral("/org/kde/osdService"),
0463                                                               QStringLiteral("org.kde.osdService"),
0464                                                               QStringLiteral("mediaPlayerVolumeChanged"));
0465 
0466             msg.setArguments({qRound(m_volume * 100), m_identity, m_iconName});
0467 
0468             QDBusConnection::sessionBus().asyncCall(msg);
0469         }
0470     });
0471 }
0472 
0473 void PlayerContainer::initBindings()
0474 {
0475     // Since the bindings are already used in QML, move them to C++ for better efficiency and consistency
0476     m_effectiveCanGoNext.setBinding([this] {
0477         return m_canControl.value() && m_canGoNext.value();
0478     });
0479     m_effectiveCanGoPrevious.setBinding([this] {
0480         return m_canControl.value() && m_canGoPrevious.value();
0481     });
0482     m_effectiveCanPlay.setBinding([this] {
0483         return m_canControl.value() && m_canPlay.value();
0484     });
0485     m_effectiveCanPause.setBinding([this] {
0486         return m_canControl.value() && m_canPause.value();
0487     });
0488     m_effectiveCanStop.setBinding([this] {
0489         return m_canControl.value() && m_canStop.value();
0490     });
0491     m_effectiveCanSeek.setBinding([this] {
0492         return m_canControl.value() && m_canSeek.value();
0493     });
0494 
0495     // Fake canStop property
0496     m_canStop.setBinding([this] {
0497         return m_canControl.value() && m_playbackStatus.value() > PlaybackStatus::Stopped;
0498     });
0499 
0500     // Metadata
0501     m_track.setBinding([this] {
0502         if (!m_xesamTitle.value().isEmpty()) {
0503             return m_xesamTitle.value();
0504         }
0505         const QStringView xesamUrl{m_xesamUrl.value()};
0506         if (xesamUrl.isEmpty()) {
0507             return QString();
0508         }
0509         if (int lastSlashPos = xesamUrl.lastIndexOf(QLatin1Char('/')); lastSlashPos < 0 || lastSlashPos == xesamUrl.size() - 1) {
0510             return QString();
0511         } else {
0512             const QStringView lastUrlPart = xesamUrl.sliced(lastSlashPos + 1);
0513             return QUrl::fromEncoded(lastUrlPart.toLatin1()).toString();
0514         }
0515     });
0516     m_artist.setBinding([this] {
0517         if (!m_xesamArtist.value().empty()) {
0518             return m_xesamArtist.value().join(QLatin1String(", "));
0519         }
0520         if (!m_xesamAlbumArtist.value().empty()) {
0521             return m_xesamAlbumArtist.value().join(QLatin1String(", "));
0522         }
0523         return QString();
0524     });
0525     m_album.setBinding([this] {
0526         if (!m_xesamAlbum.value().isEmpty()) {
0527             return m_xesamAlbum.value();
0528         }
0529         const QStringView xesamUrl{m_xesamUrl.value()};
0530         if (!xesamUrl.startsWith(QLatin1String("file:///"))) {
0531             return QString();
0532         }
0533         const QList<QStringView> urlParts = xesamUrl.split(QLatin1Char('/'));
0534         if (urlParts.size() < 3) {
0535             return QString();
0536         }
0537         // if we play a local file without title and artist, show its containing folder instead
0538         if (auto lastFolderPathIt = std::next(urlParts.crbegin()); !lastFolderPathIt->isEmpty()) {
0539             return QUrl::fromEncoded(lastFolderPathIt->toLatin1()).toString();
0540         }
0541         return QString();
0542     });
0543 
0544     auto callback = [this] {
0545         updatePosition();
0546     };
0547     m_rateNotifier = m_rate.addNotifier(callback);
0548     m_playbackStatusNotifier = m_playbackStatus.addNotifier(callback);
0549 }
0550 
0551 void PlayerContainer::updateFromMap(const QVariantMap &map)
0552 {
0553     auto updateSingleProperty = [this]<typename T>(T &property, const QVariant &value, QMetaType::Type expectedType, void (PlayerContainer::*signal)()) {
0554         if (value.metaType().id() != expectedType) {
0555             qCWarning(MPRIS2) << m_dbusAddress << "exports" << value.metaType() << "but it should be" << QMetaType(expectedType);
0556         }
0557         if (T newProperty = value.value<T>(); property != newProperty) {
0558             property = newProperty;
0559             Q_EMIT(this->*signal)();
0560         }
0561     };
0562 
0563     QString oldTrackId;
0564 
0565     for (auto it = map.cbegin(); it != map.cend(); it = std::next(it)) {
0566         const QString &propName = it.key();
0567 
0568         if (propName == QLatin1String("Identity")) {
0569             updateSingleProperty(m_identity, it.value(), QMetaType::QString, &PlayerContainer::identityChanged);
0570         } else if (propName == QLatin1String("DesktopEntry")) {
0571             m_iconName = KDesktopFile(it.value().toString() + QLatin1String(".desktop")).readIcon();
0572             if (m_iconName.isEmpty()) {
0573                 m_iconName = QStringLiteral("emblem-music-symbolic");
0574             }
0575             updateSingleProperty(m_desktopEntry, it.value(), QMetaType::QString, &PlayerContainer::desktopEntryChanged);
0576         } else if (propName == QLatin1String("SupportedUriSchemes")) {
0577             updateSingleProperty(m_supportedUriSchemes, it.value(), QMetaType::QStringList, &PlayerContainer::supportedUriSchemesChanged);
0578         } else if (propName == QLatin1String("SupportedMimeTypes")) {
0579             updateSingleProperty(m_supportedMimeTypes, it.value(), QMetaType::QStringList, &PlayerContainer::supportedMimeTypesChanged);
0580         } else if (propName == QLatin1String("Fullscreen")) {
0581             m_fullscreen = it->toBool();
0582         } else if (propName == QLatin1String("HasTrackList")) {
0583             m_hasTrackList = it->toBool();
0584         } else if (propName == QLatin1String("PlaybackStatus")) {
0585             if (const QString newValue = it->toString(); newValue == QLatin1String("Stopped")) {
0586                 m_playbackStatus = PlaybackStatus::Stopped;
0587             } else if (newValue == QLatin1String("Paused")) {
0588                 m_playbackStatus = PlaybackStatus::Paused;
0589             } else if (newValue == QLatin1String("Playing")) {
0590                 m_playbackStatus = PlaybackStatus::Playing;
0591             } else {
0592                 m_playbackStatus = PlaybackStatus::Unknown;
0593             }
0594         } else if (propName == QLatin1String("LoopStatus")) {
0595             if (const QString newValue = it.value().toString(); newValue == QLatin1String("Playlist")) {
0596                 m_loopStatus = LoopStatus::Playlist;
0597             } else if (newValue == QLatin1String("Track")) {
0598                 m_loopStatus = LoopStatus::Track;
0599             } else {
0600                 m_loopStatus = LoopStatus::None;
0601             }
0602         } else if (propName == QLatin1String("Shuffle")) {
0603             m_shuffle = it->toBool() ? ShuffleStatus::On : ShuffleStatus::Off;
0604         } else if (propName == QLatin1String("Rate")) {
0605             m_rate = it->toDouble();
0606         } else if (propName == QLatin1String("MinimumRate")) {
0607             m_minimumRate = it->toDouble();
0608         } else if (propName == QLatin1String("MaximumRate")) {
0609             m_maximumRate = it->toDouble();
0610         } else if (propName == QLatin1String("Volume")) {
0611             m_volume = it->toDouble();
0612         } else if (propName == QLatin1String("Position")) {
0613             m_position = it->toLongLong();
0614         } else if (propName == QLatin1String("Metadata")) {
0615             oldTrackId = m_trackId.value();
0616             QDBusArgument arg = it->value<QDBusArgument>();
0617             if (arg.currentType() != QDBusArgument::MapType || arg.currentSignature() != QLatin1String("a{sv}")) {
0618                 continue;
0619             }
0620 
0621             QVariantMap map;
0622             arg >> map;
0623 
0624             m_trackId = map[QStringLiteral("mpris:trackid")].value<QDBusObjectPath>().path();
0625             m_xesamTitle = map[QStringLiteral("xesam:title")].toString();
0626             m_xesamUrl = map[QStringLiteral("xesam:url")].toString();
0627             m_xesamArtist = map[QStringLiteral("xesam:artist")].toStringList();
0628             m_xesamAlbumArtist = map[QStringLiteral("xesam:albumArtist")].toStringList();
0629             m_xesamAlbum = map[QStringLiteral("xesam:album")].toString();
0630             m_artUrl = map[QStringLiteral("mpris:artUrl")].toString();
0631             m_length = map[QStringLiteral("mpris:length")].toDouble();
0632             m_kdePid = map[QStringLiteral("kde:pid")].toUInt();
0633         }
0634         // we give out CanControl, as this may completely
0635         // change the UI of the widget
0636         else if (propName == QLatin1String("CanControl")) {
0637             m_canControl = it->toBool();
0638         } else if (propName == QLatin1String("CanSeek")) {
0639             m_canSeek = it->toBool();
0640         } else if (propName == QLatin1String("CanGoNext")) {
0641             m_canGoNext = it->toBool();
0642         } else if (propName == QLatin1String("CanGoPrevious")) {
0643             m_canGoPrevious = it->toBool();
0644         } else if (propName == QLatin1String("CanRaise")) {
0645             m_canRaise = it->toBool();
0646         } else if (propName == QLatin1String("CanSetFullscreen")) {
0647             m_canSetFullscreen = it->toBool();
0648         } else if (propName == QLatin1String("CanQuit")) {
0649             m_canQuit = it->toBool();
0650         } else if (propName == QLatin1String("CanPlay")) {
0651             m_canPlay = it->toBool();
0652         } else if (propName == QLatin1String("CanPause")) {
0653             m_canPause = it->toBool();
0654         }
0655     }
0656 
0657     if (map.contains(QStringLiteral("Position"))) {
0658         return;
0659     }
0660 
0661     if (m_position != 0.0 && (m_playbackStatus == PlaybackStatus::Stopped || (!oldTrackId.isEmpty() && m_trackId.value() != oldTrackId))) {
0662         // assume the position has reset to 0, since this is really the
0663         // only sensible value for a stopped track
0664         updatePosition();
0665     }
0666 }
0667 
0668 void PlayerContainer::onGetPropsFinished(QDBusPendingCallWatcher *watcher)
0669 {
0670     QDBusPendingReply<QVariantMap> propsReply = *watcher;
0671     watcher->deleteLater();
0672 
0673     if (m_fetchesPending < 1) {
0674         // we already failed
0675         Q_EMIT initialFetchFailed(this);
0676         return;
0677     }
0678 
0679     if (propsReply.isError()) {
0680         qCDebug(MPRIS2) << m_dbusAddress << "does not implement" << OrgFreedesktopDBusPropertiesInterface::staticInterfaceName() << "correctly"
0681                         << "Error message was" << propsReply.error().name() << propsReply.error().message();
0682         m_fetchesPending = 0;
0683         Q_EMIT initialFetchFailed(this);
0684         return;
0685     }
0686 
0687     updateFromMap(propsReply.value());
0688 
0689     if (--m_fetchesPending == 0) {
0690         Q_EMIT initialFetchFinished(this);
0691 
0692         connect(m_propsIface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &PlayerContainer::onPropertiesChanged);
0693         connect(m_playerIface, &OrgMprisMediaPlayer2PlayerInterface::Seeked, this, &PlayerContainer::onSeeked);
0694     }
0695 }
0696 
0697 void PlayerContainer::onPropertiesChanged(const QString &, const QVariantMap &changedProperties, const QStringList &invalidatedProperties)
0698 {
0699     if (!invalidatedProperties.empty()) {
0700         disconnect(m_propsIface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &PlayerContainer::onPropertiesChanged);
0701         disconnect(m_playerIface, &OrgMprisMediaPlayer2PlayerInterface::Seeked, this, &PlayerContainer::onSeeked);
0702         refresh();
0703     } else {
0704         updateFromMap(changedProperties);
0705     }
0706 }
0707 
0708 void PlayerContainer::onSeeked(qlonglong position)
0709 {
0710     m_position = position;
0711 }
0712 
0713 #include "moc_playercontainer.cpp"