From 91ffaaa972f2be69ed0219d3b3f0ca2d3c203d80 Mon Sep 17 00:00:00 2001 From: Tim Gromeyer Date: Sun, 8 Jun 2025 22:06:01 +0200 Subject: [PATCH] [Linux] DBus fixes --- linux/media/mediacontroller.cpp | 116 +++++++++++++++------------- linux/media/mediacontroller.h | 6 +- linux/media/playerstatuswatcher.cpp | 23 ++++++ linux/media/playerstatuswatcher.h | 1 + linux/playerstatuswatcher.cpp | 77 ++++++++++++++++++ 5 files changed, 164 insertions(+), 59 deletions(-) create mode 100644 linux/playerstatuswatcher.cpp diff --git a/linux/media/mediacontroller.cpp b/linux/media/mediacontroller.cpp index 6257335..1055e2f 100644 --- a/linux/media/mediacontroller.cpp +++ b/linux/media/mediacontroller.cpp @@ -44,7 +44,7 @@ void MediaController::handleEarDetection(EarDetection *earDetection) if (shouldPause && isActiveOutputDeviceAirPods()) { - if (m_mediaState == Playing) + if (getCurrentMediaState() == Playing) { pause(); } @@ -172,7 +172,7 @@ void MediaController::setConnectedDeviceMacAddress(const QString &macAddress) { } MediaController::MediaState MediaController::mediaStateFromPlayerctlOutput( - const QString &output) { + const QString &output) const { if (output == "Playing") { return MediaState::Playing; } else if (output == "Paused") { @@ -182,71 +182,77 @@ MediaController::MediaState MediaController::mediaStateFromPlayerctlOutput( } } -QDBusInterface *MediaController::getMediaPlayerInterface() +MediaController::MediaState MediaController::getCurrentMediaState() const { - // List all media player services - QDBusConnection sessionBus = QDBusConnection::sessionBus(); - QDBusInterface dbusInterface("org.freedesktop.DBus", "/org/freedesktop/DBus", - "org.freedesktop.DBus", sessionBus); - QDBusReply reply = dbusInterface.call("ListNames"); - - if (!reply.isValid()) - { - LOG_ERROR("Failed to list DBus services: " << reply.error().message()); - return nullptr; - } - - QStringList services = reply.value(); - QString mediaPlayerService; - - for (const QString &service : services) - { - if (service.startsWith("org.mpris.MediaPlayer2.")) - { - mediaPlayerService = service; - break; - } - } - - if (mediaPlayerService.isEmpty()) - { - LOG_DEBUG("No active media player found on DBus"); - return nullptr; - } - - LOG_DEBUG("Found media player service: " << mediaPlayerService); - return new QDBusInterface(mediaPlayerService, "/org/mpris/MediaPlayer2", - "org.mpris.MediaPlayer2.Player", sessionBus, this); + return mediaStateFromPlayerctlOutput(PlayerStatusWatcher::getCurrentPlaybackStatus("")); } bool MediaController::sendMediaPlayerCommand(const QString &method) { - QDBusInterface *iface = getMediaPlayerInterface(); - if (!iface) + // Connect to the session bus + QDBusConnection bus = QDBusConnection::sessionBus(); + + // Find available MPRIS-compatible media players + QStringList services = bus.interface()->registeredServiceNames().value(); + QStringList mprisServices; + for (const QString &service : services) { - LOG_ERROR("No media player interface available for " << method); + if (service.startsWith("org.mpris.MediaPlayer2.")) + { + mprisServices << service; + } + } + + if (mprisServices.isEmpty()) + { + LOG_ERROR("No MPRIS-compatible media players found on DBus"); return false; } - // Use QDBusMessage for more control and error handling - QDBusMessage message = QDBusMessage::createMethodCall( - iface->service(), - iface->path(), - iface->interface(), - method); - - QDBusPendingCall call = iface->connection().asyncCall(message); - call.waitForFinished(); - - if (call.isError()) + bool success = false; + // Try each MPRIS service until one succeeds + for (const QString &service : mprisServices) { - LOG_ERROR("Failed to execute " << method << ": " << call.error().message()); - delete iface; - return false; + QDBusInterface playerInterface( + service, + "/org/mpris/MediaPlayer2", + "org.mpris.MediaPlayer2.Player", + bus); + + if (!playerInterface.isValid()) + { + LOG_ERROR("Invalid DBus interface for service: " << service); + continue; + } + + // Send the Play or Pause command + if (method == "Play" || method == "Pause") + { + QDBusReply reply = playerInterface.call(method); + if (reply.isValid()) + { + LOG_INFO("Successfully sent " << method << " to " << service); + success = true; + break; // Exit after the first successful command + } + else + { + LOG_ERROR("Failed to send " << method << " to " << service + << ": " << reply.error().message()); + } + } + else + { + LOG_ERROR("Unsupported method: " << method); + return false; + } } - delete iface; - return true; + if (!success) + { + LOG_ERROR("No media player responded successfully to " << method); + } + return success; } void MediaController::play() diff --git a/linux/media/mediacontroller.h b/linux/media/mediacontroller.h index 6134d91..f7c75f3 100644 --- a/linux/media/mediacontroller.h +++ b/linux/media/mediacontroller.h @@ -43,16 +43,15 @@ public: void play(); void pause(); - MediaState getCurrentMediaState() const { return m_mediaState; }; + MediaState getCurrentMediaState() const; Q_SIGNALS: void mediaStateChanged(MediaState state); private: - MediaState mediaStateFromPlayerctlOutput(const QString &output); + MediaState mediaStateFromPlayerctlOutput(const QString &output) const; QString getAudioDeviceName(); bool sendMediaPlayerCommand(const QString &method); - QDBusInterface *getMediaPlayerInterface(); bool wasPausedByApp = false; int initialVolume = -1; @@ -60,7 +59,6 @@ private: EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved; QString m_deviceOutputName; PlayerStatusWatcher *playerStatusWatcher = nullptr; - MediaState m_mediaState = Stopped; }; #endif // MEDIACONTROLLER_H \ No newline at end of file diff --git a/linux/media/playerstatuswatcher.cpp b/linux/media/playerstatuswatcher.cpp index b8e8656..76e4d24 100644 --- a/linux/media/playerstatuswatcher.cpp +++ b/linux/media/playerstatuswatcher.cpp @@ -3,6 +3,7 @@ #include #include #include +#include PlayerStatusWatcher::PlayerStatusWatcher(const QString &playerService, QObject *parent) : QObject(parent), @@ -44,4 +45,26 @@ void PlayerStatusWatcher::onServiceOwnerChanged(const QString &name, const QStri } else if (name == m_playerService && !newOwner.isEmpty()) { updateStatus(); // player appeared/reappeared } +} + +QString PlayerStatusWatcher::getCurrentPlaybackStatus(const QString &playerService) +{ + QDBusConnection bus = QDBusConnection::sessionBus(); + QStringList services = bus.interface()->registeredServiceNames().value(); + + for (const QString &service : services) { + if (service.startsWith("org.mpris.MediaPlayer2.")) { + QDBusInterface iface(service, "/org/mpris/MediaPlayer2", + "org.mpris.MediaPlayer2.Player", bus); + + if (iface.isValid()) { + QVariant status = iface.property("PlaybackStatus"); + if (status.isValid() && status.toString() == "Playing") { + return status.toString(); + } + } + } + } + + return QString(); } \ No newline at end of file diff --git a/linux/media/playerstatuswatcher.h b/linux/media/playerstatuswatcher.h index 38a0d6b..c0d14c4 100644 --- a/linux/media/playerstatuswatcher.h +++ b/linux/media/playerstatuswatcher.h @@ -8,6 +8,7 @@ class PlayerStatusWatcher : public QObject { Q_OBJECT public: explicit PlayerStatusWatcher(const QString &playerService, QObject *parent = nullptr); + static QString getCurrentPlaybackStatus(const QString &playerService); signals: void playbackStatusChanged(const QString &status); diff --git a/linux/playerstatuswatcher.cpp b/linux/playerstatuswatcher.cpp new file mode 100644 index 0000000..38fb59c --- /dev/null +++ b/linux/playerstatuswatcher.cpp @@ -0,0 +1,77 @@ +#include "media/playerstatuswatcher.h" +#include +#include +#include +#include + +PlayerStatusWatcher::PlayerStatusWatcher(const QString &playerService, QObject *parent) + : QObject(parent), + m_playerService(playerService), + m_iface(new QDBusInterface(playerService, "/org/mpris/MediaPlayer2", + "org.mpris.MediaPlayer2.Player", QDBusConnection::sessionBus(), this)), + m_serviceWatcher(new QDBusServiceWatcher(playerService, QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForOwnerChange, this)) +{ + // Register this object on the session bus to receive D-Bus messages + QDBusConnection::sessionBus().registerObject("/PlayerStatusWatcher", this, + QDBusConnection::ExportAllSlots); + + QDBusConnection::sessionBus().connect( + playerService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", + "PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)) + ); + connect(m_serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, + this, &PlayerStatusWatcher::onServiceOwnerChanged); + updateStatus(); +} + +void PlayerStatusWatcher::onPropertiesChanged(const QString &interface, + const QVariantMap &changed, + const QStringList &) +{ + // Get the service name of the sender + QString sender = message().service(); + + // Skip if it's a KDE Connect player + if (sender.contains("kdeconnect", Qt::CaseInsensitive)) { + return; + } + + if (interface == "org.mpris.MediaPlayer2.Player" && changed.contains("PlaybackStatus")) { + emit playbackStatusChanged(changed.value("PlaybackStatus").toString()); + } +} + +void PlayerStatusWatcher::updateStatus() { + QVariant reply = m_iface->property("PlaybackStatus"); + if (reply.isValid()) { + emit playbackStatusChanged(reply.toString()); + } +} + +void PlayerStatusWatcher::onServiceOwnerChanged(const QString &name, const QString &, const QString &newOwner) +{ + if (name == m_playerService && newOwner.isEmpty()) { + emit playbackStatusChanged(""); // player disappeared + } else if (name == m_playerService && !newOwner.isEmpty()) { + updateStatus(); // player appeared/reappeared + } +} + +QString PlayerStatusWatcher::getCurrentPlaybackStatus(const QString &playerService) +{ + QDBusInterface iface( + playerService, + "/org/mpris/MediaPlayer2", + "org.mpris.MediaPlayer2.Player", + QDBusConnection::sessionBus()); + QVariant reply = iface.property("PlaybackStatus"); + if (reply.isValid()) + { + return reply.toString(); // "Playing", "Paused", "Stopped" + } + else + { + return QString(); // or handle error as needed + } +} \ No newline at end of file