diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 5fe9f94..850467c 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -12,8 +12,8 @@ qt_standard_project_setup(REQUIRES 6.4) qt_add_executable(applinux main.cpp logger.h - mediacontroller.cpp - mediacontroller.h + media/mediacontroller.cpp + media/mediacontroller.h airpods_packets.h trayiconmanager.cpp trayiconmanager.h @@ -32,6 +32,8 @@ qt_add_executable(applinux thirdparty/QR-Code-generator/qrcodegen.hpp QRCodeImageProvider.hpp eardetection.hpp + media/playerstatuswatcher.cpp + media/playerstatuswatcher.h ) qt_add_qml_module(applinux diff --git a/linux/main.cpp b/linux/main.cpp index 4c8702e..d118890 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -14,7 +14,7 @@ #include "airpods_packets.h" #include "logger.h" -#include "mediacontroller.h" +#include "media/mediacontroller.h" #include "trayiconmanager.h" #include "enums.h" #include "battery.hpp" @@ -66,7 +66,6 @@ public: // Initialize MediaController and connect signals mediaController = new MediaController(this); connect(mediaController, &MediaController::mediaStateChanged, this, &AirPodsTrayApp::handleMediaStateChange); - mediaController->initializeMprisInterface(); mediaController->followMediaChanges(); monitor = new BluetoothMonitor(this); @@ -795,13 +794,6 @@ public: process.waitForFinished(); QString output = process.readAllStandardOutput().trimmed(); LOG_INFO("Bluetoothctl output: " << output); - if (output.contains("Connection successful")) { - LOG_INFO("Connection successful, proceeding with L2CAP connection"); - QBluetoothAddress btAddress(m_deviceInfo->bluetoothAddress()); - forceL2capConnection(btAddress); - } else { - LOG_ERROR("Connection failed, cannot proceed with L2CAP connection"); - } } QBluetoothLocalDevice localDevice; const QList connectedDevices = localDevice.connectedDevices(); @@ -816,31 +808,6 @@ public: LOG_WARN("AirPods not found among connected devices"); } - void forceL2capConnection(const QBluetoothAddress &address) { - LOG_INFO("Retrying L2CAP connection for up to 10 seconds..."); - QBluetoothDeviceInfo device(address, "", 0); - QElapsedTimer timer; - timer.start(); - while (timer.elapsed() < 10000) { - QProcess bcProcess; - bcProcess.start("bluetoothctl", QStringList() << "connect" << address.toString()); - bcProcess.waitForFinished(); - QString output = bcProcess.readAllStandardOutput().trimmed(); - LOG_INFO("Bluetoothctl output: " << output); - if (output.contains("Connection successful")) { - connectToDevice(device); - QThread::sleep(1); - if (socket && socket->isOpen()) { - LOG_INFO("Successfully connected to device: " << address.toString()); - return; - } - } else { - LOG_WARN("Connection attempt failed, retrying..."); - } - } - LOG_ERROR("Failed to connect to device within 10 seconds: " << address.toString()); - } - void initializeBluetooth() { connectToPhone(); diff --git a/linux/mediacontroller.cpp b/linux/media/mediacontroller.cpp similarity index 83% rename from linux/mediacontroller.cpp rename to linux/media/mediacontroller.cpp index c9db7b5..99e96d5 100644 --- a/linux/mediacontroller.cpp +++ b/linux/media/mediacontroller.cpp @@ -1,6 +1,7 @@ #include "mediacontroller.h" #include "logger.h" #include "eardetection.hpp" +#include "playerstatuswatcher.h" #include #include @@ -9,34 +10,6 @@ #include MediaController::MediaController(QObject *parent) : QObject(parent) { - // No additional initialization required here -} - -void MediaController::initializeMprisInterface() { - QStringList services = - QDBusConnection::sessionBus().interface()->registeredServiceNames(); - QString mprisService; - - for (const QString &service : services) { - if (service.startsWith("org.mpris.MediaPlayer2.") && - service != "org.mpris.MediaPlayer2") { - mprisService = service; - break; - } - } - - if (!mprisService.isEmpty()) { - mprisInterface = new QDBusInterface(mprisService, "/org/mpris/MediaPlayer2", - "org.mpris.MediaPlayer2.Player", - QDBusConnection::sessionBus(), this); - if (!mprisInterface->isValid()) { - LOG_ERROR("Failed to initialize MPRIS interface for service: ") << mprisService; - } else { - LOG_INFO("Connected to MPRIS service: " << mprisService); - } - } else { - LOG_WARN("No active MPRIS media players found"); - } } void MediaController::handleEarDetection(EarDetection *earDetection) @@ -118,16 +91,14 @@ void MediaController::setEarDetectionBehavior(EarDetectionBehavior behavior) } void MediaController::followMediaChanges() { - playerctlProcess = new QProcess(this); - connect(playerctlProcess, &QProcess::readyReadStandardOutput, this, - [this]() { - QString output = - playerctlProcess->readAllStandardOutput().trimmed(); - LOG_DEBUG("Playerctl output: " << output); - MediaState state = mediaStateFromPlayerctlOutput(output); + playerStatusWatcher = new PlayerStatusWatcher("", this); + connect(playerStatusWatcher, &PlayerStatusWatcher::playbackStatusChanged, + this, [this](const QString &status) + { + LOG_DEBUG("Playback status changed: " << status); + MediaState state = mediaStateFromPlayerctlOutput(status); emit mediaStateChanged(state); }); - playerctlProcess->start("playerctl", QStringList() << "--follow" << "status"); } bool MediaController::isActiveOutputDeviceAirPods() { @@ -241,13 +212,6 @@ void MediaController::pause() { } MediaController::~MediaController() { - if (playerctlProcess) { - playerctlProcess->terminate(); - if (!playerctlProcess->waitForFinished()) { - playerctlProcess->kill(); - playerctlProcess->waitForFinished(1000); - } - } } QString MediaController::getAudioDeviceName() diff --git a/linux/mediacontroller.h b/linux/media/mediacontroller.h similarity index 91% rename from linux/mediacontroller.h rename to linux/media/mediacontroller.h index 9fbf9ee..73083e2 100644 --- a/linux/mediacontroller.h +++ b/linux/media/mediacontroller.h @@ -6,6 +6,7 @@ class QProcess; class EarDetection; +class PlayerStatusWatcher; class MediaController : public QObject { @@ -29,7 +30,6 @@ public: explicit MediaController(QObject *parent = nullptr); ~MediaController(); - void initializeMprisInterface(); void handleEarDetection(EarDetection*); void followMediaChanges(); bool isActiveOutputDeviceAirPods(); @@ -50,13 +50,12 @@ private: MediaState mediaStateFromPlayerctlOutput(const QString &output); QString getAudioDeviceName(); - QDBusInterface *mprisInterface = nullptr; - QProcess *playerctlProcess = nullptr; bool wasPausedByApp = false; int initialVolume = -1; QString connectedDeviceMacAddress; EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved; QString m_deviceOutputName; + PlayerStatusWatcher *playerStatusWatcher = nullptr; }; #endif // MEDIACONTROLLER_H \ No newline at end of file diff --git a/linux/media/playerstatuswatcher.cpp b/linux/media/playerstatuswatcher.cpp new file mode 100644 index 0000000..b8e8656 --- /dev/null +++ b/linux/media/playerstatuswatcher.cpp @@ -0,0 +1,47 @@ +#include "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)) +{ + 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 &) +{ + 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 + } +} \ No newline at end of file diff --git a/linux/media/playerstatuswatcher.h b/linux/media/playerstatuswatcher.h new file mode 100644 index 0000000..38a0d6b --- /dev/null +++ b/linux/media/playerstatuswatcher.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +class PlayerStatusWatcher : public QObject { + Q_OBJECT +public: + explicit PlayerStatusWatcher(const QString &playerService, QObject *parent = nullptr); + +signals: + void playbackStatusChanged(const QString &status); + +private slots: + void onPropertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &); + void onServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); + +private: + void updateStatus(); + QString m_playerService; + QDBusInterface *m_iface; + QDBusServiceWatcher *m_serviceWatcher; +}; \ No newline at end of file