mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-01-28 22:01:50 +00:00
[Linux] Improve connection stability (#98)
This commit is contained in:
@@ -2,10 +2,16 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
#include <QDBusMetaType>
|
||||||
|
|
||||||
BluetoothMonitor::BluetoothMonitor(QObject *parent)
|
BluetoothMonitor::BluetoothMonitor(QObject *parent)
|
||||||
: QObject(parent), m_dbus(QDBusConnection::systemBus())
|
: QObject(parent), m_dbus(QDBusConnection::systemBus())
|
||||||
{
|
{
|
||||||
|
// Register meta-types for D-Bus interaction
|
||||||
|
qDBusRegisterMetaType<QDBusObjectPath>();
|
||||||
|
qDBusRegisterMetaType<ManagedObjectList>();
|
||||||
|
|
||||||
if (!m_dbus.isConnected())
|
if (!m_dbus.isConnected())
|
||||||
{
|
{
|
||||||
LOG_WARN("Failed to connect to system D-Bus");
|
LOG_WARN("Failed to connect to system D-Bus");
|
||||||
@@ -13,6 +19,7 @@ BluetoothMonitor::BluetoothMonitor(QObject *parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerDBusService();
|
registerDBusService();
|
||||||
|
checkAlreadyConnectedDevices(); // Check for already connected devices on startup
|
||||||
}
|
}
|
||||||
|
|
||||||
BluetoothMonitor::~BluetoothMonitor()
|
BluetoothMonitor::~BluetoothMonitor()
|
||||||
@@ -23,18 +30,6 @@ BluetoothMonitor::~BluetoothMonitor()
|
|||||||
void BluetoothMonitor::registerDBusService()
|
void BluetoothMonitor::registerDBusService()
|
||||||
{
|
{
|
||||||
// Match signals for PropertiesChanged on any BlueZ Device interface
|
// Match signals for PropertiesChanged on any BlueZ Device interface
|
||||||
QString matchRule = QStringLiteral("type='signal',"
|
|
||||||
"interface='org.freedesktop.DBus.Properties',"
|
|
||||||
"member='PropertiesChanged',"
|
|
||||||
"path_namespace='/org/bluez'");
|
|
||||||
|
|
||||||
m_dbus.connect("org.freedesktop.DBus",
|
|
||||||
"/org/freedesktop/DBus",
|
|
||||||
"org.freedesktop.DBus",
|
|
||||||
"AddMatch",
|
|
||||||
this,
|
|
||||||
SLOT(onPropertiesChanged(QString, QVariantMap, QStringList)));
|
|
||||||
|
|
||||||
if (!m_dbus.connect("", "", "org.freedesktop.DBus.Properties", "PropertiesChanged",
|
if (!m_dbus.connect("", "", "org.freedesktop.DBus.Properties", "PropertiesChanged",
|
||||||
this, SLOT(onPropertiesChanged(QString, QVariantMap, QStringList))))
|
this, SLOT(onPropertiesChanged(QString, QVariantMap, QStringList))))
|
||||||
{
|
{
|
||||||
@@ -42,6 +37,86 @@ void BluetoothMonitor::registerDBusService()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BluetoothMonitor::isAirPodsDevice(const QString &devicePath)
|
||||||
|
{
|
||||||
|
QDBusInterface deviceInterface("org.bluez", devicePath, "org.freedesktop.DBus.Properties", m_dbus);
|
||||||
|
|
||||||
|
// Get UUIDs to check if it's an AirPods device
|
||||||
|
QDBusReply<QVariant> uuidsReply = deviceInterface.call("Get", "org.bluez.Device1", "UUIDs");
|
||||||
|
if (!uuidsReply.isValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList uuids = uuidsReply.value().toStringList();
|
||||||
|
return uuids.contains("74ec2172-0bad-4d01-8f77-997b2be0722a");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BluetoothMonitor::getDeviceName(const QString &devicePath)
|
||||||
|
{
|
||||||
|
QDBusInterface deviceInterface("org.bluez", devicePath, "org.freedesktop.DBus.Properties", m_dbus);
|
||||||
|
QDBusReply<QVariant> nameReply = deviceInterface.call("Get", "org.bluez.Device1", "Name");
|
||||||
|
if (nameReply.isValid())
|
||||||
|
{
|
||||||
|
return nameReply.value().toString();
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothMonitor::checkAlreadyConnectedDevices()
|
||||||
|
{
|
||||||
|
QDBusInterface objectManager("org.bluez", "/", "org.freedesktop.DBus.ObjectManager", m_dbus);
|
||||||
|
QDBusMessage reply = objectManager.call("GetManagedObjects");
|
||||||
|
|
||||||
|
if (reply.type() == QDBusMessage::ErrorMessage)
|
||||||
|
{
|
||||||
|
LOG_WARN("Failed to get managed objects: " << reply.errorMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant firstArg = reply.arguments().constFirst();
|
||||||
|
QDBusArgument arg = firstArg.value<QDBusArgument>();
|
||||||
|
ManagedObjectList managedObjects;
|
||||||
|
arg >> managedObjects;
|
||||||
|
|
||||||
|
bool deviceFound = false;
|
||||||
|
|
||||||
|
for (auto it = managedObjects.constBegin(); it != managedObjects.constEnd(); ++it)
|
||||||
|
{
|
||||||
|
const QDBusObjectPath &objPath = it.key();
|
||||||
|
const QMap<QString, QVariantMap> &interfaces = it.value();
|
||||||
|
|
||||||
|
if (interfaces.contains("org.bluez.Device1"))
|
||||||
|
{
|
||||||
|
const QVariantMap &deviceProps = interfaces.value("org.bluez.Device1");
|
||||||
|
|
||||||
|
// Check if the device has the necessary properties
|
||||||
|
if (!deviceProps.contains("UUIDs") || !deviceProps.contains("Connected") ||
|
||||||
|
!deviceProps.contains("Address") || !deviceProps.contains("Name"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList uuids = deviceProps["UUIDs"].toStringList();
|
||||||
|
bool isAirPods = uuids.contains("74ec2172-0bad-4d01-8f77-997b2be0722a");
|
||||||
|
|
||||||
|
if (isAirPods)
|
||||||
|
{
|
||||||
|
bool connected = deviceProps["Connected"].toBool();
|
||||||
|
if (connected)
|
||||||
|
{
|
||||||
|
QString macAddress = deviceProps["Address"].toString();
|
||||||
|
QString deviceName = deviceProps["Name"].toString();
|
||||||
|
emit deviceConnected(macAddress, deviceName);
|
||||||
|
LOG_DEBUG("Found already connected AirPods: " << macAddress << " Name: " << deviceName);
|
||||||
|
deviceFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deviceFound;
|
||||||
|
}
|
||||||
|
|
||||||
void BluetoothMonitor::onPropertiesChanged(const QString &interface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
|
void BluetoothMonitor::onPropertiesChanged(const QString &interface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
|
||||||
{
|
{
|
||||||
Q_UNUSED(invalidatedProps);
|
Q_UNUSED(invalidatedProps);
|
||||||
@@ -56,6 +131,11 @@ void BluetoothMonitor::onPropertiesChanged(const QString &interface, const QVari
|
|||||||
bool connected = changedProps["Connected"].toBool();
|
bool connected = changedProps["Connected"].toBool();
|
||||||
QString path = QDBusContext::message().path();
|
QString path = QDBusContext::message().path();
|
||||||
|
|
||||||
|
if (!isAirPodsDevice(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QDBusInterface deviceInterface("org.bluez", path, "org.freedesktop.DBus.Properties", m_dbus);
|
QDBusInterface deviceInterface("org.bluez", path, "org.freedesktop.DBus.Properties", m_dbus);
|
||||||
|
|
||||||
// Get the device address
|
// Get the device address
|
||||||
@@ -65,29 +145,17 @@ void BluetoothMonitor::onPropertiesChanged(const QString &interface, const QVari
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QString macAddress = addrReply.value().toString();
|
QString macAddress = addrReply.value().toString();
|
||||||
|
QString deviceName = getDeviceName(path);
|
||||||
// Get UUIDs to check if it's an AirPods device
|
|
||||||
QDBusReply<QVariant> uuidsReply = deviceInterface.call("Get", "org.bluez.Device1", "UUIDs");
|
|
||||||
if (!uuidsReply.isValid())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList uuids = uuidsReply.value().toStringList();
|
|
||||||
if (!uuids.contains("74ec2172-0bad-4d01-8f77-997b2be0722a"))
|
|
||||||
{
|
|
||||||
return; // Not an AirPods device
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connected)
|
if (connected)
|
||||||
{
|
{
|
||||||
emit deviceConnected(macAddress);
|
emit deviceConnected(macAddress, deviceName);
|
||||||
LOG_DEBUG("AirPods device connected:" << macAddress);
|
LOG_DEBUG("AirPods device connected:" << macAddress << " Name:" << deviceName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
emit deviceDisconnected(macAddress);
|
emit deviceDisconnected(macAddress, deviceName);
|
||||||
LOG_DEBUG("AirPods device disconnected:" << macAddress);
|
LOG_DEBUG("AirPods device disconnected:" << macAddress << " Name:" << deviceName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,10 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QtDBus/QtDBus>
|
#include <QtDBus/QtDBus>
|
||||||
|
|
||||||
|
// Forward declarations for D-Bus types
|
||||||
|
typedef QMap<QDBusObjectPath, QMap<QString, QVariantMap>> ManagedObjectList;
|
||||||
|
Q_DECLARE_METATYPE(ManagedObjectList)
|
||||||
|
|
||||||
class BluetoothMonitor : public QObject, protected QDBusContext
|
class BluetoothMonitor : public QObject, protected QDBusContext
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -11,9 +15,11 @@ public:
|
|||||||
explicit BluetoothMonitor(QObject *parent = nullptr);
|
explicit BluetoothMonitor(QObject *parent = nullptr);
|
||||||
~BluetoothMonitor();
|
~BluetoothMonitor();
|
||||||
|
|
||||||
|
bool checkAlreadyConnectedDevices();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void deviceConnected(const QString &macAddress);
|
void deviceConnected(const QString &macAddress, const QString &deviceName);
|
||||||
void deviceDisconnected(const QString &macAddress);
|
void deviceDisconnected(const QString &macAddress, const QString &deviceName);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onPropertiesChanged(const QString &interface, const QVariantMap &changedProps, const QStringList &invalidatedProps);
|
void onPropertiesChanged(const QString &interface, const QVariantMap &changedProps, const QStringList &invalidatedProps);
|
||||||
@@ -21,6 +27,8 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
QDBusConnection m_dbus;
|
QDBusConnection m_dbus;
|
||||||
void registerDBusService();
|
void registerDBusService();
|
||||||
|
bool isAirPodsDevice(const QString &devicePath);
|
||||||
|
QString getDeviceName(const QString &devicePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BLUETOOTHMONITOR_H
|
#endif // BLUETOOTHMONITOR_H
|
||||||
@@ -104,6 +104,7 @@ ApplicationWindow {
|
|||||||
model: ["Off", "Noise Cancellation", "Transparency", "Adaptive"]
|
model: ["Off", "Noise Cancellation", "Transparency", "Adaptive"]
|
||||||
currentIndex: airPodsTrayApp.noiseControlMode
|
currentIndex: airPodsTrayApp.noiseControlMode
|
||||||
onCurrentIndexChanged: airPodsTrayApp.noiseControlMode = currentIndex
|
onCurrentIndexChanged: airPodsTrayApp.noiseControlMode = currentIndex
|
||||||
|
visible: airPodsTrayApp.airpodsConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
|||||||
185
linux/main.cpp
185
linux/main.cpp
@@ -28,6 +28,7 @@ class AirPodsTrayApp : public QObject {
|
|||||||
Q_PROPERTY(QString caseIcon READ caseIcon NOTIFY modelChanged)
|
Q_PROPERTY(QString caseIcon READ caseIcon NOTIFY modelChanged)
|
||||||
Q_PROPERTY(bool leftPodInEar READ isLeftPodInEar NOTIFY primaryChanged)
|
Q_PROPERTY(bool leftPodInEar READ isLeftPodInEar NOTIFY primaryChanged)
|
||||||
Q_PROPERTY(bool rightPodInEar READ isRightPodInEar NOTIFY primaryChanged)
|
Q_PROPERTY(bool rightPodInEar READ isRightPodInEar NOTIFY primaryChanged)
|
||||||
|
Q_PROPERTY(bool airpodsConnected READ areAirpodsConnected NOTIFY airPodsStatusChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AirPodsTrayApp(bool debugMode)
|
AirPodsTrayApp(bool debugMode)
|
||||||
@@ -64,13 +65,8 @@ public:
|
|||||||
|
|
||||||
CrossDevice.isEnabled = loadCrossDeviceEnabled();
|
CrossDevice.isEnabled = loadCrossDeviceEnabled();
|
||||||
|
|
||||||
discoveryAgent = new QBluetoothDeviceDiscoveryAgent();
|
monitor->checkAlreadyConnectedDevices();
|
||||||
discoveryAgent->setLowEnergyDiscoveryTimeout(15000);
|
LOG_INFO("AirPodsTrayApp initialized");
|
||||||
|
|
||||||
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &AirPodsTrayApp::onDeviceDiscovered);
|
|
||||||
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &AirPodsTrayApp::onDiscoveryFinished);
|
|
||||||
discoveryAgent->start();
|
|
||||||
LOG_INFO("AirPodsTrayApp initialized and started device discovery");
|
|
||||||
|
|
||||||
QBluetoothLocalDevice localDevice;
|
QBluetoothLocalDevice localDevice;
|
||||||
|
|
||||||
@@ -92,7 +88,6 @@ public:
|
|||||||
|
|
||||||
delete trayIcon;
|
delete trayIcon;
|
||||||
delete trayMenu;
|
delete trayMenu;
|
||||||
delete discoveryAgent;
|
|
||||||
delete socket;
|
delete socket;
|
||||||
delete phoneSocket;
|
delete phoneSocket;
|
||||||
}
|
}
|
||||||
@@ -122,6 +117,7 @@ public:
|
|||||||
return m_secoundaryInEar;
|
return m_secoundaryInEar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool areAirpodsConnected() const { return socket && socket->isOpen() && socket->state() == QBluetoothSocket::SocketState::ConnectedState; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool debugMode;
|
bool debugMode;
|
||||||
@@ -140,6 +136,10 @@ private:
|
|||||||
|
|
||||||
void notifyAndroidDevice()
|
void notifyAndroidDevice()
|
||||||
{
|
{
|
||||||
|
if (!CrossDevice.isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (phoneSocket && phoneSocket->isOpen())
|
if (phoneSocket && phoneSocket->isOpen())
|
||||||
{
|
{
|
||||||
phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION);
|
phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION);
|
||||||
@@ -163,21 +163,6 @@ public slots:
|
|||||||
connectToDevice(device);
|
connectToDevice(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showAvailableDevices() {
|
|
||||||
LOG_INFO("Showing available devices");
|
|
||||||
QStringList devices;
|
|
||||||
const QList<QBluetoothDeviceInfo> discoveredDevices = discoveryAgent->discoveredDevices();
|
|
||||||
for (const QBluetoothDeviceInfo &device : discoveredDevices) {
|
|
||||||
devices << device.address().toString() + " - " + device.name();
|
|
||||||
}
|
|
||||||
bool ok;
|
|
||||||
QString selectedDevice = QInputDialog::getItem(nullptr, "Select Device", "Devices:", devices, 0, false, &ok);
|
|
||||||
if (ok && !selectedDevice.isEmpty()) {
|
|
||||||
QString address = selectedDevice.split(" - ").first();
|
|
||||||
connectToDevice(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setNoiseControlMode(NoiseControlMode mode)
|
void setNoiseControlMode(NoiseControlMode mode)
|
||||||
{
|
{
|
||||||
LOG_INFO("Setting noise control mode to: " << mode);
|
LOG_INFO("Setting noise control mode to: " << mode);
|
||||||
@@ -308,46 +293,12 @@ private slots:
|
|||||||
|
|
||||||
void sendHandshake() {
|
void sendHandshake() {
|
||||||
LOG_INFO("Connected to device, sending initial packets");
|
LOG_INFO("Connected to device, sending initial packets");
|
||||||
discoveryAgent->stop();
|
|
||||||
writePacketToSocket(AirPodsPackets::Connection::HANDSHAKE, "Handshake packet written: ");
|
writePacketToSocket(AirPodsPackets::Connection::HANDSHAKE, "Handshake packet written: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDeviceDiscovered(const QBluetoothDeviceInfo &device) {
|
void bluezDeviceConnected(const QString &address, const QString &name)
|
||||||
QByteArray manufacturerData = device.manufacturerData(MANUFACTURER_ID);
|
|
||||||
if (manufacturerData.startsWith(MANUFACTURER_DATA)) {
|
|
||||||
LOG_INFO("Detected AirPods via BLE manufacturer data");
|
|
||||||
connectToDevice(device.address().toString());
|
|
||||||
}
|
|
||||||
LOG_INFO("Device discovered: " << device.name() << " (" << device.address().toString() << ")");
|
|
||||||
if (isAirPodsDevice(device)) {
|
|
||||||
LOG_DEBUG("Found AirPods device: " + device.name());
|
|
||||||
connectToDevice(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDiscoveryFinished() {
|
|
||||||
LOG_INFO("Device discovery finished");
|
|
||||||
discoveryAgent->start();
|
|
||||||
const QList<QBluetoothDeviceInfo> discoveredDevices = discoveryAgent->discoveredDevices();
|
|
||||||
for (const QBluetoothDeviceInfo &device : discoveredDevices) {
|
|
||||||
if (isAirPodsDevice(device)) {
|
|
||||||
connectToDevice(device);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG_WARN("No device with the specified UUID found");
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDeviceConnected(const QBluetoothAddress &address) {
|
|
||||||
LOG_INFO("Device connected: " << address.toString());
|
|
||||||
QBluetoothDeviceInfo device(address, "", 0);
|
|
||||||
if (isAirPodsDevice(device)) {
|
|
||||||
connectToDevice(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void bluezDeviceConnected(const QString &address)
|
|
||||||
{
|
{
|
||||||
QBluetoothDeviceInfo device(QBluetoothAddress(address), "", 0);
|
QBluetoothDeviceInfo device(QBluetoothAddress(address), name, 0);
|
||||||
connectToDevice(device);
|
connectToDevice(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,22 +317,9 @@ private slots:
|
|||||||
LOG_DEBUG("AIRPODS_DISCONNECTED packet written: " << AirPodsPackets::Connection::AIRPODS_DISCONNECTED.toHex());
|
LOG_DEBUG("AIRPODS_DISCONNECTED packet written: " << AirPodsPackets::Connection::AIRPODS_DISCONNECTED.toHex());
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaController->pause(); // Since the device is deconnected, we don't know if it was the active output device. Pause to be safe
|
|
||||||
discoveryAgent->start();
|
|
||||||
|
|
||||||
// Show system notification
|
|
||||||
trayManager->showNotification(
|
|
||||||
tr("AirPods Disconnected"),
|
|
||||||
tr("Your AirPods have been disconnected"));
|
|
||||||
}
|
|
||||||
void bluezDeviceDisconnected(const QString &address)
|
|
||||||
{
|
|
||||||
if (address == connectedDeviceMacAddress.replace("_", ":"))
|
|
||||||
{
|
|
||||||
onDeviceDisconnected(QBluetoothAddress(address));
|
|
||||||
|
|
||||||
// Clear the device name and model
|
// Clear the device name and model
|
||||||
m_deviceName.clear();
|
m_deviceName.clear();
|
||||||
|
connectedDeviceMacAddress.clear();
|
||||||
m_model = AirPodsModel::Unknown;
|
m_model = AirPodsModel::Unknown;
|
||||||
emit deviceNameChanged(m_deviceName);
|
emit deviceNameChanged(m_deviceName);
|
||||||
emit modelChanged();
|
emit modelChanged();
|
||||||
@@ -401,7 +339,21 @@ private slots:
|
|||||||
// Reset noise control mode
|
// Reset noise control mode
|
||||||
m_noiseControlMode = NoiseControlMode::Off;
|
m_noiseControlMode = NoiseControlMode::Off;
|
||||||
emit noiseControlModeChanged(m_noiseControlMode);
|
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();
|
||||||
|
|
||||||
|
// Show system notification
|
||||||
|
trayManager->showNotification(
|
||||||
|
tr("AirPods Disconnected"),
|
||||||
|
tr("Your AirPods have been disconnected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bluezDeviceDisconnected(const QString &address, const QString &name)
|
||||||
|
{
|
||||||
|
if (address == connectedDeviceMacAddress.replace("_", ":"))
|
||||||
|
{
|
||||||
|
onDeviceDisconnected(QBluetoothAddress(address)); }
|
||||||
else {
|
else {
|
||||||
LOG_WARN("Disconnected device does not match connected device: " << address << " != " << connectedDeviceMacAddress);
|
LOG_WARN("Disconnected device does not match connected device: " << address << " != " << connectedDeviceMacAddress);
|
||||||
}
|
}
|
||||||
@@ -484,53 +436,74 @@ private slots:
|
|||||||
LOG_INFO("Trailing Byte: " << trailingByte);
|
LOG_INFO("Trailing Byte: " << trailingByte);
|
||||||
}
|
}
|
||||||
|
|
||||||
void connectToDevice(const QBluetoothDeviceInfo &device) {
|
QString getEarStatus(char value)
|
||||||
if (socket && socket->isOpen() && socket->peerAddress() == device.address()) {
|
{
|
||||||
|
return (value == 0x00) ? "In Ear" : (value == 0x01) ? "Out of Ear"
|
||||||
|
: "In case";
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectToDevice(const QBluetoothDeviceInfo &device)
|
||||||
|
{
|
||||||
|
if (socket && socket->isOpen() && socket->peerAddress() == device.address())
|
||||||
|
{
|
||||||
LOG_INFO("Already connected to the device: " << device.name());
|
LOG_INFO("Already connected to the device: " << device.name());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("Connecting to device: " << device.name());
|
LOG_INFO("Connecting to device: " << device.name());
|
||||||
QBluetoothSocket *localSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol);
|
|
||||||
connect(localSocket, &QBluetoothSocket::connected, this, [this, localSocket]() {
|
|
||||||
// Start periodic magic pairing attempts
|
|
||||||
QTimer *magicPairingTimer = new QTimer(this);
|
|
||||||
connect(magicPairingTimer, &QTimer::timeout, this, [this, magicPairingTimer]() {
|
|
||||||
if (m_magicAccIRK.isEmpty() || m_magicAccEncKey.isEmpty()) {
|
|
||||||
initiateMagicPairing();
|
|
||||||
} else {
|
|
||||||
magicPairingTimer->stop();
|
|
||||||
magicPairingTimer->deleteLater();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
magicPairingTimer->start(500);
|
|
||||||
|
|
||||||
connect(localSocket, &QBluetoothSocket::readyRead, this, [this, localSocket]() {
|
// Clean up any existing socket
|
||||||
|
if (socket)
|
||||||
|
{
|
||||||
|
socket->close();
|
||||||
|
socket->deleteLater();
|
||||||
|
socket = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QBluetoothSocket *localSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol);
|
||||||
|
socket = localSocket;
|
||||||
|
|
||||||
|
// Connection handler
|
||||||
|
auto handleConnection = [this, localSocket]()
|
||||||
|
{
|
||||||
|
connect(localSocket, &QBluetoothSocket::readyRead, this, [this, localSocket]()
|
||||||
|
{
|
||||||
QByteArray data = localSocket->readAll();
|
QByteArray data = localSocket->readAll();
|
||||||
QMetaObject::invokeMethod(this, "parseData", Qt::QueuedConnection, Q_ARG(QByteArray, data));
|
QMetaObject::invokeMethod(this, "parseData", Qt::QueuedConnection, Q_ARG(QByteArray, data));
|
||||||
QMetaObject::invokeMethod(this, "relayPacketToPhone", Qt::QueuedConnection, Q_ARG(QByteArray, data));
|
QMetaObject::invokeMethod(this, "relayPacketToPhone", Qt::QueuedConnection, Q_ARG(QByteArray, data)); });
|
||||||
});
|
|
||||||
|
|
||||||
sendHandshake();
|
sendHandshake();
|
||||||
});
|
};
|
||||||
|
|
||||||
connect(localSocket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::errorOccurred), this, [this, localSocket](QBluetoothSocket::SocketError error) {
|
// Error handler with retry
|
||||||
|
auto handleError = [this, device, localSocket](QBluetoothSocket::SocketError error)
|
||||||
|
{
|
||||||
LOG_ERROR("Socket error: " << error << ", " << localSocket->errorString());
|
LOG_ERROR("Socket error: " << error << ", " << localSocket->errorString());
|
||||||
});
|
|
||||||
|
|
||||||
socket = localSocket;
|
static int retryCount = 0;
|
||||||
|
if (retryCount < 3)
|
||||||
|
{
|
||||||
|
retryCount++;
|
||||||
|
LOG_INFO("Retrying connection (attempt " << retryCount << ")");
|
||||||
|
QTimer::singleShot(1500, this, [this, device]()
|
||||||
|
{ connectToDevice(device); });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_ERROR("Failed to connect after 3 attempts");
|
||||||
|
retryCount = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
connect(localSocket, &QBluetoothSocket::connected, this, handleConnection);
|
||||||
|
connect(localSocket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::errorOccurred),
|
||||||
|
this, handleError);
|
||||||
|
|
||||||
localSocket->connectToService(device.address(), QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"));
|
localSocket->connectToService(device.address(), QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"));
|
||||||
connectedDeviceMacAddress = device.address().toString().replace(":", "_");
|
connectedDeviceMacAddress = device.address().toString().replace(":", "_");
|
||||||
mediaController->setConnectedDeviceMacAddress(connectedDeviceMacAddress);
|
mediaController->setConnectedDeviceMacAddress(connectedDeviceMacAddress);
|
||||||
notifyAndroidDevice();
|
notifyAndroidDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString getEarStatus(char value)
|
|
||||||
{
|
|
||||||
return (value == 0x00) ? "In Ear" : (value == 0x01) ? "Out of Ear"
|
|
||||||
: "In case";
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseData(const QByteArray &data)
|
void parseData(const QByteArray &data)
|
||||||
{
|
{
|
||||||
LOG_DEBUG("Received: " << data.toHex());
|
LOG_DEBUG("Received: " << data.toHex());
|
||||||
@@ -539,7 +512,7 @@ private slots:
|
|||||||
{
|
{
|
||||||
writePacketToSocket(AirPodsPackets::Connection::SET_SPECIFIC_FEATURES, "Set specific features packet written: ");
|
writePacketToSocket(AirPodsPackets::Connection::SET_SPECIFIC_FEATURES, "Set specific features packet written: ");
|
||||||
}
|
}
|
||||||
if (data.startsWith(AirPodsPackets::Parse::FEATURES_ACK))
|
else if (data.startsWith(AirPodsPackets::Parse::FEATURES_ACK))
|
||||||
{
|
{
|
||||||
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
|
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
|
||||||
|
|
||||||
@@ -626,6 +599,8 @@ private slots:
|
|||||||
else if (data.startsWith(AirPodsPackets::Parse::METADATA))
|
else if (data.startsWith(AirPodsPackets::Parse::METADATA))
|
||||||
{
|
{
|
||||||
parseMetadata(data);
|
parseMetadata(data);
|
||||||
|
initiateMagicPairing();
|
||||||
|
emit airPodsStatusChanged();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -754,6 +729,8 @@ private slots:
|
|||||||
|
|
||||||
void sendDisconnectRequestToAndroid()
|
void sendDisconnectRequestToAndroid()
|
||||||
{
|
{
|
||||||
|
if (!CrossDevice.isEnabled) return;
|
||||||
|
|
||||||
if (phoneSocket && phoneSocket->isOpen())
|
if (phoneSocket && phoneSocket->isOpen())
|
||||||
{
|
{
|
||||||
phoneSocket->write(AirPodsPackets::Phone::DISCONNECT_REQUEST);
|
phoneSocket->write(AirPodsPackets::Phone::DISCONNECT_REQUEST);
|
||||||
@@ -841,11 +818,11 @@ signals:
|
|||||||
void deviceNameChanged(const QString &name);
|
void deviceNameChanged(const QString &name);
|
||||||
void modelChanged();
|
void modelChanged();
|
||||||
void primaryChanged();
|
void primaryChanged();
|
||||||
|
void airPodsStatusChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSystemTrayIcon *trayIcon;
|
QSystemTrayIcon *trayIcon;
|
||||||
QMenu *trayMenu;
|
QMenu *trayMenu;
|
||||||
QBluetoothDeviceDiscoveryAgent *discoveryAgent;
|
|
||||||
QBluetoothSocket *socket = nullptr;
|
QBluetoothSocket *socket = nullptr;
|
||||||
QBluetoothSocket *phoneSocket = nullptr;
|
QBluetoothSocket *phoneSocket = nullptr;
|
||||||
QString connectedDeviceMacAddress;
|
QString connectedDeviceMacAddress;
|
||||||
|
|||||||
Reference in New Issue
Block a user