mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-01-28 22:01:50 +00:00
[Linux] New ear detection implementation (#145)
* New ear detection implementation * [Linux] Improved case battery detection when not connected
This commit is contained in:
@@ -31,6 +31,7 @@ qt_add_executable(applinux
|
|||||||
thirdparty/QR-Code-generator/qrcodegen.cpp
|
thirdparty/QR-Code-generator/qrcodegen.cpp
|
||||||
thirdparty/QR-Code-generator/qrcodegen.hpp
|
thirdparty/QR-Code-generator/qrcodegen.hpp
|
||||||
QRCodeImageProvider.hpp
|
QRCodeImageProvider.hpp
|
||||||
|
eardetection.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(applinux
|
qt_add_qml_module(applinux
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ ApplicationWindow {
|
|||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
PodColumn {
|
PodColumn {
|
||||||
isVisible: airPodsTrayApp.deviceInfo.battery.leftPodAvailable
|
visible: airPodsTrayApp.deviceInfo.battery.leftPodAvailable
|
||||||
inEar: airPodsTrayApp.deviceInfo.leftPodInEar
|
inEar: airPodsTrayApp.deviceInfo.leftPodInEar
|
||||||
iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.podIcon
|
iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.podIcon
|
||||||
batteryLevel: airPodsTrayApp.deviceInfo.battery.leftPodLevel
|
batteryLevel: airPodsTrayApp.deviceInfo.battery.leftPodLevel
|
||||||
@@ -103,7 +103,7 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PodColumn {
|
PodColumn {
|
||||||
isVisible: airPodsTrayApp.deviceInfo.battery.rightPodAvailable
|
visible: airPodsTrayApp.deviceInfo.battery.rightPodAvailable
|
||||||
inEar: airPodsTrayApp.deviceInfo.rightPodInEar
|
inEar: airPodsTrayApp.deviceInfo.rightPodInEar
|
||||||
iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.podIcon
|
iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.podIcon
|
||||||
batteryLevel: airPodsTrayApp.deviceInfo.battery.rightPodLevel
|
batteryLevel: airPodsTrayApp.deviceInfo.battery.rightPodLevel
|
||||||
@@ -112,7 +112,7 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PodColumn {
|
PodColumn {
|
||||||
isVisible: airPodsTrayApp.deviceInfo.battery.caseAvailable
|
visible: airPodsTrayApp.deviceInfo.battery.caseAvailable
|
||||||
inEar: true
|
inEar: true
|
||||||
iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.caseIcon
|
iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.caseIcon
|
||||||
batteryLevel: airPodsTrayApp.deviceInfo.battery.caseLevel
|
batteryLevel: airPodsTrayApp.deviceInfo.battery.caseLevel
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
property bool isVisible: true
|
id: root
|
||||||
property bool inEar: true
|
property bool inEar: true
|
||||||
property string iconSource
|
property string iconSource
|
||||||
property int batteryLevel: 0
|
property int batteryLevel: 0
|
||||||
property bool isCharging: false
|
property bool isCharging: false
|
||||||
property string indicator: ""
|
property string indicator: ""
|
||||||
|
property real targetOpacity: inEar ? 1 : 0.5
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: opacityTimer
|
||||||
|
interval: 50
|
||||||
|
onTriggered: root.opacity = root.targetOpacity
|
||||||
|
}
|
||||||
|
|
||||||
|
onInEarChanged: {
|
||||||
|
opacityTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
spacing: 5
|
spacing: 5
|
||||||
opacity: inEar ? 1 : 0.5
|
|
||||||
visible: isVisible
|
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
source: parent.iconSource
|
source: parent.iconSource
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <climits>
|
#include <climits>
|
||||||
|
|
||||||
#include "airpods_packets.h"
|
#include "airpods_packets.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
class Battery : public QObject
|
class Battery : public QObject
|
||||||
{
|
{
|
||||||
@@ -128,10 +129,14 @@ public:
|
|||||||
// Emit signal to notify about battery status change
|
// Emit signal to notify about battery status change
|
||||||
emit batteryStatusChanged();
|
emit batteryStatusChanged();
|
||||||
|
|
||||||
|
// Log which is left and right pod
|
||||||
|
LOG_INFO("Primary Pod:" << primaryPod);
|
||||||
|
LOG_INFO("Secondary Pod:" << secondaryPod);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parseEncryptedPacket(const QByteArray &packet, bool isLeftPodPrimary)
|
bool parseEncryptedPacket(const QByteArray &packet, bool isLeftPodPrimary, bool podInCase)
|
||||||
{
|
{
|
||||||
// Validate packet size (expect 16 bytes based on provided payloads)
|
// Validate packet size (expect 16 bytes based on provided payloads)
|
||||||
if (packet.size() != 16)
|
if (packet.size() != 16)
|
||||||
@@ -171,7 +176,9 @@ public:
|
|||||||
// Update states
|
// Update states
|
||||||
states[Component::Left] = {static_cast<quint8>(rawLeftBattery), isLeftCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
states[Component::Left] = {static_cast<quint8>(rawLeftBattery), isLeftCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
||||||
states[Component::Right] = {static_cast<quint8>(rawRightBattery), isRightCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
states[Component::Right] = {static_cast<quint8>(rawRightBattery), isRightCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
||||||
|
if (podInCase) {
|
||||||
states[Component::Case] = {static_cast<quint8>(rawCaseBattery), isCaseCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
states[Component::Case] = {static_cast<quint8>(rawCaseBattery), isCaseCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
||||||
|
}
|
||||||
primaryPod = isLeftPodPrimary ? Component::Left : Component::Right;
|
primaryPod = isLeftPodPrimary ? Component::Left : Component::Right;
|
||||||
secondaryPod = isLeftPodPrimary ? Component::Right : Component::Left;
|
secondaryPod = isLeftPodPrimary ? Component::Right : Component::Left;
|
||||||
emit batteryStatusChanged();
|
emit batteryStatusChanged();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include "battery.hpp"
|
#include "battery.hpp"
|
||||||
#include "enums.h"
|
#include "enums.h"
|
||||||
|
#include "eardetection.hpp"
|
||||||
|
|
||||||
using namespace AirpodsTrayApp::Enums;
|
using namespace AirpodsTrayApp::Enums;
|
||||||
|
|
||||||
@@ -12,14 +13,11 @@ class DeviceInfo : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QString batteryStatus READ batteryStatus WRITE setBatteryStatus NOTIFY batteryStatusChanged)
|
Q_PROPERTY(QString batteryStatus READ batteryStatus WRITE setBatteryStatus NOTIFY batteryStatusChanged)
|
||||||
Q_PROPERTY(QString earDetectionStatus READ earDetectionStatus WRITE setEarDetectionStatus NOTIFY earDetectionStatusChanged)
|
|
||||||
Q_PROPERTY(int noiseControlMode READ noiseControlModeInt WRITE setNoiseControlModeInt NOTIFY noiseControlModeChangedInt)
|
Q_PROPERTY(int noiseControlMode READ noiseControlModeInt WRITE setNoiseControlModeInt NOTIFY noiseControlModeChangedInt)
|
||||||
Q_PROPERTY(bool conversationalAwareness READ conversationalAwareness WRITE setConversationalAwareness NOTIFY conversationalAwarenessChanged)
|
Q_PROPERTY(bool conversationalAwareness READ conversationalAwareness WRITE setConversationalAwareness NOTIFY conversationalAwarenessChanged)
|
||||||
Q_PROPERTY(int adaptiveNoiseLevel READ adaptiveNoiseLevel WRITE setAdaptiveNoiseLevel NOTIFY adaptiveNoiseLevelChanged)
|
Q_PROPERTY(int adaptiveNoiseLevel READ adaptiveNoiseLevel WRITE setAdaptiveNoiseLevel NOTIFY adaptiveNoiseLevelChanged)
|
||||||
Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName NOTIFY deviceNameChanged)
|
Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName NOTIFY deviceNameChanged)
|
||||||
Q_PROPERTY(Battery *battery READ getBattery CONSTANT)
|
Q_PROPERTY(Battery *battery READ getBattery CONSTANT)
|
||||||
Q_PROPERTY(bool primaryInEar READ isPrimaryInEar WRITE setPrimaryInEar NOTIFY primaryChanged)
|
|
||||||
Q_PROPERTY(bool secondaryInEar READ isSecondaryInEar WRITE setSecondaryInEar NOTIFY primaryChanged)
|
|
||||||
Q_PROPERTY(bool oneBudANCMode READ oneBudANCMode WRITE setOneBudANCMode NOTIFY oneBudANCModeChanged)
|
Q_PROPERTY(bool oneBudANCMode READ oneBudANCMode WRITE setOneBudANCMode NOTIFY oneBudANCModeChanged)
|
||||||
Q_PROPERTY(AirPodsModel model READ model WRITE setModel NOTIFY modelChanged)
|
Q_PROPERTY(AirPodsModel model READ model WRITE setModel NOTIFY modelChanged)
|
||||||
Q_PROPERTY(bool adaptiveModeActive READ adaptiveModeActive NOTIFY noiseControlModeChangedInt)
|
Q_PROPERTY(bool adaptiveModeActive READ adaptiveModeActive NOTIFY noiseControlModeChangedInt)
|
||||||
@@ -32,7 +30,9 @@ class DeviceInfo : public QObject
|
|||||||
Q_PROPERTY(QString magicAccEncKey READ magicAccEncKeyHex CONSTANT)
|
Q_PROPERTY(QString magicAccEncKey READ magicAccEncKeyHex CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeviceInfo(QObject *parent = nullptr) : QObject(parent), m_battery(new Battery(this)) {}
|
explicit DeviceInfo(QObject *parent = nullptr) : QObject(parent), m_battery(new Battery(this)), m_earDetection(new EarDetection(this)) {
|
||||||
|
connect(getEarDetection(), &EarDetection::statusChanged, this, &DeviceInfo::primaryChanged);
|
||||||
|
}
|
||||||
|
|
||||||
QString batteryStatus() const { return m_batteryStatus; }
|
QString batteryStatus() const { return m_batteryStatus; }
|
||||||
void setBatteryStatus(const QString &status)
|
void setBatteryStatus(const QString &status)
|
||||||
@@ -44,16 +44,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString earDetectionStatus() const { return m_earDetectionStatus; }
|
|
||||||
void setEarDetectionStatus(const QString &status)
|
|
||||||
{
|
|
||||||
if (m_earDetectionStatus != status)
|
|
||||||
{
|
|
||||||
m_earDetectionStatus = status;
|
|
||||||
emit earDetectionStatusChanged(status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NoiseControlMode noiseControlMode() const { return m_noiseControlMode; }
|
NoiseControlMode noiseControlMode() const { return m_noiseControlMode; }
|
||||||
void setNoiseControlMode(NoiseControlMode mode)
|
void setNoiseControlMode(NoiseControlMode mode)
|
||||||
{
|
{
|
||||||
@@ -99,26 +89,6 @@ public:
|
|||||||
|
|
||||||
Battery *getBattery() const { return m_battery; }
|
Battery *getBattery() const { return m_battery; }
|
||||||
|
|
||||||
bool isPrimaryInEar() const { return m_primaryInEar; }
|
|
||||||
void setPrimaryInEar(bool inEar)
|
|
||||||
{
|
|
||||||
if (m_primaryInEar != inEar)
|
|
||||||
{
|
|
||||||
m_primaryInEar = inEar;
|
|
||||||
emit primaryChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSecondaryInEar() const { return m_secoundaryInEar; }
|
|
||||||
void setSecondaryInEar(bool inEar)
|
|
||||||
{
|
|
||||||
if (m_secoundaryInEar != inEar)
|
|
||||||
{
|
|
||||||
m_secoundaryInEar = inEar;
|
|
||||||
emit primaryChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool oneBudANCMode() const { return m_oneBudANCMode; }
|
bool oneBudANCMode() const { return m_oneBudANCMode; }
|
||||||
void setOneBudANCMode(bool enabled)
|
void setOneBudANCMode(bool enabled)
|
||||||
{
|
{
|
||||||
@@ -167,18 +137,18 @@ public:
|
|||||||
QString caseIcon() const { return getModelIcon(model()).second; }
|
QString caseIcon() const { return getModelIcon(model()).second; }
|
||||||
bool isLeftPodInEar() const
|
bool isLeftPodInEar() const
|
||||||
{
|
{
|
||||||
if (getBattery()->getPrimaryPod() == Battery::Component::Left) return isPrimaryInEar();
|
if (getBattery()->getPrimaryPod() == Battery::Component::Left) return getEarDetection()->isPrimaryInEar();
|
||||||
else return isSecondaryInEar();
|
else return getEarDetection()->isSecondaryInEar();
|
||||||
}
|
}
|
||||||
bool isRightPodInEar() const
|
bool isRightPodInEar() const
|
||||||
{
|
{
|
||||||
if (getBattery()->getPrimaryPod() == Battery::Component::Right) return isPrimaryInEar();
|
if (getBattery()->getPrimaryPod() == Battery::Component::Right) return getEarDetection()->isPrimaryInEar();
|
||||||
else return isSecondaryInEar();
|
else return getEarDetection()->isSecondaryInEar();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool adaptiveModeActive() const { return noiseControlMode() == NoiseControlMode::Adaptive; }
|
bool adaptiveModeActive() const { return noiseControlMode() == NoiseControlMode::Adaptive; }
|
||||||
bool oneOrMorePodsInCase() const { return earDetectionStatus().contains("In case"); }
|
|
||||||
bool oneOrMorePodsInEar() const { return isPrimaryInEar() || isSecondaryInEar(); }
|
EarDetection *getEarDetection() const { return m_earDetection; }
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
@@ -186,11 +156,9 @@ public:
|
|||||||
setModel(AirPodsModel::Unknown);
|
setModel(AirPodsModel::Unknown);
|
||||||
m_battery->reset();
|
m_battery->reset();
|
||||||
setBatteryStatus("");
|
setBatteryStatus("");
|
||||||
setEarDetectionStatus("");
|
|
||||||
setPrimaryInEar(false);
|
|
||||||
setSecondaryInEar(false);
|
|
||||||
setNoiseControlMode(NoiseControlMode::Off);
|
setNoiseControlMode(NoiseControlMode::Off);
|
||||||
setBluetoothAddress("");
|
setBluetoothAddress("");
|
||||||
|
getEarDetection()->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveToSettings(QSettings &settings)
|
void saveToSettings(QSettings &settings)
|
||||||
@@ -210,9 +178,16 @@ public:
|
|||||||
setMagicAccEncKey(settings.value("DeviceInfo/magicAccEncKey", QByteArray()).toByteArray());
|
setMagicAccEncKey(settings.value("DeviceInfo/magicAccEncKey", QByteArray()).toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateBatteryStatus()
|
||||||
|
{
|
||||||
|
int leftLevel = getBattery()->getState(Battery::Component::Left).level;
|
||||||
|
int rightLevel = getBattery()->getState(Battery::Component::Right).level;
|
||||||
|
int caseLevel = getBattery()->getState(Battery::Component::Case).level;
|
||||||
|
setBatteryStatus(QString("Left: %1%, Right: %2%, Case: %3%").arg(leftLevel).arg(rightLevel).arg(caseLevel));
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void batteryStatusChanged(const QString &status);
|
void batteryStatusChanged(const QString &status);
|
||||||
void earDetectionStatusChanged(const QString &status);
|
|
||||||
void noiseControlModeChanged(NoiseControlMode mode);
|
void noiseControlModeChanged(NoiseControlMode mode);
|
||||||
void noiseControlModeChangedInt(int mode);
|
void noiseControlModeChangedInt(int mode);
|
||||||
void conversationalAwarenessChanged(bool enabled);
|
void conversationalAwarenessChanged(bool enabled);
|
||||||
@@ -225,14 +200,11 @@ signals:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_batteryStatus;
|
QString m_batteryStatus;
|
||||||
QString m_earDetectionStatus;
|
|
||||||
NoiseControlMode m_noiseControlMode = NoiseControlMode::Transparency;
|
NoiseControlMode m_noiseControlMode = NoiseControlMode::Transparency;
|
||||||
bool m_conversationalAwareness = false;
|
bool m_conversationalAwareness = false;
|
||||||
int m_adaptiveNoiseLevel = 50;
|
int m_adaptiveNoiseLevel = 50;
|
||||||
QString m_deviceName;
|
QString m_deviceName;
|
||||||
Battery *m_battery;
|
Battery *m_battery;
|
||||||
bool m_primaryInEar = false;
|
|
||||||
bool m_secoundaryInEar = false;
|
|
||||||
QByteArray m_magicAccIRK;
|
QByteArray m_magicAccIRK;
|
||||||
QByteArray m_magicAccEncKey;
|
QByteArray m_magicAccEncKey;
|
||||||
bool m_oneBudANCMode = false;
|
bool m_oneBudANCMode = false;
|
||||||
@@ -240,4 +212,5 @@ private:
|
|||||||
QString m_modelNumber;
|
QString m_modelNumber;
|
||||||
QString m_manufacturer;
|
QString m_manufacturer;
|
||||||
QString m_bluetoothAddress;
|
QString m_bluetoothAddress;
|
||||||
|
EarDetection *m_earDetection;
|
||||||
};
|
};
|
||||||
94
linux/eardetection.hpp
Normal file
94
linux/eardetection.hpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QPair>
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
class EarDetection : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class EarDetectionStatus
|
||||||
|
{
|
||||||
|
InEar,
|
||||||
|
NotInEar,
|
||||||
|
InCase,
|
||||||
|
Disconnected,
|
||||||
|
};
|
||||||
|
Q_ENUM(EarDetectionStatus)
|
||||||
|
|
||||||
|
explicit EarDetection(QObject *parent = nullptr) : QObject(parent)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
primaryStatus = EarDetectionStatus::Disconnected;
|
||||||
|
secondaryStatus = EarDetectionStatus::Disconnected;
|
||||||
|
emit statusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parseData(const QByteArray &data)
|
||||||
|
{
|
||||||
|
if (data.size() < 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [newprimaryStatus, newsecondaryStatus] = parseStatusBytes(data);
|
||||||
|
|
||||||
|
primaryStatus = newprimaryStatus;
|
||||||
|
secondaryStatus = newsecondaryStatus;
|
||||||
|
LOG_DEBUG("Parsed Ear Detection Status: Primary - " << primaryStatus
|
||||||
|
<< ", Secondary - " << secondaryStatus);
|
||||||
|
emit statusChanged();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void overrideEarDetectionStatus(bool primaryInEar, bool secondaryInEar)
|
||||||
|
{
|
||||||
|
primaryStatus = primaryInEar ? EarDetectionStatus::InEar : EarDetectionStatus::NotInEar;
|
||||||
|
secondaryStatus = secondaryInEar ? EarDetectionStatus::InEar : EarDetectionStatus::NotInEar;
|
||||||
|
emit statusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPrimaryInEar() const { return primaryStatus == EarDetectionStatus::InEar; }
|
||||||
|
bool isSecondaryInEar() const { return secondaryStatus == EarDetectionStatus::InEar; }
|
||||||
|
bool oneOrMorePodsInCase() const { return primaryStatus == EarDetectionStatus::InCase || secondaryStatus == EarDetectionStatus::InCase; }
|
||||||
|
bool oneOrMorePodsInEar() const { return isPrimaryInEar() || isSecondaryInEar(); }
|
||||||
|
|
||||||
|
EarDetectionStatus getprimaryStatus() const { return primaryStatus; }
|
||||||
|
EarDetectionStatus getsecondaryStatus() const { return secondaryStatus; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void statusChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPair<EarDetectionStatus, EarDetectionStatus> parseStatusBytes(const QByteArray &data) const
|
||||||
|
{
|
||||||
|
quint8 primaryByte = static_cast<quint8>(data[6]);
|
||||||
|
quint8 secondaryByte = static_cast<quint8>(data[7]);
|
||||||
|
|
||||||
|
auto primaryStatus = parseStatusByte(primaryByte);
|
||||||
|
auto secondaryStatus = parseStatusByte(secondaryByte);
|
||||||
|
|
||||||
|
return qMakePair(primaryStatus, secondaryStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
EarDetectionStatus parseStatusByte(quint8 byte) const
|
||||||
|
{
|
||||||
|
if (byte == 0x00)
|
||||||
|
return EarDetectionStatus::InEar;
|
||||||
|
if (byte == 0x01)
|
||||||
|
return EarDetectionStatus::NotInEar;
|
||||||
|
if (byte == 0x02)
|
||||||
|
return EarDetectionStatus::InCase;
|
||||||
|
return EarDetectionStatus::Disconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
EarDetectionStatus primaryStatus = EarDetectionStatus::Disconnected;
|
||||||
|
EarDetectionStatus secondaryStatus = EarDetectionStatus::Disconnected;
|
||||||
|
};
|
||||||
@@ -65,7 +65,6 @@ public:
|
|||||||
|
|
||||||
// Initialize MediaController and connect signals
|
// Initialize MediaController and connect signals
|
||||||
mediaController = new MediaController(this);
|
mediaController = new MediaController(this);
|
||||||
connect(m_deviceInfo, &DeviceInfo::earDetectionStatusChanged, mediaController, &MediaController::handleEarDetection);
|
|
||||||
connect(mediaController, &MediaController::mediaStateChanged, this, &AirPodsTrayApp::handleMediaStateChange);
|
connect(mediaController, &MediaController::mediaStateChanged, this, &AirPodsTrayApp::handleMediaStateChange);
|
||||||
mediaController->initializeMprisInterface();
|
mediaController->initializeMprisInterface();
|
||||||
mediaController->followMediaChanges();
|
mediaController->followMediaChanges();
|
||||||
@@ -590,26 +589,14 @@ private slots:
|
|||||||
// Ear Detection
|
// Ear Detection
|
||||||
else if (data.size() == 8 && data.startsWith(AirPodsPackets::Parse::EAR_DETECTION))
|
else if (data.size() == 8 && data.startsWith(AirPodsPackets::Parse::EAR_DETECTION))
|
||||||
{
|
{
|
||||||
char primary = data[6];
|
m_deviceInfo->getEarDetection()->parseData(data);
|
||||||
char secondary = data[7];
|
mediaController->handleEarDetection(m_deviceInfo->getEarDetection());
|
||||||
m_deviceInfo->setPrimaryInEar(data[6] == 0x00);
|
|
||||||
m_deviceInfo->setSecondaryInEar(data[7] == 0x00);
|
|
||||||
m_deviceInfo->setEarDetectionStatus(QString("Primary: %1, Secondary: %2")
|
|
||||||
.arg(getEarStatus(primary), getEarStatus(secondary)));
|
|
||||||
LOG_INFO("Ear detection status: " << m_deviceInfo->earDetectionStatus());
|
|
||||||
}
|
}
|
||||||
// Battery Status
|
// Battery Status
|
||||||
else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS))
|
else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS))
|
||||||
{
|
{
|
||||||
m_deviceInfo->getBattery()->parsePacket(data);
|
m_deviceInfo->getBattery()->parsePacket(data);
|
||||||
|
m_deviceInfo->updateBatteryStatus();
|
||||||
int leftLevel = m_deviceInfo->getBattery()->getState(Battery::Component::Left).level;
|
|
||||||
int rightLevel = m_deviceInfo->getBattery()->getState(Battery::Component::Right).level;
|
|
||||||
int caseLevel = m_deviceInfo->getBattery()->getState(Battery::Component::Case).level;
|
|
||||||
m_deviceInfo->setBatteryStatus(QString("Left: %1%, Right: %2%, Case: %3%")
|
|
||||||
.arg(leftLevel)
|
|
||||||
.arg(rightLevel)
|
|
||||||
.arg(caseLevel));
|
|
||||||
LOG_INFO("Battery status: " << m_deviceInfo->batteryStatus());
|
LOG_INFO("Battery status: " << m_deviceInfo->batteryStatus());
|
||||||
}
|
}
|
||||||
// Conversational Awareness Data
|
// Conversational Awareness Data
|
||||||
@@ -623,7 +610,7 @@ private slots:
|
|||||||
parseMetadata(data);
|
parseMetadata(data);
|
||||||
initiateMagicPairing();
|
initiateMagicPairing();
|
||||||
mediaController->setConnectedDeviceMacAddress(m_deviceInfo->bluetoothAddress().replace(":", "_"));
|
mediaController->setConnectedDeviceMacAddress(m_deviceInfo->bluetoothAddress().replace(":", "_"));
|
||||||
if (m_deviceInfo->oneOrMorePodsInEar()) // AirPods get added as output device only after this
|
if (m_deviceInfo->getEarDetection()->oneOrMorePodsInEar()) // AirPods get added as output device only after this
|
||||||
{
|
{
|
||||||
mediaController->activateA2dpProfile();
|
mediaController->activateA2dpProfile();
|
||||||
}
|
}
|
||||||
@@ -762,9 +749,8 @@ private slots:
|
|||||||
if (BLEUtils::isValidIrkRpa(m_deviceInfo->magicAccIRK(), device.address)) {
|
if (BLEUtils::isValidIrkRpa(m_deviceInfo->magicAccIRK(), device.address)) {
|
||||||
m_deviceInfo->setModel(device.modelName);
|
m_deviceInfo->setModel(device.modelName);
|
||||||
auto decryptet = BLEUtils::decryptLastBytes(device.encryptedPayload, m_deviceInfo->magicAccEncKey());
|
auto decryptet = BLEUtils::decryptLastBytes(device.encryptedPayload, m_deviceInfo->magicAccEncKey());
|
||||||
m_deviceInfo->getBattery()->parseEncryptedPacket(decryptet, device.primaryLeft);
|
m_deviceInfo->getBattery()->parseEncryptedPacket(decryptet, device.primaryLeft, device.isThisPodInTheCase);
|
||||||
m_deviceInfo->setPrimaryInEar(device.isPrimaryInEar);
|
m_deviceInfo->getEarDetection()->overrideEarDetectionStatus(device.isPrimaryInEar, device.isSecondaryInEar);
|
||||||
m_deviceInfo->setSecondaryInEar(device.isSecondaryInEar);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "mediacontroller.h"
|
#include "mediacontroller.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "eardetection.hpp"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
@@ -38,7 +39,7 @@ void MediaController::initializeMprisInterface() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaController::handleEarDetection(const QString &status)
|
void MediaController::handleEarDetection(EarDetection *earDetection)
|
||||||
{
|
{
|
||||||
if (earDetectionBehavior == Disabled)
|
if (earDetectionBehavior == Disabled)
|
||||||
{
|
{
|
||||||
@@ -46,15 +47,8 @@ void MediaController::handleEarDetection(const QString &status)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool primaryInEar = false;
|
bool primaryInEar = earDetection->isPrimaryInEar();
|
||||||
bool secondaryInEar = false;
|
bool secondaryInEar = earDetection->isSecondaryInEar();
|
||||||
|
|
||||||
QStringList parts = status.split(", ");
|
|
||||||
if (parts.size() == 2)
|
|
||||||
{
|
|
||||||
primaryInEar = parts[0].contains("In Ear");
|
|
||||||
secondaryInEar = parts[1].contains("In Ear");
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DEBUG("Ear detection status: primaryInEar="
|
LOG_DEBUG("Ear detection status: primaryInEar="
|
||||||
<< primaryInEar << ", secondaryInEar=" << secondaryInEar
|
<< primaryInEar << ", secondaryInEar=" << secondaryInEar
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class QProcess;
|
class QProcess;
|
||||||
|
class EarDetection;
|
||||||
|
|
||||||
class MediaController : public QObject
|
class MediaController : public QObject
|
||||||
{
|
{
|
||||||
@@ -29,7 +30,7 @@ public:
|
|||||||
~MediaController();
|
~MediaController();
|
||||||
|
|
||||||
void initializeMprisInterface();
|
void initializeMprisInterface();
|
||||||
void handleEarDetection(const QString &status);
|
void handleEarDetection(EarDetection*);
|
||||||
void followMediaChanges();
|
void followMediaChanges();
|
||||||
bool isActiveOutputDeviceAirPods();
|
bool isActiveOutputDeviceAirPods();
|
||||||
void handleConversationalAwareness(const QByteArray &data);
|
void handleConversationalAwareness(const QByteArray &data);
|
||||||
|
|||||||
Reference in New Issue
Block a user