File indexing completed on 2024-05-19 05:38:36
0001 /* 0002 SPDX-FileCopyrightText: 2007-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 "mpris2sourcemodel.h" 0009 0010 #include <QDBusConnection> 0011 #include <QDBusConnectionInterface> 0012 #include <QDBusPendingCallWatcher> 0013 #include <QDBusPendingReply> 0014 #include <QDBusServiceWatcher> 0015 #include <QStringList> 0016 0017 #include "libkmpris_debug.h" 0018 #include "playercontainer.h" 0019 0020 std::shared_ptr<Mpris2SourceModel> Mpris2SourceModel::self() 0021 { 0022 static std::weak_ptr<Mpris2SourceModel> s_model; 0023 if (s_model.expired()) { 0024 std::shared_ptr<Mpris2SourceModel> ptr{new Mpris2SourceModel}; 0025 s_model = ptr; 0026 return ptr; 0027 } 0028 0029 return s_model.lock(); 0030 } 0031 0032 Mpris2SourceModel::Mpris2SourceModel(QObject *parent) 0033 : QAbstractListModel(parent) 0034 { 0035 auto watcher = 0036 new QDBusServiceWatcher(QStringLiteral("org.mpris.MediaPlayer2*"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); 0037 connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &Mpris2SourceModel::onServiceOwnerChanged); 0038 0039 fetchServiceNames(); 0040 } 0041 0042 Mpris2SourceModel::~Mpris2SourceModel() 0043 { 0044 } 0045 0046 QVariant Mpris2SourceModel::dataFromPlayer(PlayerContainer *container, int role) 0047 { 0048 switch (role) { 0049 case TrackRole: 0050 return container->track(); 0051 case CanControlRole: 0052 return container->canControl(); 0053 case CanGoNextRole: 0054 return container->canGoNext(); 0055 case CanGoPreviousRole: 0056 return container->canGoPrevious(); 0057 case CanPlayRole: 0058 return container->canPlay(); 0059 case CanPauseRole: 0060 return container->canPause(); 0061 case CanStopRole: 0062 return container->canStop(); 0063 case CanSeekRole: 0064 return container->canSeek(); 0065 case LoopStatusRole: 0066 return container->loopStatus(); 0067 case PlaybackStatusRole: 0068 return container->playbackStatus(); 0069 case PositionRole: 0070 return container->position(); 0071 case RateRole: 0072 return container->rate(); 0073 case ShuffleRole: 0074 return container->shuffle(); 0075 case VolumeRole: 0076 return container->volume(); 0077 case ArtUrlRole: 0078 return container->artUrl(); 0079 case ArtistRole: 0080 return container->artist(); 0081 case AlbumRole: 0082 return container->album(); 0083 case LengthRole: 0084 return container->length(); 0085 case CanQuitRole: 0086 return container->canQuit(); 0087 case CanRaiseRole: 0088 return container->canRaise(); 0089 case CanSetFullscreenRole: 0090 return container->canSetFullscreen(); 0091 case DesktopEntryRole: 0092 return container->desktopEntry(); 0093 case IdentityRole: 0094 return container->identity(); 0095 case IconNameRole: 0096 return container->iconName(); 0097 case InstancePidRole: 0098 return container->instancePid(); 0099 case KDEPidRole: 0100 return container->kdePid(); 0101 case ContainerRole: 0102 return QVariant::fromValue(container); 0103 default: 0104 return {}; 0105 } 0106 } 0107 0108 QVariant Mpris2SourceModel::data(const QModelIndex &index, int role) const 0109 { 0110 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) { 0111 return {}; 0112 } 0113 0114 return dataFromPlayer(m_containers.at(index.row()), role); 0115 } 0116 0117 bool Mpris2SourceModel::setData(const QModelIndex &index, const QVariant &value, int role) 0118 { 0119 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) { 0120 return {}; 0121 } 0122 0123 bool ok = false; 0124 switch (PlayerContainer *const container = m_containers.at(index.row()); role) { 0125 case LoopStatusRole: 0126 Q_ASSERT_X(value.toUInt() <= qToUnderlying(LoopStatus::Track), Q_FUNC_INFO, qUtf8Printable(QString::number(value.toUInt()))); 0127 if (value.toUInt() <= qToUnderlying(LoopStatus::Track)) { 0128 container->setLoopStatus(static_cast<LoopStatus::Status>(value.toUInt(&ok))); 0129 return ok; 0130 } 0131 break; 0132 case PlaybackStatusRole: 0133 Q_ASSERT_X(value.toUInt() <= qToUnderlying(PlaybackStatus::Paused), Q_FUNC_INFO, qUtf8Printable(QString::number(value.toUInt()))); 0134 if (value.toUInt() <= qToUnderlying(PlaybackStatus::Paused)) { 0135 container->setPlaybackStatus(static_cast<PlaybackStatus::Status>(value.toUInt(&ok))); 0136 return ok; 0137 } 0138 break; 0139 case PositionRole: 0140 container->setPosition(value.toLongLong(&ok)); 0141 return ok; 0142 case ShuffleRole: 0143 Q_ASSERT_X(value.toUInt() <= qToUnderlying(ShuffleStatus::On), Q_FUNC_INFO, qUtf8Printable(QString::number(value.toUInt()))); 0144 if (value.toUInt() <= qToUnderlying(ShuffleStatus::On)) { 0145 container->setShuffle(static_cast<ShuffleStatus::Status>(value.toUInt(&ok))); 0146 return ok; 0147 } 0148 break; 0149 case VolumeRole: 0150 Q_ASSERT_X(value.toDouble() >= 0.0 && value.toDouble() <= 1.0, Q_FUNC_INFO, qUtf8Printable(QString::number(value.toDouble()))); 0151 if (value.toDouble() >= 0.0 && value.toDouble() <= 1.0) { 0152 container->setVolume(value.toDouble(&ok)); 0153 return ok; 0154 } 0155 break; 0156 default: 0157 break; 0158 } 0159 0160 return false; 0161 } 0162 0163 int Mpris2SourceModel::rowCount(const QModelIndex &parent) const 0164 { 0165 return parent.isValid() ? 0 : m_containers.size(); 0166 } 0167 0168 QHash<int, QByteArray> Mpris2SourceModel::roleNames() const 0169 { 0170 return { 0171 {TrackRole, QByteArrayLiteral("track")}, 0172 {CanControlRole, QByteArrayLiteral("canControl")}, 0173 {CanGoNextRole, QByteArrayLiteral("canGoNext")}, 0174 {CanGoPreviousRole, QByteArrayLiteral("canGoPrevious")}, 0175 {CanPlayRole, QByteArrayLiteral("canPlay")}, 0176 {CanPauseRole, QByteArrayLiteral("canPause")}, 0177 {CanStopRole, QByteArrayLiteral("canStop")}, 0178 {CanSeekRole, QByteArrayLiteral("canSeek")}, 0179 {LoopStatusRole, QByteArrayLiteral("loopStatus")}, 0180 {PlaybackStatusRole, QByteArrayLiteral("playbackStatus")}, 0181 {PositionRole, QByteArrayLiteral("position")}, 0182 {RateRole, QByteArrayLiteral("rate")}, 0183 {ShuffleRole, QByteArrayLiteral("shuffle")}, 0184 {VolumeRole, QByteArrayLiteral("volume")}, 0185 {ArtUrlRole, QByteArrayLiteral("artUrl")}, 0186 {ArtistRole, QByteArrayLiteral("artist")}, 0187 {AlbumRole, QByteArrayLiteral("album")}, 0188 {LengthRole, QByteArrayLiteral("length")}, 0189 {CanQuitRole, QByteArrayLiteral("canQuit")}, 0190 {CanRaiseRole, QByteArrayLiteral("canRaise")}, 0191 {CanSetFullscreenRole, QByteArrayLiteral("canSetFullscreen")}, 0192 {DesktopEntryRole, QByteArrayLiteral("desktopEntry")}, 0193 {IdentityRole, QByteArrayLiteral("identity")}, 0194 {IconNameRole, QByteArrayLiteral("iconName")}, 0195 {KDEPidRole, QByteArrayLiteral("kdePid")}, 0196 {ContainerRole, QByteArrayLiteral("container")}, 0197 }; 0198 } 0199 0200 void Mpris2SourceModel::onServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) 0201 { 0202 if (!serviceName.startsWith(MPRIS2_PREFIX)) { 0203 return; 0204 } 0205 0206 const QString sourceName = serviceName.sliced(MPRIS2_PATH.size()); 0207 0208 if (!oldOwner.isEmpty()) { 0209 qCDebug(MPRIS2) << "MPRIS service" << serviceName << "just went offline"; 0210 removeMediaPlayer(sourceName); 0211 } 0212 0213 if (!newOwner.isEmpty()) { 0214 qCDebug(MPRIS2) << "MPRIS service" << serviceName << "just came online"; 0215 addMediaPlayer(serviceName, sourceName); 0216 } 0217 } 0218 0219 void Mpris2SourceModel::onServiceNamesFetched(QDBusPendingCallWatcher *watcher) 0220 { 0221 QDBusPendingReply<QStringList> propsReply = *watcher; 0222 watcher->deleteLater(); 0223 0224 if (propsReply.isError()) { 0225 qCWarning(MPRIS2) << "Could not get list of available D-Bus services"; 0226 } else { 0227 for (const QStringList names = propsReply.value(); const QString &serviceName : names) { 0228 if (!serviceName.startsWith(MPRIS2_PREFIX)) { 0229 continue; 0230 } 0231 0232 qCDebug(MPRIS2) << "Found MPRIS2 service" << serviceName; 0233 // watch out for race conditions; the media player could 0234 // have appeared between starting the service watcher and 0235 // this call being dealt with 0236 // NB: _disappearing_ between sending this call and doing 0237 // this processing is fine 0238 const QString sourceName = serviceName.sliced(MPRIS2_PREFIX.size()); 0239 const bool exist = std::any_of(m_containers.cbegin(), m_containers.cend(), [&sourceName](PlayerContainer *c) { 0240 return c->objectName() == sourceName; 0241 }); 0242 if (!exist && !m_pendingContainers.contains(sourceName)) { 0243 qCDebug(MPRIS2) << "Haven't already seen" << serviceName; 0244 addMediaPlayer(serviceName, sourceName); 0245 } 0246 } 0247 } 0248 } 0249 0250 void Mpris2SourceModel::onInitialFetchFinished(PlayerContainer *container) 0251 { 0252 qCDebug(MPRIS2) << "Props fetch for" << container->objectName() << "finished; adding"; 0253 const auto erased = m_pendingContainers.erase(container->objectName()); 0254 Q_ASSERT_X(erased == 1, Q_FUNC_INFO, qUtf8Printable(container->objectName())); 0255 0256 // don't let future refreshes trigger this 0257 disconnect(container, &PlayerContainer::initialFetchFinished, this, &Mpris2SourceModel::onInitialFetchFinished); 0258 disconnect(container, &PlayerContainer::initialFetchFailed, this, &Mpris2SourceModel::onInitialFetchFailed); 0259 0260 // Check if the player follows the specification dutifully. 0261 if (container->identity().isEmpty()) { 0262 qCDebug(MPRIS2) << "MPRIS2 service" << container->objectName() << "isn't standard-compliant, ignoring"; 0263 return; 0264 } 0265 0266 const int row = m_containers.size(); 0267 beginInsertRows(QModelIndex(), row, row); 0268 0269 m_containers.push_back(container); 0270 0271 endInsertRows(); 0272 0273 // Property bindings 0274 connect(container, &AbstractPlayerContainer::canControlChanged, this, [this] { 0275 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0276 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanControlRole}); 0277 }); 0278 connect(container, &AbstractPlayerContainer::trackChanged, this, [this] { 0279 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0280 Q_EMIT dataChanged(index(row, 0), index(row, 0), {TrackRole}); 0281 }); 0282 connect(container, &AbstractPlayerContainer::canGoNextChanged, this, [this] { 0283 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0284 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanGoNextRole}); 0285 }); 0286 connect(container, &AbstractPlayerContainer::canGoPreviousChanged, this, [this] { 0287 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0288 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanGoPreviousRole}); 0289 }); 0290 connect(container, &AbstractPlayerContainer::canPlayChanged, this, [this] { 0291 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0292 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanPlayRole}); 0293 }); 0294 connect(container, &AbstractPlayerContainer::canPauseChanged, this, [this] { 0295 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0296 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanPauseRole}); 0297 }); 0298 connect(container, &AbstractPlayerContainer::canStopChanged, this, [this] { 0299 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0300 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanStopRole}); 0301 }); 0302 connect(container, &AbstractPlayerContainer::canSeekChanged, this, [this] { 0303 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0304 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanSeekRole}); 0305 }); 0306 connect(container, &AbstractPlayerContainer::loopStatusChanged, this, [this] { 0307 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0308 Q_EMIT dataChanged(index(row, 0), index(row, 0), {LoopStatusRole}); 0309 }); 0310 connect(container, &AbstractPlayerContainer::playbackStatusChanged, this, [this] { 0311 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0312 Q_EMIT dataChanged(index(row, 0), index(row, 0), {PlaybackStatusRole}); 0313 }); 0314 connect(container, &AbstractPlayerContainer::positionChanged, this, [this] { 0315 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0316 Q_EMIT dataChanged(index(row, 0), index(row, 0), {PositionRole}); 0317 }); 0318 connect(container, &AbstractPlayerContainer::rateChanged, this, [this] { 0319 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0320 Q_EMIT dataChanged(index(row, 0), index(row, 0), {RateRole}); 0321 }); 0322 connect(container, &AbstractPlayerContainer::shuffleChanged, this, [this] { 0323 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0324 Q_EMIT dataChanged(index(row, 0), index(row, 0), {ShuffleRole}); 0325 }); 0326 connect(container, &AbstractPlayerContainer::volumeChanged, this, [this] { 0327 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0328 Q_EMIT dataChanged(index(row, 0), index(row, 0), {VolumeRole}); 0329 }); 0330 connect(container, &AbstractPlayerContainer::artUrlChanged, this, [this] { 0331 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0332 Q_EMIT dataChanged(index(row, 0), index(row, 0), {ArtUrlRole}); 0333 }); 0334 connect(container, &AbstractPlayerContainer::artistChanged, this, [this] { 0335 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0336 Q_EMIT dataChanged(index(row, 0), index(row, 0), {ArtistRole}); 0337 }); 0338 connect(container, &AbstractPlayerContainer::albumChanged, this, [this] { 0339 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0340 Q_EMIT dataChanged(index(row, 0), index(row, 0), {AlbumRole}); 0341 }); 0342 connect(container, &AbstractPlayerContainer::lengthChanged, this, [this] { 0343 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0344 Q_EMIT dataChanged(index(row, 0), index(row, 0), {LengthRole}); 0345 }); 0346 connect(container, &AbstractPlayerContainer::canQuitChanged, this, [this] { 0347 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0348 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanQuitRole}); 0349 }); 0350 connect(container, &AbstractPlayerContainer::canRaiseChanged, this, [this] { 0351 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0352 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanRaiseRole}); 0353 }); 0354 connect(container, &AbstractPlayerContainer::canSetFullscreenChanged, this, [this] { 0355 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0356 Q_EMIT dataChanged(index(row, 0), index(row, 0), {CanSetFullscreenRole}); 0357 }); 0358 connect(container, &AbstractPlayerContainer::desktopEntryChanged, this, [this] { 0359 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0360 Q_EMIT dataChanged(index(row, 0), index(row, 0), {DesktopEntryRole, IconNameRole}); 0361 }); 0362 connect(container, &AbstractPlayerContainer::identityChanged, this, [this] { 0363 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0364 Q_EMIT dataChanged(index(row, 0), index(row, 0), {IdentityRole}); 0365 }); 0366 connect(container, &AbstractPlayerContainer::kdePidChanged, this, [this] { 0367 const int row = std::distance(m_containers.cbegin(), std::find(m_containers.cbegin(), m_containers.cend(), sender())); 0368 Q_EMIT dataChanged(index(row, 0), index(row, 0), {KDEPidRole}); 0369 }); 0370 } 0371 0372 void Mpris2SourceModel::onInitialFetchFailed(PlayerContainer *container) 0373 { 0374 qCDebug(MPRIS2) << "Failed to find a working MPRIS2 interface for" << container->dbusAddress(); 0375 const auto erased = m_pendingContainers.erase(container->objectName()); 0376 Q_ASSERT_X(erased == 1, Q_FUNC_INFO, qUtf8Printable(container->objectName())); 0377 delete container; 0378 } 0379 0380 void Mpris2SourceModel::fetchServiceNames() 0381 { 0382 QDBusPendingCall call = QDBusConnection::sessionBus().interface()->asyncCall(QStringLiteral("ListNames")); 0383 auto watcher = new QDBusPendingCallWatcher(call, this); 0384 connect(watcher, &QDBusPendingCallWatcher::finished, this, &Mpris2SourceModel::onServiceNamesFetched); 0385 } 0386 0387 void Mpris2SourceModel::addMediaPlayer(const QString &serviceName, const QString &sourceName) 0388 { 0389 Q_ASSERT_X(!m_pendingContainers.contains(sourceName), Q_FUNC_INFO, qUtf8Printable(sourceName)); 0390 0391 PlayerContainer *const container = new PlayerContainer(serviceName, this); 0392 container->setObjectName(sourceName); 0393 m_pendingContainers.emplace(sourceName, container); 0394 0395 connect(container, &PlayerContainer::initialFetchFinished, this, &Mpris2SourceModel::onInitialFetchFinished); 0396 connect(container, &PlayerContainer::initialFetchFailed, this, &Mpris2SourceModel::onInitialFetchFailed); 0397 } 0398 0399 void Mpris2SourceModel::removeMediaPlayer(const QString &sourceName) 0400 { 0401 auto it = std::find_if(m_containers.begin(), m_containers.end(), [&sourceName](PlayerContainer *c) { 0402 return c->objectName() == sourceName; 0403 }); 0404 if (it == m_containers.end()) { 0405 auto pendingIt = m_pendingContainers.find(sourceName); 0406 if (pendingIt == m_pendingContainers.end()) [[unlikely]] { 0407 // This can happen when a player appears and disappears on DBus before the service list is fetched 0408 // or a player is invalid (like lacking identity) 0409 return; 0410 } 0411 delete pendingIt->second; 0412 m_pendingContainers.erase(pendingIt); 0413 return; 0414 } 0415 0416 PlayerContainer *container = *it; 0417 disconnect(container, nullptr, this, nullptr); 0418 const int row = std::distance(m_containers.begin(), it); 0419 beginRemoveRows(QModelIndex(), row, row); 0420 m_containers.erase(it); 0421 endRemoveRows(); 0422 0423 delete container; 0424 } 0425 0426 #include "moc_mpris2sourcemodel.cpp"