From 0846c3eb487a5eaa892fb0dc012e997ed3c9d15b Mon Sep 17 00:00:00 2001 From: Tim Gromeyer Date: Fri, 18 Apr 2025 18:03:43 +0200 Subject: [PATCH] [Linux] Allow setting ear detection behaviour --- linux/main.cpp | 21 ++++------ linux/mediacontroller.cpp | 85 ++++++++++++++++++++++++++++----------- linux/mediacontroller.h | 26 ++++++++++-- 3 files changed, 92 insertions(+), 40 deletions(-) diff --git a/linux/main.cpp b/linux/main.cpp index f6df035..aa0900f 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -34,7 +34,8 @@ public: AirPodsTrayApp(bool debugMode) : debugMode(debugMode) , m_battery(new Battery(this)) - , monitor(new BluetoothMonitor(this)) { + , monitor(new BluetoothMonitor(this)) + , m_settings(new QSettings("AirPodsTrayApp", "AirPodsTrayApp")){ if (debugMode) { QLoggingCategory::setFilterRules("airpodsApp.debug=true"); } else { @@ -85,6 +86,7 @@ public: ~AirPodsTrayApp() { saveCrossDeviceEnabled(); + saveEarDetectionSettings(); delete trayIcon; delete trayMenu; @@ -265,18 +267,11 @@ public slots: } } - bool loadCrossDeviceEnabled() - { - QSettings settings; - return settings.value("crossdevice/enabled", false).toBool(); - } + bool loadCrossDeviceEnabled() { return m_settings->value("crossdevice/enabled", false).toBool(); } + void saveCrossDeviceEnabled() { m_settings->setValue("crossdevice/enabled", CrossDevice.isEnabled); } - void saveCrossDeviceEnabled() - { - QSettings settings; - settings.setValue("crossdevice/enabled", CrossDevice.isEnabled); - settings.sync(); - } + int loadEarDetectionSettings() { return m_settings->value("earDetection/setting", MediaController::EarDetectionBehavior::PauseWhenOneRemoved).toInt(); } + void saveEarDetectionSettings() { m_settings->setValue("earDetection/setting", mediaController->getEarDetectionBehavior()); } private slots: void onTrayIconActivated() @@ -836,7 +831,7 @@ private: MediaController* mediaController; TrayIconManager *trayManager; BluetoothMonitor *monitor; - QSettings *settings; + QSettings *m_settings; QString m_batteryStatus; QString m_earDetectionStatus; diff --git a/linux/mediacontroller.cpp b/linux/mediacontroller.cpp index e52c119..2b1500a 100644 --- a/linux/mediacontroller.cpp +++ b/linux/mediacontroller.cpp @@ -38,12 +38,20 @@ void MediaController::initializeMprisInterface() { } } -void MediaController::handleEarDetection(const QString &status) { +void MediaController::handleEarDetection(const QString &status) +{ + if (earDetectionBehavior == Disabled) + { + LOG_DEBUG("Ear detection is disabled, ignoring status"); + return; + } + bool primaryInEar = false; bool secondaryInEar = false; QStringList parts = status.split(", "); - if (parts.size() == 2) { + if (parts.size() == 2) + { primaryInEar = parts[0].contains("In Ear"); secondaryInEar = parts[1].contains("In Ear"); } @@ -51,37 +59,68 @@ void MediaController::handleEarDetection(const QString &status) { LOG_DEBUG("Ear detection status: primaryInEar=" << primaryInEar << ", secondaryInEar=" << secondaryInEar << ", isAirPodsActive=" << isActiveOutputDeviceAirPods()); - if (primaryInEar || secondaryInEar) { - LOG_INFO("At least one AirPod is in ear"); - activateA2dpProfile(); - } else { - LOG_INFO("Both AirPods are out of ear"); - removeAudioOutputDevice(); + + // First handle playback pausing based on selected behavior + bool shouldPause = false; + bool shouldResume = false; + + if (earDetectionBehavior == PauseWhenOneRemoved) + { + shouldPause = !primaryInEar || !secondaryInEar; + shouldResume = primaryInEar && secondaryInEar; + } + else if (earDetectionBehavior == PauseWhenBothRemoved) + { + shouldPause = !primaryInEar && !secondaryInEar; + shouldResume = primaryInEar || secondaryInEar; } - if (primaryInEar && secondaryInEar) { - if (wasPausedByApp && isActiveOutputDeviceAirPods()) { + if (shouldPause && isActiveOutputDeviceAirPods()) + { + QProcess process; + process.start("playerctl", QStringList() << "status"); + process.waitForFinished(); + QString playbackStatus = process.readAllStandardOutput().trimmed(); + LOG_DEBUG("Playback status: " << playbackStatus); + if (playbackStatus == "Playing") + { + pause(); + } + } + + // Then handle device profile switching + if (primaryInEar || secondaryInEar) + { + LOG_INFO("At least one AirPod is in ear"); + activateA2dpProfile(); + + // Resume if conditions are met and we previously paused + if (shouldResume && wasPausedByApp && isActiveOutputDeviceAirPods()) + { int result = QProcess::execute("playerctl", QStringList() << "play"); LOG_DEBUG("Executed 'playerctl play' with result: " << result); - if (result == 0) { + if (result == 0) + { LOG_INFO("Resumed playback via Playerctl"); wasPausedByApp = false; - } else { + } + else + { LOG_ERROR("Failed to resume playback via Playerctl"); } } - } else { - if (isActiveOutputDeviceAirPods()) { - QProcess process; - process.start("playerctl", QStringList() << "status"); - process.waitForFinished(); - QString playbackStatus = process.readAllStandardOutput().trimmed(); - LOG_DEBUG("Playback status: " << playbackStatus); - if (playbackStatus == "Playing") { - pause(); - } - } } + else + { + LOG_INFO("Both AirPods are out of ear"); + removeAudioOutputDevice(); + } +} + +void MediaController::setEarDetectionBehavior(EarDetectionBehavior behavior) +{ + earDetectionBehavior = behavior; + LOG_INFO("Set ear detection behavior to: " << behavior); } void MediaController::followMediaChanges() { diff --git a/linux/mediacontroller.h b/linux/mediacontroller.h index 72448e9..f30db75 100644 --- a/linux/mediacontroller.h +++ b/linux/mediacontroller.h @@ -6,14 +6,28 @@ class QProcess; -class MediaController : public QObject { +class MediaController : public QObject +{ Q_OBJECT public: - enum MediaState { Playing, Paused, Stopped }; + enum MediaState + { + Playing, + Paused, + Stopped + }; + Q_ENUM(MediaState) + enum EarDetectionBehavior + { + PauseWhenOneRemoved, + PauseWhenBothRemoved, + Disabled + }; + Q_ENUM(EarDetectionBehavior) explicit MediaController(QObject *parent = nullptr); ~MediaController(); - + void initializeMprisInterface(); void handleEarDetection(const QString &status); void followMediaChanges(); @@ -23,6 +37,9 @@ public: void removeAudioOutputDevice(); void setConnectedDeviceMacAddress(const QString &macAddress); + void setEarDetectionBehavior(EarDetectionBehavior behavior); + inline EarDetectionBehavior getEarDetectionBehavior() const { return earDetectionBehavior; } + void pause(); Q_SIGNALS: @@ -36,6 +53,7 @@ private: bool wasPausedByApp = false; int initialVolume = -1; QString connectedDeviceMacAddress; + EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved; }; -#endif // MEDIACONTROLLER_H +#endif // MEDIACONTROLLER_H \ No newline at end of file