diff --git a/linux/Main.qml b/linux/Main.qml index 1bfc3b7..822c5fc 100644 --- a/linux/Main.qml +++ b/linux/Main.qml @@ -154,8 +154,28 @@ ApplicationWindow { visible: airPodsTrayApp.airpodsConnected text: "Conversational Awareness" checked: airPodsTrayApp.deviceInfo.conversationalAwareness + // Disable when no phone MAC set or using default placeholder + enabled: !(PHONE_MAC_ADDRESS === "" || PHONE_MAC_ADDRESS === "00:00:00:00:00:00") onCheckedChanged: airPodsTrayApp.setConversationalAwareness(checked) } + + // Instruction when Conversational Awareness is disabled due to missing phone MAC + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 8 + visible: airPodsTrayApp.airpodsConnected && (PHONE_MAC_ADDRESS === "" || PHONE_MAC_ADDRESS === "00:00:00:00:00:00") + + Label { + text: "Set your phone's MAC in Settings to use this feature" + font.pixelSize: 12 + color: "gray" + } + + Button { + text: "Open Settings" + onClicked: stackView.push(settingsPage) + } + } } RoundButton { @@ -269,6 +289,23 @@ ApplicationWindow { } } + Row { + spacing: 10 + visible: airPodsTrayApp.airpodsConnected + + TextField { + id: newPhoneMacField + placeholderText: (PHONE_MAC_ADDRESS !== "" ? PHONE_MAC_ADDRESS : "00:00:00:00:00:00") + maximumLength: 32 + } + + Button { + text: "Change Phone MAC" + onClicked: airPodsTrayApp.setPhoneMac(newPhoneMacField.text) + } + } + + Button { text: "Show Magic Cloud Keys QR" onClicked: keysQrDialog.show() diff --git a/linux/main.cpp b/linux/main.cpp index 8bcfe42..74fc672 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "airpods_packets.h" #include "logger.h" @@ -40,6 +41,7 @@ class AirPodsTrayApp : public QObject { Q_PROPERTY(int retryAttempts READ retryAttempts WRITE setRetryAttempts NOTIFY retryAttemptsChanged) Q_PROPERTY(bool hideOnStart READ hideOnStart CONSTANT) Q_PROPERTY(DeviceInfo *deviceInfo READ deviceInfo CONSTANT) + Q_PROPERTY(QString phoneMacStatus READ phoneMacStatus NOTIFY phoneMacStatusChanged) public: AirPodsTrayApp(bool debugMode, bool hideOnStart, QQmlApplicationEngine *parent = nullptr) @@ -119,6 +121,7 @@ public: int retryAttempts() const { return m_retryAttempts; } bool hideOnStart() const { return m_hideOnStart; } DeviceInfo *deviceInfo() const { return m_deviceInfo; } + QString phoneMacStatus() const { return m_phoneMacStatus; } private: bool debugMode; @@ -310,6 +313,51 @@ public slots: emit crossDeviceEnabledChanged(enabled); } + void setPhoneMac(const QString &mac) + { + if (mac.isEmpty()) { + LOG_WARN("Empty MAC provided, ignoring"); + m_phoneMacStatus = QStringLiteral("No MAC provided (ignoring)"); + emit phoneMacStatusChanged(); + return; + } + + // Basic MAC address validation (accepts formats like AA:BB:CC:DD:EE:FF, AABBCCDDEEFF, AA-BB-CC-DD-EE-FF) + QRegularExpression re("^([0-9A-Fa-f]{2}([-:]?)){5}[0-9A-Fa-f]{2}$"); + if (!re.match(mac).hasMatch()) { + LOG_ERROR("Invalid MAC address format: " << mac); + m_phoneMacStatus = QStringLiteral("Invalid MAC: ") + mac; + emit phoneMacStatusChanged(); + return; + } + + // Set environment variable for the running process + qputenv("PHONE_MAC_ADDRESS", mac.toUtf8()); + LOG_INFO("PHONE_MAC_ADDRESS environment variable set to: " << mac); + + m_phoneMacStatus = QStringLiteral("Updated MAC: ") + mac; + emit phoneMacStatusChanged(); + + // Update QML context property so UI placeholders reflect the new value + if (parent) { + parent->rootContext()->setContextProperty("PHONE_MAC_ADDRESS", mac); + } + + // If a phone socket exists, restart connection using the new MAC + if (phoneSocket && phoneSocket->isOpen()) { + phoneSocket->close(); + phoneSocket->deleteLater(); + phoneSocket = nullptr; + } + connectToPhone(); + } + + void updatePhoneMacStatus(const QString &status) + { + m_phoneMacStatus = status; + emit phoneMacStatusChanged(); + } + bool writePacketToSocket(const QByteArray &packet, const QString &logMessage) { if (socket && socket->isOpen()) @@ -658,6 +706,7 @@ private slots: } QBluetoothAddress phoneAddress("00:00:00:00:00:00"); // Default address, will be overwritten if PHONE_MAC_ADDRESS is set QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + if (!env.value("PHONE_MAC_ADDRESS").isEmpty()) { phoneAddress = QBluetoothAddress(env.value("PHONE_MAC_ADDRESS")); @@ -854,6 +903,7 @@ signals: void notificationsEnabledChanged(bool enabled); void retryAttemptsChanged(int attempts); void oneBudANCModeChanged(bool enabled); + void phoneMacStatusChanged(); private: QBluetoothSocket *socket = nullptr; @@ -870,6 +920,7 @@ private: DeviceInfo *m_deviceInfo; BleManager *m_bleManager; SystemSleepMonitor *m_systemSleepMonitor = nullptr; + QString m_phoneMacStatus; }; int main(int argc, char *argv[]) { @@ -917,6 +968,16 @@ int main(int argc, char *argv[]) { qmlRegisterType("me.kavishdevar.DeviceInfo", 1, 0, "DeviceInfo"); AirPodsTrayApp *trayApp = new AirPodsTrayApp(debugMode, hideOnStart, &engine); engine.rootContext()->setContextProperty("airPodsTrayApp", trayApp); + + // Expose PHONE_MAC_ADDRESS environment variable to QML for placeholder in settings + { + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + QString phoneMacEnv = env.value("PHONE_MAC_ADDRESS", ""); + engine.rootContext()->setContextProperty("PHONE_MAC_ADDRESS", phoneMacEnv); + // Initialize the visible status in the GUI + trayApp->updatePhoneMacStatus(phoneMacEnv.isEmpty() ? QStringLiteral("No phone MAC set") : phoneMacEnv); + } + engine.addImageProvider("qrcode", new QRCodeImageProvider()); trayApp->loadMainModule();