[Linux] Move all device related properties to new class (#135)

* Clean up code

* Move all device releated properties to new class
This commit is contained in:
Tim Gromeyer
2025-06-03 09:07:30 +02:00
committed by GitHub
parent b783b86b7a
commit 8a69dbe173
6 changed files with 314 additions and 223 deletions

View File

@@ -18,22 +18,12 @@ namespace ControlCommand
return packet;
}
// Parse activated/not activated
inline std::optional<bool> parseActive(const QByteArray &data)
inline std::optional<char> parseActive(const QByteArray &data)
{
if (!data.startsWith(ControlCommand::HEADER))
return std::nullopt;
quint8 statusByte = static_cast<quint8>(data.at(7));
switch (statusByte)
{
case 0x01: // Enabled
return true;
case 0x02: // Disabled
return false;
default:
return std::nullopt;
}
return static_cast<quint8>(data.at(7));
}
}
@@ -54,6 +44,19 @@ struct BasicControlCommand
// Basically returns the byte at the index 7
static std::optional<bool> parseState(const QByteArray &data)
{
switch (ControlCommand::parseActive(data).value_or(0x00))
{
case 0x01: // Enabled
return true;
case 0x02: // Disabled
return false;
default:
return std::nullopt;
}
}
static std::optional<char> getValue(const QByteArray &data)
{
return ControlCommand::parseActive(data);
}

View File

