From 8a69dbe173d9a73c780eac64c98c3a0d3c93d61b Mon Sep 17 00:00:00 2001 From: Tim Gromeyer <58736434+tim-gromeyer@users.noreply.github.com> Date: Tue, 3 Jun 2025 09:07:30 +0200 Subject: [PATCH] [Linux] Move all device related properties to new class (#135) * Clean up code * Move all device releated properties to new class --- linux/BasicControlCommand.hpp | 27 ++-- linux/CMakeLists.txt | 1 + linux/airpods_packets.h | 13 +- linux/battery.hpp | 2 + linux/deviceinfo.hpp | 209 +++++++++++++++++++++++++ linux/main.cpp | 285 +++++++++------------------------- 6 files changed, 314 insertions(+), 223 deletions(-) create mode 100644 linux/deviceinfo.hpp diff --git a/linux/BasicControlCommand.hpp b/linux/BasicControlCommand.hpp index 747068e..8fe3f3e 100644 --- a/linux/BasicControlCommand.hpp +++ b/linux/BasicControlCommand.hpp @@ -18,22 +18,12 @@ namespace ControlCommand return packet; } - // Parse activated/not activated - inline std::optional parseActive(const QByteArray &data) + inline std::optional parseActive(const QByteArray &data) { if (!data.startsWith(ControlCommand::HEADER)) return std::nullopt; - quint8 statusByte = static_cast(data.at(7)); - switch (statusByte) - { - case 0x01: // Enabled - return true; - case 0x02: // Disabled - return false; - default: - return std::nullopt; - } + return static_cast(data.at(7)); } } @@ -54,6 +44,19 @@ struct BasicControlCommand // Basically returns the byte at the index 7 static std::optional 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 getValue(const QByteArray &data) { return ControlCommand::parseActive(data); } diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 186b108..cae68ef 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -23,6 +23,7 @@ qt_add_executable(applinux BluetoothMonitor.h autostartmanager.hpp BasicControlCommand.hpp + deviceinfo.hpp ) qt_add_qml_module(applinux diff --git a/linux/airpods_packets.h b/linux/airpods_packets.h index e7a4db1..58e1361 100644 --- a/linux/airpods_packets.h +++ b/linux/airpods_packets.h @@ -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 parseMode(const QByteArray &data) + { + char mode = ControlCommand::parseActive(data).value_or(CHAR_MAX); + if (mode < static_cast(NoiseControlMode::MinValue) || + mode > static_cast(NoiseControlMode::MaxValue)) + { + return std::nullopt; + } + return static_cast(mode - 1); + } } // One Bud ANC Mode diff --git a/linux/battery.hpp b/linux/battery.hpp index 6211901..a8a95a2 100644 --- a/linux/battery.hpp +++ b/linux/battery.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include #include diff --git a/linux/deviceinfo.hpp b/linux/deviceinfo.hpp new file mode 100644 index 0000000..4bbabde --- /dev/null +++ b/linux/deviceinfo.hpp @@ -0,0 +1,209 @@ +#pragma once + +#include +#include +#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(mode)); + } + } + int noiseControlModeInt() const { return static_cast(noiseControlMode()); } + void setNoiseControlModeInt(int mode) { setNoiseControlMode(static_cast(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; +}; \ No newline at end of file diff --git a/linux/main.cpp b/linux/main.cpp index 925757f..b507253 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -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(&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(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(NoiseControlMode::Adaptive)) + { + LOG_ERROR("Invalid noise control mode: " << mode); + return; + } setNoiseControlMode(static_cast(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(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("me.kavishdevar.Battery", 1, 0, "Battery"); + qmlRegisterType("me.kavishdevar.DeviceInfo", 1, 0, "DeviceInfo"); AirPodsTrayApp *trayApp = new AirPodsTrayApp(debugMode, hideOnStart, &engine); engine.rootContext()->setContextProperty("airPodsTrayApp", trayApp); trayApp->loadMainModule();