diff --git a/linux/Main.qml b/linux/Main.qml index 28e9eee..c225cd0 100644 --- a/linux/Main.qml +++ b/linux/Main.qml @@ -94,46 +94,46 @@ ApplicationWindow { spacing: 8 PodColumn { - isVisible: airPodsTrayApp.battery.leftPodAvailable - inEar: airPodsTrayApp.leftPodInEar - iconSource: "qrc:/icons/assets/" + airPodsTrayApp.podIcon - batteryLevel: airPodsTrayApp.battery.leftPodLevel - isCharging: airPodsTrayApp.battery.leftPodCharging + isVisible: airPodsTrayApp.deviceInfo.battery.leftPodAvailable + inEar: airPodsTrayApp.deviceInfo.leftPodInEar + iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.podIcon + batteryLevel: airPodsTrayApp.deviceInfo.battery.leftPodLevel + isCharging: airPodsTrayApp.deviceInfo.battery.leftPodCharging indicator: "L" } PodColumn { - isVisible: airPodsTrayApp.battery.rightPodAvailable - inEar: airPodsTrayApp.rightPodInEar - iconSource: "qrc:/icons/assets/" + airPodsTrayApp.podIcon - batteryLevel: airPodsTrayApp.battery.rightPodLevel - isCharging: airPodsTrayApp.battery.rightPodCharging + isVisible: airPodsTrayApp.deviceInfo.battery.rightPodAvailable + inEar: airPodsTrayApp.deviceInfo.rightPodInEar + iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.podIcon + batteryLevel: airPodsTrayApp.deviceInfo.battery.rightPodLevel + isCharging: airPodsTrayApp.deviceInfo.battery.rightPodCharging indicator: "R" } PodColumn { - isVisible: airPodsTrayApp.battery.caseAvailable + isVisible: airPodsTrayApp.deviceInfo.battery.caseAvailable inEar: true - iconSource: "qrc:/icons/assets/" + airPodsTrayApp.caseIcon - batteryLevel: airPodsTrayApp.battery.caseLevel - isCharging: airPodsTrayApp.battery.caseCharging + iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.caseIcon + batteryLevel: airPodsTrayApp.deviceInfo.battery.caseLevel + isCharging: airPodsTrayApp.deviceInfo.battery.caseCharging } } SegmentedControl { anchors.horizontalCenter: parent.horizontalCenter model: ["Off", "Noise Cancellation", "Transparency", "Adaptive"] - currentIndex: airPodsTrayApp.noiseControlMode - onCurrentIndexChanged: airPodsTrayApp.noiseControlMode = currentIndex + currentIndex: airPodsTrayApp.deviceInfo.noiseControlMode + onCurrentIndexChanged: airPodsTrayApp.setNoiseControlModeInt(currentIndex) visible: airPodsTrayApp.airpodsConnected } Slider { - visible: airPodsTrayApp.adaptiveModeActive + visible: airPodsTrayApp.deviceInfo.adaptiveModeActive from: 0 to: 100 stepSize: 1 - value: airPodsTrayApp.adaptiveNoiseLevel + value: airPodsTrayApp.deviceInfo.adaptiveNoiseLevel Timer { id: debounceTimer @@ -153,8 +153,8 @@ ApplicationWindow { Switch { visible: airPodsTrayApp.airpodsConnected text: "Conversational Awareness" - checked: airPodsTrayApp.conversationalAwareness - onCheckedChanged: airPodsTrayApp.conversationalAwareness = checked + checked: airPodsTrayApp.deviceInfo.conversationalAwareness + onCheckedChanged: airPodsTrayApp.setConversationalAwareness(checked) } } @@ -229,8 +229,8 @@ ApplicationWindow { Switch { visible: airPodsTrayApp.airpodsConnected text: "One Bud ANC Mode" - checked: airPodsTrayApp.oneBudANCMode - onCheckedChanged: airPodsTrayApp.oneBudANCMode = checked + checked: airPodsTrayApp.deviceInfo.oneBudANCMode + onCheckedChanged: airPodsTrayApp.deviceInfo.oneBudANCMode = checked ToolTip { visible: parent.hovered @@ -259,13 +259,13 @@ ApplicationWindow { TextField { id: newNameField - placeholderText: airPodsTrayApp.deviceName + placeholderText: airPodsTrayApp.deviceInfo.deviceName maximumLength: 32 } Button { text: "Rename" - onClicked: airPodsTrayApp.renameAirPods(newNameField.text) + onClicked: airPodsTrayApp.deviceInfo.renameAirPods(newNameField.text) } } } diff --git a/linux/deviceinfo.hpp b/linux/deviceinfo.hpp index 4bbabde..ceca235 100644 --- a/linux/deviceinfo.hpp +++ b/linux/deviceinfo.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "battery.hpp" #include "enums.h" @@ -26,6 +27,7 @@ class DeviceInfo : public QObject 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(QString bluetoothAddress READ bluetoothAddress WRITE setBluetoothAddress NOTIFY bluetoothAddressChanged) public: explicit DeviceInfo(QObject *parent = nullptr) : QObject(parent), m_battery(new Battery(this)) {} @@ -147,6 +149,16 @@ public: QString manufacturer() const { return m_manufacturer; } void setManufacturer(const QString &manufacturer) { m_manufacturer = manufacturer; } + QString bluetoothAddress() const { return m_bluetoothAddress; } + void setBluetoothAddress(const QString &address) + { + if (m_bluetoothAddress != address) + { + m_bluetoothAddress = address; + emit bluetoothAddressChanged(address); + } + } + QString podIcon() const { return getModelIcon(model()).first; } QString caseIcon() const { return getModelIcon(model()).second; } bool isLeftPodInEar() const @@ -174,6 +186,29 @@ public: setPrimaryInEar(false); setSecondaryInEar(false); setNoiseControlMode(NoiseControlMode::Off); + setBluetoothAddress(""); + } + + void save() const + { + QSettings settings("AirpodsTrayApp", "DeviceInfo"); + settings.beginGroup("DeviceInfo"); + settings.setValue("deviceName", m_deviceName); + settings.setValue("bluetoothAddress", m_bluetoothAddress); + settings.setValue("magicAccIRK", m_magicAccIRK.toBase64()); + settings.setValue("magicAccEncKey", m_magicAccEncKey.toBase64()); + settings.endGroup(); + } + + void load() + { + QSettings settings("AirpodsTrayApp", "DeviceInfo"); + settings.beginGroup("DeviceInfo"); + setDeviceName(settings.value("deviceName", "").toString()); + setBluetoothAddress(settings.value("bluetoothAddress", "").toString()); + setMagicAccIRK(QByteArray::fromBase64(settings.value("magicAccIRK", "").toByteArray())); + setMagicAccEncKey(QByteArray::fromBase64(settings.value("magicAccEncKey", "").toByteArray())); + settings.endGroup(); } signals: @@ -187,6 +222,7 @@ signals: void primaryChanged(); void oneBudANCModeChanged(bool enabled); void modelChanged(); + void bluetoothAddressChanged(const QString &address); private: QString m_batteryStatus; @@ -202,8 +238,7 @@ private: QByteArray m_magicAccEncKey; bool m_oneBudANCMode = false; AirPodsModel m_model = AirPodsModel::Unknown; - - // Additional metadata fields QString m_modelNumber; QString m_manufacturer; + QString m_bluetoothAddress; }; \ No newline at end of file diff --git a/linux/main.cpp b/linux/main.cpp index b507253..7cb09a9 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -389,11 +389,11 @@ private slots: void bluezDeviceDisconnected(const QString &address, const QString &name) { - if (address == connectedDeviceMacAddress.replace("_", ":")) + if (address == m_deviceInfo->bluetoothAddress()) { - onDeviceDisconnected(QBluetoothAddress(address)); } - else { - LOG_WARN("Disconnected device does not match connected device: " << address << " != " << connectedDeviceMacAddress); + onDeviceDisconnected(QBluetoothAddress(address)); + } else { + LOG_WARN("Disconnected device does not match connected device: " << address << " != " << m_deviceInfo->bluetoothAddress()); } } @@ -512,7 +512,7 @@ private slots: this, handleError); localSocket->connectToService(device.address(), QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a")); - connectedDeviceMacAddress = device.address().toString().replace(":", "_"); + m_deviceInfo->setBluetoothAddress(device.address().toString()); notifyAndroidDevice(); } @@ -599,7 +599,7 @@ private slots: { parseMetadata(data); initiateMagicPairing(); - mediaController->setConnectedDeviceMacAddress(connectedDeviceMacAddress); + mediaController->setConnectedDeviceMacAddress(m_deviceInfo->bluetoothAddress().replace(":", "_")); if (m_deviceInfo->oneOrMorePodsInEar()) // AirPods get added as output device only after this { mediaController->activateA2dpProfile(); @@ -708,7 +708,7 @@ private slots: socket->close(); LOG_INFO("Disconnected from AirPods"); QProcess process; - process.start("bluetoothctl", QStringList() << "disconnect" << connectedDeviceMacAddress.replace("_", ":")); + process.start("bluetoothctl", QStringList() << "disconnect" << m_deviceInfo->bluetoothAddress()); process.waitForFinished(); QString output = process.readAllStandardOutput().trimmed(); LOG_INFO("Bluetoothctl output: " << output); @@ -770,13 +770,13 @@ public: if (force) { LOG_INFO("Forcing connection to AirPods"); QProcess process; - process.start("bluetoothctl", QStringList() << "connect" << connectedDeviceMacAddress.replace("_", ":")); + process.start("bluetoothctl", QStringList() << "connect" << m_deviceInfo->bluetoothAddress()); process.waitForFinished(); QString output = process.readAllStandardOutput().trimmed(); LOG_INFO("Bluetoothctl output: " << output); if (output.contains("Connection successful")) { LOG_INFO("Connection successful, proceeding with L2CAP connection"); - QBluetoothAddress btAddress(connectedDeviceMacAddress.replace("_", ":")); + QBluetoothAddress btAddress(m_deviceInfo->bluetoothAddress()); forceL2capConnection(btAddress); } else { LOG_ERROR("Connection failed, cannot proceed with L2CAP connection"); @@ -847,7 +847,6 @@ signals: private: QBluetoothSocket *socket = nullptr; QBluetoothSocket *phoneSocket = nullptr; - QString connectedDeviceMacAddress; QByteArray lastBatteryStatus; QByteArray lastEarDetectionStatus; MediaController* mediaController;