@@ -23,6 +23,7 @@ qt_add_executable(applinux
BluetoothMonitor.h
autostartmanager.hpp
BasicControlCommand.hpp
deviceinfo.hpp
)
qt_add_qml_module(applinux

View File

@@ -13,6 +13,7 @@ namespace AirPodsPackets
// Noise Control Mode Packets
namespace NoiseControl
{
using NoiseControlMode = AirpodsTrayApp::Enums::NoiseControlMode;
static const QByteArray HEADER = ControlCommand::HEADER + 0x0D;
static const QByteArray OFF = ControlCommand::createCommand(0x0D, 0x01);
static const QByteArray NOISE_CANCELLATION = ControlCommand::createCommand(0x0D, 0x02);
@@ -21,7 +22,6 @@ namespace AirPodsPackets
static const QByteArray getPacketForMode(AirpodsTrayApp::Enums::NoiseControlMode mode)
{
using NoiseControlMode = AirpodsTrayApp::Enums::NoiseControlMode;
switch (mode)
{
case NoiseControlMode::Off:
@@ -36,6 +36,17 @@ namespace AirPodsPackets
return QByteArray();
}
}
inline std::optional<NoiseControlMode> parseMode(const QByteArray &data)
{
char mode = ControlCommand::parseActive(data).value_or(CHAR_MAX);
if (mode < static_cast<quint8>(NoiseControlMode::MinValue) ||
mode > static_cast<quint8>(NoiseControlMode::MaxValue))
{
return std::nullopt;
}
return static_cast<NoiseControlMode>(mode - 1);
}
}
// One Bud ANC Mode

View File

@@ -1,3 +1,5 @@
#pragma once
#include <QByteArray>
#include <QMap>
#include <QString>

209
linux/deviceinfo.hpp Normal file
View File

@@ -0,0 +1,209 @@
#pragma once
#include <QObject>
#include <QByteArray>
#include "battery.hpp"
#include "enums.h"
using namespace AirpodsTrayApp::Enums;
class DeviceInfo : public QObject
{
Q_OBJECT
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(bool conversationalAwareness READ conversationalAwareness WRITE setConversationalAwareness NOTIFY conversationalAwarenessChanged)
Q_PROPERTY(int adaptiveNoiseLevel READ adaptiveNoiseLevel WRITE setAdaptiveNoiseLevel NOTIFY adaptiveNoiseLevelChanged)
Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName NOTIFY deviceNameChanged)
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(AirPodsModel model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(bool adaptiveModeActive READ adaptiveModeActive NOTIFY noiseControlModeChangedInt)
Q_PROPERTY(QString podIcon READ podIcon NOTIFY modelChanged)
Q_PROPERTY(QString caseIcon READ caseIcon NOTIFY modelChanged)
Q_PROPERTY(bool leftPodInEar READ isLeftPodInEar NOTIFY primaryChanged)
Q_PROPERTY(bool rightPodInEar READ isRightPodInEar NOTIFY primaryChanged)
public:
explicit DeviceInfo(QObject *parent = nullptr) : QObject(parent), m_battery(new Battery(this)) {}
QString batteryStatus() const { return m_batteryStatus; }
void setBatteryStatus(const QString &status)
{
if (m_batteryStatus != status)
{
m_batteryStatus = status;
emit batteryStatusChanged(status);
}
}
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; }
void setNoiseControlMode(NoiseControlMode mode)
{
if (m_noiseControlMode != mode)
{
m_noiseControlMode = mode;
emit noiseControlModeChanged(mode);
emit noiseControlModeChangedInt(static_cast<int>(mode));
}
}
int noiseControlModeInt() const { return static_cast<int>(noiseControlMode()); }
void setNoiseControlModeInt(int mode) { setNoiseControlMode(static_cast<NoiseControlMode>(mode)); }
bool conversationalAwareness() const { return m_conversationalAwareness; }
void setConversationalAwareness(bool enabled)
{
if (m_conversationalAwareness != enabled)
{
m_conversationalAwareness = enabled;
emit conversationalAwarenessChanged(enabled);
}
}
int adaptiveNoiseLevel() const { return m_adaptiveNoiseLevel; }
void setAdaptiveNoiseLevel(int level)
{
if (m_adaptiveNoiseLevel != level)
{
m_adaptiveNoiseLevel = level;
emit adaptiveNoiseLevelChanged(level);
}
}
QString deviceName() const { return m_deviceName; }
void setDeviceName(const QString &name)
{
if (m_deviceName != name)
{
m_deviceName = name;
emit deviceNameChanged(name);
}
}
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; }
void setOneBudANCMode(bool enabled)
{
if (m_oneBudANCMode != enabled)
{
m_oneBudANCMode = enabled;
emit oneBudANCModeChanged(enabled);
}
}
AirPodsModel model() const { return m_model; }
void setModel(AirPodsModel model)
{
if (m_model != model)
{
m_model = model;
emit modelChanged();
}
}
QByteArray magicAccIRK() const { return m_magicAccIRK; }
void setMagicAccIRK(const QByteArray &irk) { m_magicAccIRK = irk; }
QByteArray magicAccEncKey() const { return m_magicAccEncKey; }
void setMagicAccEncKey(const QByteArray &key) { m_magicAccEncKey = key; }
QString modelNumber() const { return m_modelNumber; }
void setModelNumber(const QString &modelNumber) { m_modelNumber = modelNumber; }
QString manufacturer() const { return m_manufacturer; }
void setManufacturer(const QString &manufacturer) { m_manufacturer = manufacturer; }
QString podIcon() const { return getModelIcon(model()).first; }
QString caseIcon() const { return getModelIcon(model()).second; }
bool isLeftPodInEar() const
{
if (getBattery()->getPrimaryPod() == Battery::Component::Left) return isPrimaryInEar();
else return isSecondaryInEar();
}
bool isRightPodInEar() const
{
if (getBattery()->getPrimaryPod() == Battery::Component::Right) return isPrimaryInEar();
else return isSecondaryInEar();
}
bool adaptiveModeActive() const { return noiseControlMode() == NoiseControlMode::Adaptive; }
bool oneOrMorePodsInCase() const { return earDetectionStatus().contains("In case"); }
bool oneOrMorePodsInEar() const { return isPrimaryInEar() || isSecondaryInEar(); }
void reset()
{
setDeviceName("");
setModel(AirPodsModel::Unknown);
m_battery->reset();
setBatteryStatus("");
setEarDetectionStatus("");
setPrimaryInEar(false);
setSecondaryInEar(false);
setNoiseControlMode(NoiseControlMode::Off);
}
signals:
void batteryStatusChanged(const QString &status);
void earDetectionStatusChanged(const QString &status);
void noiseControlModeChanged(NoiseControlMode mode);
void noiseControlModeChangedInt(int mode);
void conversationalAwarenessChanged(bool enabled);
void adaptiveNoiseLevelChanged(int level);
void deviceNameChanged(const QString &name);
void primaryChanged();
void oneBudANCModeChanged(bool enabled);
void modelChanged();
private:
QString m_batteryStatus;
QString m_earDetectionStatus;
NoiseControlMode m_noiseControlMode = NoiseControlMode::Off;
bool m_conversationalAwareness = false;
int m_adaptiveNoiseLevel = 50;
QString m_deviceName;
Battery *m_battery;
bool m_primaryInEar = false;
bool m_secoundaryInEar = false;
QByteArray m_magicAccIRK;
QByteArray m_magicAccEncKey;
bool m_oneBudANCMode = false;
AirPodsModel m_model = AirPodsModel::Unknown;
// Additional metadata fields
QString m_modelNumber;
QString m_manufacturer;
};

