File indexing completed on 2023-05-30 11:40:29
0001 /* 0002 Now playing 0003 Copyright (C) 2017 James D. Smith <smithjd15@gmail.com> 0004 Copyright (C) 2011 Martin Klapetek <martin.klapetek@gmail.com> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Lesser General Public 0008 License as published by the Free Software Foundation; either 0009 version 2.1 of the License, or (at your option) any later version. 0010 0011 This library is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 Lesser General Public License for more details. 0015 0016 You should have received a copy of the GNU Lesser General Public 0017 License along with this library; if not, write to the Free Software 0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 0019 */ 0020 0021 #include "telepathy-mpris.h" 0022 #include "ktp_kded_debug.h" 0023 0024 #include <KConfig> 0025 #include <KConfigGroup> 0026 #include <KSharedConfig> 0027 0028 #include <QTimer> 0029 #include <QDBusConnectionInterface> 0030 #include <QDBusInterface> 0031 #include <QDBusReply> 0032 0033 static const QLatin1String dbusInterfaceProperties("org.freedesktop.DBus.Properties"); 0034 0035 static const QLatin1String mprisPath("/org/mpris/MediaPlayer2"); 0036 static const QLatin1String mprisServicePrefix("org.mpris.MediaPlayer2"); 0037 static const QLatin1String mprisInterfaceName("org.mpris.MediaPlayer2.Player"); 0038 0039 TelepathyMPRIS::TelepathyMPRIS(QObject* parent) 0040 : QObject(parent), 0041 m_activationTimer(new QTimer()), 0042 m_activePlayer(new Player()) 0043 { 0044 connect(this, &TelepathyMPRIS::playerChange, &m_initLoop, &QEventLoop::quit); 0045 0046 m_activationTimer->setSingleShot(true); 0047 m_activationTimer->setInterval(400); 0048 } 0049 0050 TelepathyMPRIS::~TelepathyMPRIS() 0051 { 0052 } 0053 0054 void TelepathyMPRIS::enable(bool enable) 0055 { 0056 if (enable && !m_timerConnection) { 0057 //player changed 0058 m_timerConnection = QObject::connect(m_activationTimer, &QTimer::timeout, m_activationTimer, [=] { 0059 auto getPlayers = [this] (Service state) { 0060 QList<Player*> players; 0061 for (Player *watchedPlayer : m_players.values()) { 0062 if (watchedPlayer->playState == state) { 0063 players << watchedPlayer; 0064 } 0065 } 0066 0067 return players; 0068 }; 0069 0070 if (m_activePlayer->playState < Paused) { 0071 QList<Player*> players = QList<Player*>() << getPlayers(Playing) << getPlayers(Paused); 0072 0073 //if the players list is empty create a new default active player 0074 if (players.isEmpty()) { 0075 m_activePlayer = new Player(); 0076 } else { 0077 m_activePlayer = players.at(0); 0078 } 0079 0080 qCDebug(KTP_KDED_MODULE) << "Active player changed:" << m_players.key(m_activePlayer); 0081 } 0082 0083 if (!m_initLoop.isRunning()) { 0084 Q_EMIT playerChange(); 0085 } else { 0086 m_initLoop.quit(); 0087 } 0088 }); 0089 0090 //get registered service names asynchronously 0091 QDBusPendingCall async = QDBusConnection::sessionBus().interface()->asyncCall(QLatin1String("ListNames")); 0092 QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); 0093 connect(callWatcher, &QDBusPendingCallWatcher::finished, callWatcher, [=] { 0094 bool foundPlayers = false; 0095 QDBusPendingReply<QStringList> reply = *callWatcher; 0096 if (reply.isError()) { 0097 qCDebug(KTP_KDED_MODULE) << reply.error(); 0098 return; 0099 } 0100 0101 for (const QString &serviceName : reply.value()) { 0102 if (serviceName.startsWith(mprisServicePrefix)) { 0103 requestPlaybackStatus(serviceName, QDBusConnection::sessionBus().interface()->serviceOwner(serviceName)); 0104 foundPlayers = true; 0105 } 0106 } 0107 0108 if (!foundPlayers) { 0109 m_initLoop.quit(); 0110 } 0111 0112 callWatcher->deleteLater(); 0113 }); 0114 0115 //watch for mpris2-enabled players appearing and disappearing 0116 connect(QDBusConnection::sessionBus().interface(), 0117 &QDBusConnectionInterface::serviceOwnerChanged, 0118 this, 0119 &TelepathyMPRIS::serviceOwnerChanged); 0120 0121 m_initLoop.exec(); 0122 } else if (!enable) { 0123 disconnect(m_timerConnection); 0124 disconnect(QDBusConnection::sessionBus().interface(), 0125 &QDBusConnectionInterface::serviceOwnerChanged, 0126 this, 0127 &TelepathyMPRIS::serviceOwnerChanged); 0128 0129 QHash<QString, QString> serviceNameByOwner = m_serviceNameByOwner; 0130 for (const QString &serviceName : serviceNameByOwner) { 0131 serviceOwnerChanged(serviceName, serviceNameByOwner.key(serviceName), QString()); 0132 } 0133 } 0134 } 0135 0136 void TelepathyMPRIS::onPlayerSignalReceived(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties) 0137 { 0138 Q_UNUSED(invalidatedProperties) 0139 0140 //this is not the correct property interface, ignore 0141 if (interface != mprisInterfaceName) { 0142 return; 0143 } 0144 0145 const QString &serviceName = m_serviceNameByOwner[QDBusConnection::sessionBus().interface()->serviceOwner(message().service())]; 0146 sortPlayerReply(changedProperties, serviceName); 0147 } 0148 0149 void TelepathyMPRIS::requestPlaybackStatus(const QString &serviceName, const QString &owner) 0150 { 0151 auto playerConnected = [=] () { 0152 if (m_players.keys().contains(serviceName)) { 0153 return true; 0154 } 0155 0156 bool connected = QDBusConnection::sessionBus().connect(serviceName, 0157 mprisPath, 0158 dbusInterfaceProperties, 0159 QLatin1String("PropertiesChanged"), 0160 this, 0161 SLOT(onPlayerSignalReceived(QString,QVariantMap,QStringList))); 0162 0163 if (connected) { 0164 qCDebug(KTP_KDED_MODULE) << "Found player" << serviceName; 0165 Player *player = new Player(); 0166 m_players.insert(serviceName, player); 0167 m_serviceNameByOwner.insert(owner, serviceName); 0168 } 0169 0170 return connected; 0171 }; 0172 0173 QDBusMessage mprisMsg = QDBusMessage::createMethodCall(serviceName, mprisPath, dbusInterfaceProperties, QLatin1String("GetAll")); 0174 mprisMsg.setArguments(QList<QVariant>() << mprisInterfaceName); 0175 0176 QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(mprisMsg); 0177 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); 0178 connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [=] { 0179 QDBusPendingReply<QVariantMap> reply = *watcher; 0180 0181 if (reply.isError()) { 0182 qCWarning(KTP_KDED_MODULE) << "Received error reply from DBus" << reply.error() << "service" << serviceName; 0183 } else if (playerConnected()) { 0184 sortPlayerReply(reply.value(), serviceName); 0185 } 0186 0187 watcher->deleteLater(); 0188 }); 0189 } 0190 0191 void TelepathyMPRIS::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) 0192 { 0193 auto playerVanished = [=] (const QString &serviceName, const QString &oldOwner) { 0194 if (m_players.keys().contains(serviceName)) { 0195 QDBusConnection::sessionBus().disconnect(serviceName, 0196 mprisPath, 0197 dbusInterfaceProperties, 0198 QLatin1String("PropertiesChanged"), 0199 this, 0200 SLOT(onPlayerSignalReceived(QString,QVariantMap,QStringList))); 0201 0202 m_players[serviceName]->playState = Service::Unknown; 0203 if (m_players[serviceName] == m_activePlayer) { 0204 m_activationTimer->start(); 0205 } 0206 m_players.remove(serviceName); 0207 m_serviceNameByOwner.remove(oldOwner); 0208 qCDebug(KTP_KDED_MODULE) << "Player" << serviceName << "is no longer available"; 0209 } 0210 }; 0211 0212 if (serviceName.startsWith(mprisServicePrefix)) { 0213 qCDebug(KTP_KDED_MODULE) << "DBus service name change:" << serviceName << "once owned by" << oldOwner << "is now owned by" << newOwner; 0214 if (oldOwner.isEmpty()) { 0215 //if we have no oldOwner, we have new player registered at dbus 0216 requestPlaybackStatus(serviceName, newOwner); 0217 } else if (newOwner.isEmpty()) { 0218 //if there's no newOwner, the player quit 0219 playerVanished(serviceName, oldOwner); 0220 } else if (!oldOwner.isEmpty() && !newOwner.isEmpty()) { 0221 //otherwise the service name owner changed 0222 m_serviceNameByOwner.remove(oldOwner); 0223 m_serviceNameByOwner.insert(newOwner, serviceName); 0224 requestPlaybackStatus(serviceName, newOwner); 0225 } 0226 } 0227 } 0228 0229 void TelepathyMPRIS::sortPlayerReply(const QVariantMap &serviceInfo, const QString &serviceName) 0230 { 0231 bool playerChanged = false; 0232 0233 //sort and store incoming metadata 0234 if (serviceInfo.keys().contains(QLatin1String("Metadata"))) { 0235 QVariantMap metadata = qdbus_cast<QVariantMap>(serviceInfo.value(QLatin1String("Metadata"))); 0236 if (m_players[serviceName]->metadata != metadata) { 0237 m_players[serviceName]->metadata = metadata; 0238 playerChanged = true; 0239 } 0240 } 0241 0242 //sort and store incoming playback information 0243 if (serviceInfo.keys().contains(QLatin1String("PlaybackStatus"))) { 0244 QVariant playbackStatus = serviceInfo.value(QLatin1String("PlaybackStatus")); 0245 auto playState = [] (const QVariant &playbackStatus) { 0246 TelepathyMPRIS::Service playState; 0247 if (playbackStatus == QLatin1String("Playing")) { 0248 playState = Service::Playing; 0249 } else if (playbackStatus == QLatin1String("Paused")) { 0250 playState = Service::Paused; 0251 } else if (playbackStatus == QLatin1String("Stopped")) { 0252 playState = Service::Stopped; 0253 } else { 0254 playState = Service::Unknown; 0255 } 0256 0257 return playState; 0258 }; 0259 0260 if (m_players[serviceName]->playState != playState(playbackStatus)) { 0261 if ((m_players[serviceName]->playState == Unknown) && (playState(playbackStatus) < Playing)) { 0262 playerChanged = false; 0263 } else { 0264 playerChanged = true; 0265 } 0266 0267 m_players[serviceName]->playState = playState(serviceInfo.value(QLatin1String("PlaybackStatus"))); 0268 } 0269 } 0270 0271 qCDebug(KTP_KDED_MODULE) << "Player" << serviceName << m_players[serviceName]->playState; 0272 0273 if (playerChanged || m_initLoop.isRunning()) { 0274 m_activationTimer->start(); 0275 } 0276 }