[Linux] Allow setting ear detection behaviour

This commit is contained in:
Tim Gromeyer
2025-04-18 18:03:43 +02:00
committed by Tim Gromeyer
parent c2db0afdf1
commit 0846c3eb48
3 changed files with 92 additions and 40 deletions

View File

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

View File

@@ -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() {

View File

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