View File

@@ -10,6 +10,7 @@
#include "battery.hpp"
#include "BluetoothMonitor.h"
#include "autostartmanager.hpp"
#include "deviceinfo.hpp"
using namespace AirpodsTrayApp::Enums;
@@ -17,19 +18,6 @@ Q_LOGGING_CATEGORY(airpodsApp, "airpodsApp")
class AirPodsTrayApp : public QObject {
Q_OBJECT
Q_PROPERTY(QString batteryStatus READ batteryStatus NOTIFY batteryStatusChanged)
Q_PROPERTY(QString earDetectionStatus READ earDetectionStatus NOTIFY earDetectionStatusChanged)
Q_PROPERTY(int noiseControlMode READ noiseControlMode WRITE setNoiseControlMode NOTIFY noiseControlModeChanged)
Q_PROPERTY(bool conversationalAwareness READ conversationalAwareness WRITE setConversationalAwareness NOTIFY conversationalAwarenessChanged)
Q_PROPERTY(int adaptiveNoiseLevel READ adaptiveNoiseLevel WRITE setAdaptiveNoiseLevel NOTIFY adaptiveNoiseLevelChanged)
Q_PROPERTY(bool adaptiveModeActive READ adaptiveModeActive NOTIFY noiseControlModeChanged)
Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
Q_PROPERTY(Battery* battery READ getBattery NOTIFY batteryStatusChanged)
Q_PROPERTY(bool oneOrMorePodsInCase READ oneOrMorePodsInCase NOTIFY earDetectionStatusChanged)
Q_PROPERTY(QString podIcon READ podIcon NOTIFY modelChanged)
Q_PROPERTY(QString caseIcon READ caseIcon NOTIFY modelChanged)
Q_PROPERTY(bool leftPodInEar READ isLeftPodInEar NOTIFY primaryChanged)
Q_PROPERTY(bool rightPodInEar READ isRightPodInEar NOTIFY primaryChanged)
Q_PROPERTY(bool airpodsConnected READ areAirpodsConnected NOTIFY airPodsStatusChanged)
Q_PROPERTY(int earDetectionBehavior READ earDetectionBehavior WRITE setEarDetectionBehavior NOTIFY earDetectionBehaviorChanged)
Q_PROPERTY(bool crossDeviceEnabled READ crossDeviceEnabled WRITE setCrossDeviceEnabled NOTIFY crossDeviceEnabledChanged)
@@ -37,24 +25,13 @@ class AirPodsTrayApp : public QObject {
Q_PROPERTY(bool notificationsEnabled READ notificationsEnabled WRITE setNotificationsEnabled NOTIFY notificationsEnabledChanged)
Q_PROPERTY(int retryAttempts READ retryAttempts WRITE setRetryAttempts NOTIFY retryAttemptsChanged)
Q_PROPERTY(bool hideOnStart READ hideOnStart CONSTANT)
Q_PROPERTY(bool oneBudANCMode READ oneBudANCMode WRITE setOneBudANCMode NOTIFY oneBudANCModeChanged)
Q_PROPERTY(DeviceInfo *deviceInfo READ deviceInfo CONSTANT)
public:
AirPodsTrayApp(bool debugMode, bool hideOnStart, QQmlApplicationEngine *parent = nullptr)
: QObject(parent)
, debugMode(debugMode)
, m_battery(new Battery(this))
, monitor(new BluetoothMonitor(this))
, m_settings(new QSettings("AirPodsTrayApp", "AirPodsTrayApp"))
, m_autoStartManager(new AutoStartManager(this))
, m_hideOnStart(hideOnStart)
, parent(parent)
{
if (debugMode) {
QLoggingCategory::setFilterRules("airpodsApp.debug=true");
} else {
QLoggingCategory::setFilterRules("airpodsApp.debug=false");
}
: QObject(parent), debugMode(debugMode), m_settings(new QSettings("AirPodsTrayApp", "AirPodsTrayApp")), m_autoStartManager(new AutoStartManager(this)), m_hideOnStart(hideOnStart), parent(parent), m_deviceInfo(new DeviceInfo(this))
{
QLoggingCategory::setFilterRules(QString("airpodsApp.debug=%1").arg(debugMode ? "true" : "false"));
LOG_INFO("Initializing AirPodsTrayApp");
// Initialize tray icon and connect signals
@@ -63,25 +40,26 @@ public:
connect(trayManager, &TrayIconManager::trayClicked, this, &AirPodsTrayApp::onTrayIconActivated);
connect(trayManager, &TrayIconManager::openApp, this, &AirPodsTrayApp::onOpenApp);
connect(trayManager, &TrayIconManager::openSettings, this, &AirPodsTrayApp::onOpenSettings);
connect(trayManager, &TrayIconManager::noiseControlChanged, this, qOverload<NoiseControlMode>(&AirPodsTrayApp::setNoiseControlMode));
connect(trayManager, &TrayIconManager::noiseControlChanged, this, &AirPodsTrayApp::setNoiseControlMode);
connect(trayManager, &TrayIconManager::conversationalAwarenessToggled, this, &AirPodsTrayApp::setConversationalAwareness);
connect(this, &AirPodsTrayApp::batteryStatusChanged, trayManager, &TrayIconManager::updateBatteryStatus);
connect(this, &AirPodsTrayApp::noiseControlModeChanged, trayManager, &TrayIconManager::updateNoiseControlState);
connect(this, &AirPodsTrayApp::conversationalAwarenessChanged, trayManager, &TrayIconManager::updateConversationalAwareness);
connect(m_deviceInfo, &DeviceInfo::batteryStatusChanged, trayManager, &TrayIconManager::updateBatteryStatus);
connect(m_deviceInfo, &DeviceInfo::noiseControlModeChanged, trayManager, &TrayIconManager::updateNoiseControlState);
connect(m_deviceInfo, &DeviceInfo::conversationalAwarenessChanged, trayManager, &TrayIconManager::updateConversationalAwareness);
connect(trayManager, &TrayIconManager::notificationsEnabledChanged, this, &AirPodsTrayApp::saveNotificationsEnabled);
connect(trayManager, &TrayIconManager::notificationsEnabledChanged, this, &AirPodsTrayApp::notificationsEnabledChanged);
// Initialize MediaController and connect signals
mediaController = new MediaController(this);
connect(this, &AirPodsTrayApp::earDetectionStatusChanged, mediaController, &MediaController::handleEarDetection);
connect(m_deviceInfo, &DeviceInfo::earDetectionStatusChanged, mediaController, &MediaController::handleEarDetection);
connect(mediaController, &MediaController::mediaStateChanged, this, &AirPodsTrayApp::handleMediaStateChange);
mediaController->initializeMprisInterface();
mediaController->followMediaChanges();
monitor = new BluetoothMonitor(this);
connect(monitor, &BluetoothMonitor::deviceConnected, this, &AirPodsTrayApp::bluezDeviceConnected);
connect(monitor, &BluetoothMonitor::deviceDisconnected, this, &AirPodsTrayApp::bluezDeviceDisconnected);
connect(m_battery, &Battery::primaryChanged, this, &AirPodsTrayApp::primaryChanged);
connect(m_deviceInfo->getBattery(), &Battery::primaryChanged, this, &AirPodsTrayApp::primaryChanged);
// Load settings
CrossDevice.isEnabled = loadCrossDeviceEnabled();
@@ -114,31 +92,6 @@ public:
delete phoneSocket;
}
QString batteryStatus() const { return m_batteryStatus; }
QString earDetectionStatus() const { return m_earDetectionStatus; }
int noiseControlMode() const { return static_cast<int>(m_noiseControlMode); }
bool conversationalAwareness() const { return m_conversationalAwareness; }
bool adaptiveModeActive() const { return m_noiseControlMode == NoiseControlMode::Adaptive; }
int adaptiveNoiseLevel() const { return m_adaptiveNoiseLevel; }
QString deviceName() const { return m_deviceName; }
Battery *getBattery() const { return m_battery; }
bool oneOrMorePodsInCase() const { return m_earDetectionStatus.contains("In case"); }
QString podIcon() const { return getModelIcon(m_model).first; }
QString caseIcon() const { return getModelIcon(m_model).second; }
bool isLeftPodInEar() const {
if (m_battery->getPrimaryPod() == Battery::Component::Left) {
return m_primaryInEar;
} else {
return m_secoundaryInEar;
}
}
bool isRightPodInEar() const {
if (m_battery->getPrimaryPod() == Battery::Component::Right) {
return m_primaryInEar;
} else {
return m_secoundaryInEar;
}
}
bool areAirpodsConnected() const { return socket && socket->isOpen() && socket->state() == QBluetoothSocket::SocketState::ConnectedState; }
int earDetectionBehavior() const { return mediaController->getEarDetectionBehavior(); }
bool crossDeviceEnabled() const { return CrossDevice.isEnabled; }
@@ -147,7 +100,7 @@ public:
void setNotificationsEnabled(bool enabled) { trayManager->setNotificationsEnabled(enabled); }
int retryAttempts() const { return m_retryAttempts; }
bool hideOnStart() const { return m_hideOnStart; }
bool oneBudANCMode() const { return m_oneBudANCMode; }
DeviceInfo *deviceInfo() const { return m_deviceInfo; }
private:
bool debugMode;
@@ -199,39 +152,32 @@ public slots:
void setNoiseControlMode(NoiseControlMode mode)
{
LOG_INFO("Setting noise control mode to: " << mode);
if (m_noiseControlMode == mode)
{
LOG_INFO("Noise control mode is already " << mode);
return;
}
QByteArray packet = AirPodsPackets::NoiseControl::getPacketForMode(mode);
writePacketToSocket(packet, "Noise control mode packet written: ");
}
void setNoiseControlMode(int mode)
void setNoiseControlModeInt(int mode)
{
if (mode < 0 || mode > static_cast<int>(NoiseControlMode::Adaptive))
{
LOG_ERROR("Invalid noise control mode: " << mode);
return;
}
setNoiseControlMode(static_cast<NoiseControlMode>(mode));
}
void setConversationalAwareness(bool enabled)
{
if (m_conversationalAwareness == enabled)
{
LOG_INFO("Conversational awareness is already " << (enabled ? "enabled" : "disabled"));
return;
}
LOG_INFO("Setting conversational awareness to: " << (enabled ? "enabled" : "disabled"));
QByteArray packet = enabled ? AirPodsPackets::ConversationalAwareness::ENABLED
: AirPodsPackets::ConversationalAwareness::DISABLED;
writePacketToSocket(packet, "Conversational awareness packet written: ");
m_conversationalAwareness = enabled;
emit conversationalAwarenessChanged(enabled);
m_deviceInfo->setConversationalAwareness(enabled);
}
void setOneBudANCMode(bool enabled)
{
if (m_oneBudANCMode == enabled)
if (m_deviceInfo->oneBudANCMode() == enabled)
{
LOG_INFO("One Bud ANC mode is already " << (enabled ? "enabled" : "disabled"));
return;
@@ -243,8 +189,7 @@ public slots:
if (writePacketToSocket(packet, "One Bud ANC mode packet written: "))
{
m_oneBudANCMode = enabled;
emit oneBudANCModeChanged(enabled);
m_deviceInfo->setOneBudANCMode(enabled);
}
else
{
@@ -277,12 +222,11 @@ public slots:
void setAdaptiveNoiseLevel(int level)
{
level = qBound(0, level, 100);
if (m_adaptiveNoiseLevel != level && adaptiveModeActive())
if (m_deviceInfo->adaptiveNoiseLevel() != level && m_deviceInfo->adaptiveModeActive())
{
m_adaptiveNoiseLevel = level;
QByteArray packet = AirPodsPackets::AdaptiveNoise::getPacket(level);
writePacketToSocket(packet, "Adaptive noise level packet written: ");
emit adaptiveNoiseLevelChanged(level);
m_deviceInfo->setAdaptiveNoiseLevel(level);
}
}
@@ -298,7 +242,7 @@ public slots:
LOG_WARN("Name is too long, must be 32 characters or less");
return;
}
if (newName == m_deviceName)
if (newName == m_deviceInfo->deviceName())
{
LOG_INFO("Name is already set to: " << newName);
return;
@@ -308,8 +252,7 @@ public slots:
if (writePacketToSocket(packet, "Rename packet written: "))
{
LOG_INFO("Sent rename command for new name: " << newName);
m_deviceName = newName;
emit deviceNameChanged(newName);
m_deviceInfo->setDeviceName(newName);
}
else
{
@@ -413,7 +356,7 @@ private slots:
writePacketToSocket(AirPodsPackets::Connection::HANDSHAKE, "Handshake packet written: ");
}
void bluezDeviceConnected(const QString &address, const QString &name)
void bluezDeviceConnected(const QString &address, const QString &name)
{
QBluetoothDeviceInfo device(QBluetoothAddress(address), name, 0);
connectToDevice(device);
@@ -435,31 +378,7 @@ private slots:
}
// Clear the device name and model
m_deviceName.clear();
connectedDeviceMacAddress.clear();
mediaController->setConnectedDeviceMacAddress(connectedDeviceMacAddress);
m_model = AirPodsModel::Unknown;
emit deviceNameChanged(m_deviceName);
emit modelChanged();
// Reset battery status
m_battery->reset();
m_batteryStatus.clear();
emit batteryStatusChanged(m_batteryStatus);
// Reset ear detection
m_earDetectionStatus.clear();
m_primaryInEar = false;
m_secoundaryInEar = false;
emit earDetectionStatusChanged(m_earDetectionStatus);
emit primaryChanged();
// Reset noise control mode
m_noiseControlMode = NoiseControlMode::Off;
emit noiseControlModeChanged(m_noiseControlMode);
mediaController->pause(); // Since the device is deconnected, we don't know if it was the active output device. Pause to be safe
emit airPodsStatusChanged();
m_deviceInfo->reset();
// Show system notification
trayManager->showNotification(
@@ -516,43 +435,18 @@ private slots:
return str;
};
m_deviceName = extractString();
QString modelNumber = extractString();
QString manufacturer = extractString();
QString hardwareVersion = extractString();
QString firmwareVersion = extractString();
QString firmwareVersion2 = extractString();
QString softwareVersion = extractString();
QString appIdentifier = extractString();
QString serialNumber1 = extractString();
QString serialNumber2 = extractString();
QString unknownNumeric = extractString();
QString unknownHash = extractString();
QString trailingByte = extractString();
m_model = parseModelNumber(modelNumber);
m_deviceInfo->setDeviceName(extractString());
m_deviceInfo->setModelNumber(extractString());
m_deviceInfo->setManufacturer(extractString());
m_deviceInfo->setModel(parseModelNumber(m_deviceInfo->modelNumber()));
emit modelChanged();
m_model = parseModelNumber(modelNumber);
emit modelChanged();
emit deviceNameChanged(m_deviceName);
// Log extracted metadata
LOG_INFO("Parsed AirPods metadata:");
LOG_INFO("Device Name: " << m_deviceName);
LOG_INFO("Model Number: " << modelNumber);
LOG_INFO("Manufacturer: " << manufacturer);
LOG_INFO("Hardware Version: " << hardwareVersion);
LOG_INFO("Firmware Version: " << firmwareVersion);
LOG_INFO("Firmware Version2: " << firmwareVersion2);
LOG_INFO("Software Version: " << softwareVersion);
LOG_INFO("App Identifier: " << appIdentifier);
LOG_INFO("Serial Number 1: " << serialNumber1);
LOG_INFO("Serial Number 2: " << serialNumber2);
LOG_INFO("Unknown Numeric: " << unknownNumeric);
LOG_INFO("Unknown Hash: " << unknownHash);
LOG_INFO("Trailing Byte: " << trailingByte);
LOG_INFO("Device Name: " << m_deviceInfo->deviceName());
LOG_INFO("Model Number: " << m_deviceInfo->modelNumber());
LOG_INFO("Manufacturer: " << m_deviceInfo->manufacturer());
}
QString getEarStatus(char value)
@@ -606,7 +500,7 @@ private slots:
QTimer::singleShot(1500, this, [this, device]()
{ connectToDevice(device); });
}
else
else
{
LOG_ERROR("Failed to connect after 3 attempts");
retryCount = 0;
@@ -635,7 +529,7 @@ private slots:
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
QTimer::singleShot(2000, this, [this]() {
if (m_batteryStatus.isEmpty()) {
if (m_deviceInfo->batteryStatus().isEmpty()) {
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
}
});
@@ -648,34 +542,26 @@ private slots:
LOG_INFO("MagicAccIRK: " << keys.magicAccIRK.toHex());
LOG_INFO("MagicAccEncKey: " << keys.magicAccEncKey.toHex());
// Store the keys for later use if needed
m_magicAccIRK = keys.magicAccIRK;
m_magicAccEncKey = keys.magicAccEncKey;
// Store the keys
m_deviceInfo->setMagicAccIRK(keys.magicAccIRK);
m_deviceInfo->setMagicAccEncKey(keys.magicAccEncKey);
}
// Get CA state
else if (data.startsWith(AirPodsPackets::ConversationalAwareness::HEADER)) {
auto result = AirPodsPackets::ConversationalAwareness::parseState(data);
if (result.has_value()) {
m_conversationalAwareness = result.value();
LOG_INFO("Conversational awareness state received: " << m_conversationalAwareness);
emit conversationalAwarenessChanged(m_conversationalAwareness);
} else {
LOG_ERROR("Failed to parse conversational awareness state");
if (auto result = AirPodsPackets::ConversationalAwareness::parseState(data))
{
m_deviceInfo->setConversationalAwareness(result.value());
LOG_INFO("Conversational awareness state received: " << m_deviceInfo->conversationalAwareness());
}
}
// Noise Control Mode
else if (data.size() == 11 && data.startsWith(AirPodsPackets::NoiseControl::HEADER))
{
quint8 rawMode = data[7] - 1; // Offset still needed due to protocol
if (rawMode >= (int)NoiseControlMode::MinValue && rawMode <= (int)NoiseControlMode::MaxValue)
if (auto value = AirPodsPackets::NoiseControl::parseMode(data))
{
m_noiseControlMode = static_cast<NoiseControlMode>(rawMode);
LOG_INFO("Noise control mode: " << rawMode);
emit noiseControlModeChanged(m_noiseControlMode);
}
else
{
LOG_ERROR("Invalid noise control mode value received: " << rawMode);
LOG_INFO("Received noise control mode: " << value.value());
m_deviceInfo->setNoiseControlMode(value.value());
LOG_INFO("Noise control mode received: " << m_deviceInfo->noiseControlMode());
}
}
// Ear Detection
@@ -683,28 +569,25 @@ private slots:
{
char primary = data[6];
char secondary = data[7];
m_primaryInEar = data[6] == 0x00;
m_secoundaryInEar = data[7] == 0x00;
m_earDetectionStatus = QString("Primary: %1, Secondary: %2")
.arg(getEarStatus(primary), getEarStatus(secondary));
LOG_INFO("Ear detection status: " << m_earDetectionStatus);
emit earDetectionStatusChanged(m_earDetectionStatus);
emit primaryChanged();
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
else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS))
{
m_battery->parsePacket(data);
m_deviceInfo->getBattery()->parsePacket(data);
int leftLevel = m_battery->getState(Battery::Component::Left).level;
int rightLevel = m_battery->getState(Battery::Component::Right).level;
int caseLevel = m_battery->getState(Battery::Component::Case).level;
m_batteryStatus = QString("Left: %1%, Right: %2%, Case: %3%")
.arg(leftLevel)
.arg(rightLevel)
.arg(caseLevel);
LOG_INFO("Battery status: " << m_batteryStatus);
emit batteryStatusChanged(m_batteryStatus);
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());
}
// Conversational Awareness Data
else if (data.size() == 10 && data.startsWith(AirPodsPackets::ConversationalAwareness::DATA_HEADER))
@@ -717,23 +600,17 @@ private slots:
parseMetadata(data);
initiateMagicPairing();
mediaController->setConnectedDeviceMacAddress(connectedDeviceMacAddress);
if (isLeftPodInEar() || isRightPodInEar()) // AirPods get added as output device only after this
if (m_deviceInfo->oneOrMorePodsInEar()) // AirPods get added as output device only after this
{
mediaController->activateA2dpProfile();
}
emit airPodsStatusChanged();
}
else if (data.startsWith(AirPodsPackets::OneBudANCMode::HEADER)) {
auto result = AirPodsPackets::OneBudANCMode::parseState(data);
if (result.has_value())
if (auto value = AirPodsPackets::OneBudANCMode::parseState(data))
{
m_oneBudANCMode = result.value();
LOG_INFO("One Bud ANC mode received: " << m_conversationalAwareness);
emit oneBudANCModeChanged(m_conversationalAwareness);
}
else
{
LOG_ERROR("Failed to parse One Bud ANC mode");
m_deviceInfo->setOneBudANCMode(value.value());
LOG_INFO("One Bud ANC mode received: " << m_deviceInfo->oneBudANCMode());
}
}
else
@@ -856,14 +733,14 @@ private slots:
QMetaObject::invokeMethod(this, "handlePhonePacket", Qt::QueuedConnection, Q_ARG(QByteArray, data));
}
public:
void handleMediaStateChange(MediaController::MediaState state) {
if (state == MediaController::MediaState::Playing) {
LOG_INFO("Media started playing, sending disconnect request to Android and taking over audio");
sendDisconnectRequestToAndroid();
connectToAirPods(true);
}
public:
void handleMediaStateChange(MediaController::MediaState state) {
if (state == MediaController::MediaState::Playing) {
LOG_INFO("Media started playing, sending disconnect request to Android and taking over audio");
sendDisconnectRequestToAndroid();
connectToAirPods(true);
}
}
void sendDisconnectRequestToAndroid()
{
@@ -980,20 +857,7 @@ private:
AutoStartManager *m_autoStartManager;
int m_retryAttempts = 3;
bool m_hideOnStart = false;
QString m_batteryStatus;
QString m_earDetectionStatus;
NoiseControlMode m_noiseControlMode = NoiseControlMode::Off;
bool m_conversationalAwareness = false;
int m_adaptiveNoiseLevel = 50;
QString m_deviceName;
Battery *m_battery;
AirPodsModel m_model = AirPodsModel::Unknown;
bool m_primaryInEar = false;
bool m_secoundaryInEar = false;
QByteArray m_magicAccIRK;
QByteArray m_magicAccEncKey;
bool m_oneBudANCMode = false;
DeviceInfo *m_deviceInfo;
};
int main(int argc, char *argv[]) {
@@ -1038,6 +902,7 @@ int main(int argc, char *argv[]) {
QQmlApplicationEngine engine;
qmlRegisterType<Battery>("me.kavishdevar.Battery", 1, 0, "Battery");
qmlRegisterType<DeviceInfo>("me.kavishdevar.DeviceInfo", 1, 0, "DeviceInfo");
AirPodsTrayApp *trayApp = new AirPodsTrayApp(debugMode, hideOnStart, &engine);
engine.rootContext()->setContextProperty("airPodsTrayApp", trayApp);
trayApp->loadMainModule();