[Linux] DBus fixes

This commit is contained in:
Tim Gromeyer
2025-06-08 22:06:01 +02:00
committed by Tim Gromeyer
parent 48ae249405
commit 91ffaaa972
5 changed files with 164 additions and 59 deletions

View File

@@ -44,7 +44,7 @@ void MediaController::handleEarDetection(EarDetection *earDetection)
if (shouldPause && isActiveOutputDeviceAirPods()) if (shouldPause && isActiveOutputDeviceAirPods())
{ {
if (m_mediaState == Playing) if (getCurrentMediaState() == Playing)
{ {
pause(); pause();
} }
@@ -172,7 +172,7 @@ void MediaController::setConnectedDeviceMacAddress(const QString &macAddress) {
} }
MediaController::MediaState MediaController::mediaStateFromPlayerctlOutput( MediaController::MediaState MediaController::mediaStateFromPlayerctlOutput(
const QString &output) { const QString &output) const {
if (output == "Playing") { if (output == "Playing") {
return MediaState::Playing; return MediaState::Playing;
} else if (output == "Paused") { } 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 return mediaStateFromPlayerctlOutput(PlayerStatusWatcher::getCurrentPlaybackStatus(""));
QDBusConnection sessionBus = QDBusConnection::sessionBus();
QDBusInterface dbusInterface("org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", sessionBus);
QDBusReply<QStringList> 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);
} }
bool MediaController::sendMediaPlayerCommand(const QString &method) bool MediaController::sendMediaPlayerCommand(const QString &method)
{ {
QDBusInterface *iface = getMediaPlayerInterface(); // Connect to the session bus
if (!iface) 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; return false;
} }
// Use QDBusMessage for more control and error handling bool success = false;
QDBusMessage message = QDBusMessage::createMethodCall( // Try each MPRIS service until one succeeds
iface->service(), for (const QString &service : mprisServices)
iface->path(),
iface->interface(),
method);
QDBusPendingCall call = iface->connection().asyncCall(message);
call.waitForFinished();
if (call.isError())
{ {
LOG_ERROR("Failed to execute " << method << ": " << call.error().message()); QDBusInterface playerInterface(
delete iface; service,
return false; "/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<void> 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; if (!success)
return true; {
LOG_ERROR("No media player responded successfully to " << method);
}
return success;
} }
void MediaController::play() void MediaController::play()

View File

@@ -43,16 +43,15 @@ public:
void play(); void play();
void pause(); void pause();
MediaState getCurrentMediaState() const { return m_mediaState; }; MediaState getCurrentMediaState() const;
Q_SIGNALS: Q_SIGNALS:
void mediaStateChanged(MediaState state); void mediaStateChanged(MediaState state);
private: private:
MediaState mediaStateFromPlayerctlOutput(const QString &output); MediaState mediaStateFromPlayerctlOutput(const QString &output) const;
QString getAudioDeviceName(); QString getAudioDeviceName();
bool sendMediaPlayerCommand(const QString &method); bool sendMediaPlayerCommand(const QString &method);
QDBusInterface *getMediaPlayerInterface();
bool wasPausedByApp = false; bool wasPausedByApp = false;
int initialVolume = -1; int initialVolume = -1;
@@ -60,7 +59,6 @@ private:
EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved; EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved;
QString m_deviceOutputName; QString m_deviceOutputName;
PlayerStatusWatcher *playerStatusWatcher = nullptr; PlayerStatusWatcher *playerStatusWatcher = nullptr;
MediaState m_mediaState = Stopped;
}; };
#endif // MEDIACONTROLLER_H #endif // MEDIACONTROLLER_H

View File

@@ -3,6 +3,7 @@
#include <QDBusPendingReply> #include <QDBusPendingReply>
#include <QVariantMap> #include <QVariantMap>
#include <QDBusReply> #include <QDBusReply>
#include <QDBusConnectionInterface>
PlayerStatusWatcher::PlayerStatusWatcher(const QString &playerService, QObject *parent) PlayerStatusWatcher::PlayerStatusWatcher(const QString &playerService, QObject *parent)
: QObject(parent), : QObject(parent),
@@ -44,4 +45,26 @@ void PlayerStatusWatcher::onServiceOwnerChanged(const QString &name, const QStri
} else if (name == m_playerService && !newOwner.isEmpty()) { } else if (name == m_playerService && !newOwner.isEmpty()) {
updateStatus(); // player appeared/reappeared 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();
} }

View File

@@ -8,6 +8,7 @@ class PlayerStatusWatcher : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit PlayerStatusWatcher(const QString &playerService, QObject *parent = nullptr); explicit PlayerStatusWatcher(const QString &playerService, QObject *parent = nullptr);
static QString getCurrentPlaybackStatus(const QString &playerService);
signals: signals:
void playbackStatusChanged(const QString &status); void playbackStatusChanged(const QString &status);

View File

@@ -0,0 +1,77 @@
#include "media/playerstatuswatcher.h"
#include <QDBusConnection>
#include <QDBusPendingReply>
#include <QVariantMap>
#include <QDBusReply>
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
}
}