mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-10 19:52:24 +00:00
[Linux] CA state parsing, robuster handshake, persistent window (#94)
* [Linux] Don't quit app when closing window * Add magic pairing functionality * BLE: Allow selecting text * Parse CA state from airpods * Add ability to disable cross-device * More robust handshake/notification request
This commit is contained in:
@@ -2,11 +2,16 @@ import QtQuick 2.15
|
|||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
|
id: mainWindow
|
||||||
visible: true
|
visible: true
|
||||||
width: 400
|
width: 400
|
||||||
height: 300
|
height: 300
|
||||||
title: "AirPods Settings"
|
title: "AirPods Settings"
|
||||||
|
|
||||||
|
onClosing: function(event) {
|
||||||
|
mainWindow.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|||||||
@@ -38,10 +38,27 @@ namespace AirPodsPackets
|
|||||||
// Conversational Awareness Packets
|
// Conversational Awareness Packets
|
||||||
namespace ConversationalAwareness
|
namespace ConversationalAwareness
|
||||||
{
|
{
|
||||||
static const QByteArray HEADER = QByteArray::fromHex("04000400090028"); // Added for parsing
|
static const QByteArray HEADER = QByteArray::fromHex("04000400090028"); // For command/status
|
||||||
static const QByteArray ENABLED = HEADER + QByteArray::fromHex("01000000");
|
static const QByteArray ENABLED = HEADER + QByteArray::fromHex("01000000"); // Command to enable
|
||||||
static const QByteArray DISABLED = HEADER + QByteArray::fromHex("02000000");
|
static const QByteArray DISABLED = HEADER + QByteArray::fromHex("02000000"); // Command to disable
|
||||||
static const QByteArray DATA_HEADER = QByteArray::fromHex("040004004B00020001"); // For received data
|
static const QByteArray DATA_HEADER = QByteArray::fromHex("040004004B00020001"); // For received speech level data
|
||||||
|
|
||||||
|
static std::optional<bool> parseCAState(const QByteArray &data)
|
||||||
|
{
|
||||||
|
// Extract the status byte (index 7)
|
||||||
|
quint8 statusByte = static_cast<quint8>(data.at(HEADER.size())); // HEADER.size() is 7
|
||||||
|
|
||||||
|
// Interpret the status byte
|
||||||
|
switch (statusByte)
|
||||||
|
{
|
||||||
|
case 0x01: // Enabled
|
||||||
|
return true;
|
||||||
|
case 0x02: // Disabled
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection Packets
|
// Connection Packets
|
||||||
@@ -88,12 +105,91 @@ namespace AirPodsPackets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace MagicPairing {
|
||||||
|
static const QByteArray REQUEST_MAGIC_CLOUD_KEYS = QByteArray::fromHex("0400040030000500");
|
||||||
|
static const QByteArray MAGIC_CLOUD_KEYS_HEADER = QByteArray::fromHex("04000400310002");
|
||||||
|
|
||||||
|
struct MagicCloudKeys {
|
||||||
|
QByteArray magicAccIRK; // 16 bytes
|
||||||
|
QByteArray magicAccEncKey; // 16 bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
inline MagicCloudKeys parseMagicCloudKeysPacket(const QByteArray &data)
|
||||||
|
{
|
||||||
|
MagicCloudKeys keys;
|
||||||
|
|
||||||
|
// Expected size: header (7 bytes) + (1 (tag) + 2 (length) + 1 (reserved) + 16 (value)) * 2 = 47 bytes.
|
||||||
|
if (data.size() < 47)
|
||||||
|
{
|
||||||
|
return keys; // or handle error as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check header
|
||||||
|
if (!data.startsWith(MAGIC_CLOUD_KEYS_HEADER))
|
||||||
|
{
|
||||||
|
return keys; // header mismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = MAGIC_CLOUD_KEYS_HEADER.size(); // Start after header (index 7)
|
||||||
|
|
||||||
|
// --- TLV Block 1 (MagicAccIRK) ---
|
||||||
|
// Tag should be 0x01
|
||||||
|
if (static_cast<quint8>(data.at(index)) != 0x01)
|
||||||
|
{
|
||||||
|
return keys; // unexpected tag
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
// Read length (2 bytes, big-endian)
|
||||||
|
quint16 len1 = (static_cast<quint8>(data.at(index)) << 8) | static_cast<quint8>(data.at(index + 1));
|
||||||
|
if (len1 != 16)
|
||||||
|
{
|
||||||
|
return keys; // invalid length
|
||||||
|
}
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
// Skip reserved byte
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
// Extract MagicAccIRK (16 bytes)
|
||||||
|
keys.magicAccIRK = data.mid(index, 16);
|
||||||
|
index += 16;
|
||||||
|
|
||||||
|
// --- TLV Block 2 (MagicAccEncKey) ---
|
||||||
|
// Tag should be 0x04
|
||||||
|
if (static_cast<quint8>(data.at(index)) != 0x04)
|
||||||
|
{
|
||||||
|
return keys; // unexpected tag
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
// Read length (2 bytes, big-endian)
|
||||||
|
quint16 len2 = (static_cast<quint8>(data.at(index)) << 8) | static_cast<quint8>(data.at(index + 1));
|
||||||
|
if (len2 != 16)
|
||||||
|
{
|
||||||
|
return keys; // invalid length
|
||||||
|
}
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
// Skip reserved byte
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
// Extract MagicAccEncKey (16 bytes)
|
||||||
|
keys.magicAccEncKey = data.mid(index, 16);
|
||||||
|
index += 16;
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parsing Headers
|
// Parsing Headers
|
||||||
namespace Parse
|
namespace Parse
|
||||||
{
|
{
|
||||||
static const QByteArray EAR_DETECTION = QByteArray::fromHex("040004000600");
|
static const QByteArray EAR_DETECTION = QByteArray::fromHex("040004000600");
|
||||||
static const QByteArray BATTERY_STATUS = QByteArray::fromHex("040004000400");
|
static const QByteArray BATTERY_STATUS = QByteArray::fromHex("040004000400");
|
||||||
static const QByteArray METADATA = QByteArray::fromHex("040004001d");
|
static const QByteArray METADATA = QByteArray::fromHex("040004001d");
|
||||||
|
static const QByteArray HANDSHAKE_ACK = QByteArray::fromHex("01000400");
|
||||||
|
static const QByteArray FEATURES_ACK = QByteArray::fromHex("040004002b00"); // Note: Only tested with airpods pro 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ BleScanner::BleScanner(QWidget *parent) : QMainWindow(parent)
|
|||||||
detailsLayout->addWidget(new QLabel("Raw Data:"), 7, 0);
|
detailsLayout->addWidget(new QLabel("Raw Data:"), 7, 0);
|
||||||
rawDataLabel = new QLabel(this);
|
rawDataLabel = new QLabel(this);
|
||||||
rawDataLabel->setWordWrap(true);
|
rawDataLabel->setWordWrap(true);
|
||||||
|
rawDataLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
detailsLayout->addWidget(rawDataLabel, 7, 1, 1, 2);
|
detailsLayout->addWidget(rawDataLabel, 7, 1, 1, 2);
|
||||||
|
|
||||||
// New Rows for Additional Info
|
// New Rows for Additional Info
|
||||||
|
|||||||
139
linux/main.cpp
139
linux/main.cpp
@@ -57,8 +57,7 @@ public:
|
|||||||
|
|
||||||
connect(m_battery, &Battery::primaryChanged, this, &AirPodsTrayApp::primaryChanged);
|
connect(m_battery, &Battery::primaryChanged, this, &AirPodsTrayApp::primaryChanged);
|
||||||
|
|
||||||
// load conversational awareness state
|
CrossDevice.isEnabled = loadCrossDeviceEnabled();
|
||||||
setConversationalAwareness(loadConversationalAwarenessState());
|
|
||||||
|
|
||||||
discoveryAgent = new QBluetoothDeviceDiscoveryAgent();
|
discoveryAgent = new QBluetoothDeviceDiscoveryAgent();
|
||||||
discoveryAgent->setLowEnergyDiscoveryTimeout(15000);
|
discoveryAgent->setLowEnergyDiscoveryTimeout(15000);
|
||||||
@@ -69,8 +68,6 @@ public:
|
|||||||
LOG_INFO("AirPodsTrayApp initialized and started device discovery");
|
LOG_INFO("AirPodsTrayApp initialized and started device discovery");
|
||||||
|
|
||||||
QBluetoothLocalDevice localDevice;
|
QBluetoothLocalDevice localDevice;
|
||||||
connect(&localDevice, &QBluetoothLocalDevice::deviceConnected, this, &AirPodsTrayApp::onDeviceConnected);
|
|
||||||
connect(&localDevice, &QBluetoothLocalDevice::deviceDisconnected, this, &AirPodsTrayApp::onDeviceDisconnected);
|
|
||||||
|
|
||||||
const QList<QBluetoothAddress> connectedDevices = localDevice.connectedDevices();
|
const QList<QBluetoothAddress> connectedDevices = localDevice.connectedDevices();
|
||||||
for (const QBluetoothAddress &address : connectedDevices) {
|
for (const QBluetoothAddress &address : connectedDevices) {
|
||||||
@@ -80,7 +77,6 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connect(phoneSocket, &QBluetoothSocket::readyRead, this, &AirPodsTrayApp::onPhoneDataReceived);
|
|
||||||
|
|
||||||
QDBusInterface iface("org.bluez", "/org/bluez", "org.bluez.Adapter1");
|
QDBusInterface iface("org.bluez", "/org/bluez", "org.bluez.Adapter1");
|
||||||
QDBusReply<QVariant> reply = iface.call("GetServiceRecords", QString::fromUtf8("74ec2172-0bad-4d01-8f77-997b2be0722a"));
|
QDBusReply<QVariant> reply = iface.call("GetServiceRecords", QString::fromUtf8("74ec2172-0bad-4d01-8f77-997b2be0722a"));
|
||||||
@@ -96,6 +92,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~AirPodsTrayApp() {
|
~AirPodsTrayApp() {
|
||||||
|
saveCrossDeviceEnabled();
|
||||||
|
|
||||||
delete trayIcon;
|
delete trayIcon;
|
||||||
delete trayMenu;
|
delete trayMenu;
|
||||||
delete discoveryAgent;
|
delete discoveryAgent;
|
||||||
@@ -136,6 +134,7 @@ private:
|
|||||||
bool isConnectedLocally = false;
|
bool isConnectedLocally = false;
|
||||||
struct {
|
struct {
|
||||||
bool isAvailable = true;
|
bool isAvailable = true;
|
||||||
|
bool isEnabled = true; // Ability to disable the feature
|
||||||
} CrossDevice;
|
} CrossDevice;
|
||||||
|
|
||||||
void initializeDBus() {
|
void initializeDBus() {
|
||||||
@@ -287,7 +286,17 @@ public slots:
|
|||||||
writePacketToSocket(packet, "Conversational awareness packet written: ");
|
writePacketToSocket(packet, "Conversational awareness packet written: ");
|
||||||
m_conversationalAwareness = enabled;
|
m_conversationalAwareness = enabled;
|
||||||
emit conversationalAwarenessChanged(enabled);
|
emit conversationalAwarenessChanged(enabled);
|
||||||
saveConversationalAwarenessState();
|
}
|
||||||
|
|
||||||
|
void initiateMagicPairing()
|
||||||
|
{
|
||||||
|
if (!socket || !socket->isOpen())
|
||||||
|
{
|
||||||
|
LOG_ERROR("Socket nicht offen, Magic Pairing kann nicht gestartet werden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
writePacketToSocket(AirPodsPackets::MagicPairing::REQUEST_MAGIC_CLOUD_KEYS, "Magic Pairing packet written: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAdaptiveNoiseLevel(int level)
|
void setAdaptiveNoiseLevel(int level)
|
||||||
@@ -348,16 +357,16 @@ public slots:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loadConversationalAwarenessState()
|
bool loadCrossDeviceEnabled()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
return settings.value("conversationalAwareness", false).toBool();
|
return settings.value("crossdevice/enabled", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveConversationalAwarenessState()
|
void saveCrossDeviceEnabled()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.setValue("conversationalAwareness", m_conversationalAwareness);
|
settings.setValue("crossdevice/enabled", CrossDevice.isEnabled);
|
||||||
settings.sync();
|
settings.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,6 +383,12 @@ private slots:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendHandshake() {
|
||||||
|
LOG_INFO("Connected to device, sending initial packets");
|
||||||
|
discoveryAgent->stop();
|
||||||
|
writePacketToSocket(AirPodsPackets::Connection::HANDSHAKE, "Handshake packet written: ");
|
||||||
|
}
|
||||||
|
|
||||||
void onDeviceDiscovered(const QBluetoothDeviceInfo &device) {
|
void onDeviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||||
QByteArray manufacturerData = device.manufacturerData(MANUFACTURER_ID);
|
QByteArray manufacturerData = device.manufacturerData(MANUFACTURER_ID);
|
||||||
if (manufacturerData.startsWith(MANUFACTURER_DATA)) {
|
if (manufacturerData.startsWith(MANUFACTURER_DATA)) {
|
||||||
@@ -509,38 +524,21 @@ private slots:
|
|||||||
|
|
||||||
LOG_INFO("Connecting to device: " << device.name());
|
LOG_INFO("Connecting to device: " << device.name());
|
||||||
QBluetoothSocket *localSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol);
|
QBluetoothSocket *localSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol);
|
||||||
|
connect(localSocket, &QBluetoothSocket::disconnected, this, [this, localSocket]() {
|
||||||
|
onDeviceDisconnected(localSocket->peerAddress());
|
||||||
|
});
|
||||||
connect(localSocket, &QBluetoothSocket::connected, this, [this, localSocket]() {
|
connect(localSocket, &QBluetoothSocket::connected, this, [this, localSocket]() {
|
||||||
LOG_INFO("Connected to device, sending initial packets");
|
// Start periodic magic pairing attempts
|
||||||
discoveryAgent->stop();
|
QTimer *magicPairingTimer = new QTimer(this);
|
||||||
|
connect(magicPairingTimer, &QTimer::timeout, this, [this, magicPairingTimer]() {
|
||||||
QByteArray handshakePacket = AirPodsPackets::Connection::HANDSHAKE;
|
if (m_magicAccIRK.isEmpty() || m_magicAccEncKey.isEmpty()) {
|
||||||
QByteArray setSpecificFeaturesPacket = AirPodsPackets::Connection::SET_SPECIFIC_FEATURES;
|
initiateMagicPairing();
|
||||||
QByteArray requestNotificationsPacket = AirPodsPackets::Connection::REQUEST_NOTIFICATIONS;
|
} else {
|
||||||
|
magicPairingTimer->stop();
|
||||||
qint64 bytesWritten = localSocket->write(handshakePacket);
|
magicPairingTimer->deleteLater();
|
||||||
LOG_DEBUG("Handshake packet written: " << handshakePacket.toHex() << ", bytes written: " << bytesWritten);
|
|
||||||
localSocket->write(setSpecificFeaturesPacket);
|
|
||||||
LOG_DEBUG("Set specific features packet written: " << setSpecificFeaturesPacket.toHex());
|
|
||||||
localSocket->write(requestNotificationsPacket);
|
|
||||||
LOG_DEBUG("Request notifications packet written: " << requestNotificationsPacket.toHex());
|
|
||||||
connect(localSocket, &QBluetoothSocket::bytesWritten, this, [this, localSocket, setSpecificFeaturesPacket, requestNotificationsPacket](qint64 bytes) {
|
|
||||||
LOG_INFO("Bytes written: " << bytes);
|
|
||||||
if (bytes > 0) {
|
|
||||||
static int step = 0;
|
|
||||||
switch (step) {
|
|
||||||
case 0:
|
|
||||||
localSocket->write(setSpecificFeaturesPacket);
|
|
||||||
LOG_DEBUG("Set specific features packet written: " << setSpecificFeaturesPacket.toHex());
|
|
||||||
step++;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
localSocket->write(requestNotificationsPacket);
|
|
||||||
LOG_DEBUG("Request notifications packet written: " << requestNotificationsPacket.toHex());
|
|
||||||
step++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
magicPairingTimer->start(500);
|
||||||
|
|
||||||
connect(localSocket, &QBluetoothSocket::readyRead, this, [this, localSocket]() {
|
connect(localSocket, &QBluetoothSocket::readyRead, this, [this, localSocket]() {
|
||||||
QByteArray data = localSocket->readAll();
|
QByteArray data = localSocket->readAll();
|
||||||
@@ -548,24 +546,15 @@ private slots:
|
|||||||
QMetaObject::invokeMethod(this, "relayPacketToPhone", Qt::QueuedConnection, Q_ARG(QByteArray, data));
|
QMetaObject::invokeMethod(this, "relayPacketToPhone", Qt::QueuedConnection, Q_ARG(QByteArray, data));
|
||||||
});
|
});
|
||||||
|
|
||||||
QTimer::singleShot(500, this, [localSocket, setSpecificFeaturesPacket, requestNotificationsPacket]() {
|
sendHandshake();
|
||||||
if (localSocket->isOpen()) {
|
|
||||||
localSocket->write(setSpecificFeaturesPacket);
|
|
||||||
LOG_DEBUG("Resent set specific features packet: " << setSpecificFeaturesPacket.toHex());
|
|
||||||
localSocket->write(requestNotificationsPacket);
|
|
||||||
LOG_DEBUG("Resent request notifications packet: " << requestNotificationsPacket.toHex());
|
|
||||||
} else {
|
|
||||||
LOG_WARN("Socket is not open, cannot resend packets");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(localSocket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::errorOccurred), this, [this, localSocket](QBluetoothSocket::SocketError error) {
|
connect(localSocket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::errorOccurred), this, [this, localSocket](QBluetoothSocket::SocketError error) {
|
||||||
LOG_ERROR("Socket error: " << error << ", " << localSocket->errorString());
|
LOG_ERROR("Socket error: " << error << ", " << localSocket->errorString());
|
||||||
});
|
});
|
||||||
|
|
||||||
localSocket->connectToService(device.address(), QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"));
|
|
||||||
socket = localSocket;
|
socket = localSocket;
|
||||||
|
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();
|
||||||
@@ -581,8 +570,45 @@ private slots:
|
|||||||
{
|
{
|
||||||
LOG_DEBUG("Received: " << data.toHex());
|
LOG_DEBUG("Received: " << data.toHex());
|
||||||
|
|
||||||
|
if (data.startsWith(AirPodsPackets::Parse::HANDSHAKE_ACK))
|
||||||
|
{
|
||||||
|
writePacketToSocket(AirPodsPackets::Connection::SET_SPECIFIC_FEATURES, "Set specific features packet written: ");
|
||||||
|
}
|
||||||
|
if (data.startsWith(AirPodsPackets::Parse::FEATURES_ACK))
|
||||||
|
{
|
||||||
|
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
|
||||||
|
|
||||||
|
QTimer::singleShot(2000, this, [this]() {
|
||||||
|
if (m_batteryStatus.isEmpty()) {
|
||||||
|
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Magic Cloud Keys Response
|
||||||
|
else if (data.startsWith(AirPodsPackets::MagicPairing::MAGIC_CLOUD_KEYS_HEADER))
|
||||||
|
{
|
||||||
|
auto keys = AirPodsPackets::MagicPairing::parseMagicCloudKeysPacket(data);
|
||||||
|
LOG_INFO("Received Magic Cloud Keys:");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
// Get CA state
|
||||||
|
else if (data.startsWith(AirPodsPackets::ConversationalAwareness::HEADER)) {
|
||||||
|
auto result = AirPodsPackets::ConversationalAwareness::parseCAState(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");
|
||||||
|
}
|
||||||
|
}
|
||||||
// Noise Control Mode
|
// Noise Control Mode
|
||||||
if (data.size() == 11 && data.startsWith(AirPodsPackets::NoiseControl::HEADER))
|
else if (data.size() == 11 && data.startsWith(AirPodsPackets::NoiseControl::HEADER))
|
||||||
{
|
{
|
||||||
quint8 rawMode = data[7] - 1; // Offset still needed due to protocol
|
quint8 rawMode = data[7] - 1; // Offset still needed due to protocol
|
||||||
if (rawMode >= (int)NoiseControlMode::MinValue && rawMode <= (int)NoiseControlMode::MaxValue)
|
if (rawMode >= (int)NoiseControlMode::MinValue && rawMode <= (int)NoiseControlMode::MaxValue)
|
||||||
@@ -643,6 +669,10 @@ private slots:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void connectToPhone() {
|
void connectToPhone() {
|
||||||
|
if (!CrossDevice.isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (phoneSocket && phoneSocket->isOpen()) {
|
if (phoneSocket && phoneSocket->isOpen()) {
|
||||||
LOG_INFO("Already connected to the phone");
|
LOG_INFO("Already connected to the phone");
|
||||||
return;
|
return;
|
||||||
@@ -671,6 +701,9 @@ private slots:
|
|||||||
|
|
||||||
void relayPacketToPhone(const QByteArray &packet)
|
void relayPacketToPhone(const QByteArray &packet)
|
||||||
{
|
{
|
||||||
|
if (!CrossDevice.isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (phoneSocket && phoneSocket->isOpen())
|
if (phoneSocket && phoneSocket->isOpen())
|
||||||
{
|
{
|
||||||
phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION + packet);
|
phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION + packet);
|
||||||
@@ -876,6 +909,7 @@ private:
|
|||||||
QByteArray lastEarDetectionStatus;
|
QByteArray lastEarDetectionStatus;
|
||||||
MediaController* mediaController;
|
MediaController* mediaController;
|
||||||
TrayIconManager *trayManager;
|
TrayIconManager *trayManager;
|
||||||
|
QSettings *settings;
|
||||||
|
|
||||||
QString m_batteryStatus;
|
QString m_batteryStatus;
|
||||||
QString m_earDetectionStatus;
|
QString m_earDetectionStatus;
|
||||||
@@ -887,10 +921,13 @@ private:
|
|||||||
AirPodsModel m_model = AirPodsModel::Unknown;
|
AirPodsModel m_model = AirPodsModel::Unknown;
|
||||||
bool m_primaryInEar = false;
|
bool m_primaryInEar = false;
|
||||||
bool m_secoundaryInEar = false;
|
bool m_secoundaryInEar = false;
|
||||||
|
QByteArray m_magicAccIRK;
|
||||||
|
QByteArray m_magicAccEncKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
app.setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
bool debugMode = false;
|
bool debugMode = false;
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
|||||||
Reference in New Issue
Block a user