Skip to content

Commit 0846c3e

Browse files
committed
[Linux] Allow setting ear detection behaviour
1 parent c2db0af commit 0846c3e

File tree

3 files changed

+91
-39
lines changed

3 files changed

+91
-39
lines changed

linux/main.cpp

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ class AirPodsTrayApp : public QObject {
3434
AirPodsTrayApp(bool debugMode)
3535
: debugMode(debugMode)
3636
, m_battery(new Battery(this))
37-
, monitor(new BluetoothMonitor(this)) {
37+
, monitor(new BluetoothMonitor(this))
38+
, m_settings(new QSettings("AirPodsTrayApp", "AirPodsTrayApp")){
3839
if (debugMode) {
3940
QLoggingCategory::setFilterRules("airpodsApp.debug=true");
4041
} else {
@@ -85,6 +86,7 @@ class AirPodsTrayApp : public QObject {
8586

8687
~AirPodsTrayApp() {
8788
saveCrossDeviceEnabled();
89+
saveEarDetectionSettings();
8890

8991
delete trayIcon;
9092
delete trayMenu;
@@ -265,18 +267,11 @@ public slots:
265267
}
266268
}
267269

268-
bool loadCrossDeviceEnabled()
269-
{
270-
QSettings settings;
271-
return settings.value("crossdevice/enabled", false).toBool();
272-
}
270+
bool loadCrossDeviceEnabled() { return m_settings->value("crossdevice/enabled", false).toBool(); }
271+
void saveCrossDeviceEnabled() { m_settings->setValue("crossdevice/enabled", CrossDevice.isEnabled); }
273272

274-
void saveCrossDeviceEnabled()
275-
{
276-
QSettings settings;
277-
settings.setValue("crossdevice/enabled", CrossDevice.isEnabled);
278-
settings.sync();
279-
}
273+
int loadEarDetectionSettings() { return m_settings->value("earDetection/setting", MediaController::EarDetectionBehavior::PauseWhenOneRemoved).toInt(); }
274+
void saveEarDetectionSettings() { m_settings->setValue("earDetection/setting", mediaController->getEarDetectionBehavior()); }
280275

281276
private slots:
282277
void onTrayIconActivated()
@@ -836,7 +831,7 @@ private slots:
836831
MediaController* mediaController;
837832
TrayIconManager *trayManager;
838833
BluetoothMonitor *monitor;
839-
QSettings *settings;
834+
QSettings *m_settings;
840835

841836
QString m_batteryStatus;
842837
QString m_earDetectionStatus;

linux/mediacontroller.cpp

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,50 +38,89 @@ void MediaController::initializeMprisInterface() {
3838
}
3939
}
4040

41-
void MediaController::handleEarDetection(const QString &status) {
41+
void MediaController::handleEarDetection(const QString &status)
42+
{
43+
if (earDetectionBehavior == Disabled)
44+
{
45+
LOG_DEBUG("Ear detection is disabled, ignoring status");
46+
return;
47+
}
48+
4249
bool primaryInEar = false;
4350
bool secondaryInEar = false;
4451

4552
QStringList parts = status.split(", ");
46-
if (parts.size() == 2) {
53+
if (parts.size() == 2)
54+
{
4755
primaryInEar = parts[0].contains("In Ear");
4856
secondaryInEar = parts[1].contains("In Ear");
4957
}
5058

5159
LOG_DEBUG("Ear detection status: primaryInEar="
5260
<< primaryInEar << ", secondaryInEar=" << secondaryInEar
5361
<< ", isAirPodsActive=" << isActiveOutputDeviceAirPods());
54-
if (primaryInEar || secondaryInEar) {
62+
63+
// First handle playback pausing based on selected behavior
64+
bool shouldPause = false;
65+
bool shouldResume = false;
66+
67+
if (earDetectionBehavior == PauseWhenOneRemoved)
68+
{
69+
shouldPause = !primaryInEar || !secondaryInEar;
70+
shouldResume = primaryInEar && secondaryInEar;
71+
}
72+
else if (earDetectionBehavior == PauseWhenBothRemoved)
73+
{
74+
shouldPause = !primaryInEar && !secondaryInEar;
75+
shouldResume = primaryInEar || secondaryInEar;
76+
}
77+
78+
if (shouldPause && isActiveOutputDeviceAirPods())
79+
{
80+
QProcess process;
81+
process.start("playerctl", QStringList() << "status");
82+
process.waitForFinished();
83+
QString playbackStatus = process.readAllStandardOutput().trimmed();
84+
LOG_DEBUG("Playback status: " << playbackStatus);
85+
if (playbackStatus == "Playing")
86+
{
87+
pause();
88+
}
89+
}
90+
91+
// Then handle device profile switching
92+
if (primaryInEar || secondaryInEar)
93+
{
5594
LOG_INFO("At least one AirPod is in ear");
5695
activateA2dpProfile();
57-
} else {
58-
LOG_INFO("Both AirPods are out of ear");
59-
removeAudioOutputDevice();
60-
}
6196

62-
if (primaryInEar && secondaryInEar) {
63-
if (wasPausedByApp && isActiveOutputDeviceAirPods()) {
97+
// Resume if conditions are met and we previously paused
98+
if (shouldResume && wasPausedByApp && isActiveOutputDeviceAirPods())
99+
{
64100
int result = QProcess::execute("playerctl", QStringList() << "play");
65101
LOG_DEBUG("Executed 'playerctl play' with result: " << result);
66-
if (result == 0) {
102+
if (result == 0)
103+
{
67104
LOG_INFO("Resumed playback via Playerctl");
68105
wasPausedByApp = false;
69-
} else {
70-
LOG_ERROR("Failed to resume playback via Playerctl");
71106
}
72-
}
73-
} else {
74-
if (isActiveOutputDeviceAirPods()) {
75-
QProcess process;
76-
process.start("playerctl", QStringList() << "status");
77-
process.waitForFinished();
78-
QString playbackStatus = process.readAllStandardOutput().trimmed();
79-
LOG_DEBUG("Playback status: " << playbackStatus);
80-
if (playbackStatus == "Playing") {
81-
pause();
107+
else
108+
{
109+
LOG_ERROR("Failed to resume playback via Playerctl");
82110
}
83111
}
84112
}
113+
else
114+
{
115+
LOG_INFO("Both AirPods are out of ear");
116+
removeAudioOutputDevice();
117+
}
118+
}
119+
120+
void MediaController::setEarDetectionBehavior(EarDetectionBehavior behavior)
121+
{
122+
earDetectionBehavior = behavior;
123+
LOG_INFO("Set ear detection behavior to: " << behavior);
85124
}
86125

87126
void MediaController::followMediaChanges() {

linux/mediacontroller.h

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,28 @@
66

77
class QProcess;
88

9-
class MediaController : public QObject {
9+
class MediaController : public QObject
10+
{
1011
Q_OBJECT
1112
public:
12-
enum MediaState { Playing, Paused, Stopped };
13+
enum MediaState
14+
{
15+
Playing,
16+
Paused,
17+
Stopped
18+
};
19+
Q_ENUM(MediaState)
20+
enum EarDetectionBehavior
21+
{
22+
PauseWhenOneRemoved,
23+
PauseWhenBothRemoved,
24+
Disabled
25+
};
26+
Q_ENUM(EarDetectionBehavior)
1327

1428
explicit MediaController(QObject *parent = nullptr);
1529
~MediaController();
16-
30+
1731
void initializeMprisInterface();
1832
void handleEarDetection(const QString &status);
1933
void followMediaChanges();
@@ -23,6 +37,9 @@ class MediaController : public QObject {
2337
void removeAudioOutputDevice();
2438
void setConnectedDeviceMacAddress(const QString &macAddress);
2539

40+
void setEarDetectionBehavior(EarDetectionBehavior behavior);
41+
inline EarDetectionBehavior getEarDetectionBehavior() const { return earDetectionBehavior; }
42+
2643
void pause();
2744

2845
Q_SIGNALS:
@@ -36,6 +53,7 @@ class MediaController : public QObject {
3653
bool wasPausedByApp = false;
3754
int initialVolume = -1;
3855
QString connectedDeviceMacAddress;
56+
EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved;
3957
};
4058

41-
#endif // MEDIACONTROLLER_H
59+
#endif // MEDIACONTROLLER_H

0 commit comments

Comments
 (0)