[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 (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<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);
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<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;
return true;
if (!success)
{
LOG_ERROR("No media player responded successfully to " << method);
}
return success;
}
void MediaController::play()

View File

@@ -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

View File

@@ -3,6 +3,7 @@
#include <QDBusPendingReply>
#include <QVariantMap>
#include <QDBusReply>
#include <QDBusConnectionInterface>
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();
}

View File

@@ -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);

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
}
}