mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-23 06:34:21 +00:00
merge the a11 fix with local
This commit is contained in:
@@ -1,9 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
android:sharedUserId="android.uid.system"
|
|
||||||
android:sharedUserMaxSdkVersion="32"
|
|
||||||
tools:targetApi="33">
|
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.telephony"
|
android:name="android.hardware.telephony"
|
||||||
@@ -34,6 +31,9 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
tools:ignore="ScopedStorage" />
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||||
|
android:maxSdkVersion="30" />
|
||||||
|
|
||||||
<protected-broadcast android:name="batterywidget.impl.action.update_bluetooth_data" />
|
<protected-broadcast android:name="batterywidget.impl.action.update_bluetooth_data" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalEncodingApi::class)
|
||||||
|
|
||||||
package me.kavishdevar.librepods
|
package me.kavishdevar.librepods
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
@@ -27,6 +29,7 @@ import android.content.Intent
|
|||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
@@ -113,6 +116,7 @@ import me.kavishdevar.librepods.ui.theme.LibrePodsTheme
|
|||||||
import me.kavishdevar.librepods.utils.AirPodsNotifications
|
import me.kavishdevar.librepods.utils.AirPodsNotifications
|
||||||
import me.kavishdevar.librepods.utils.CrossDevice
|
import me.kavishdevar.librepods.utils.CrossDevice
|
||||||
import me.kavishdevar.librepods.utils.RadareOffsetFinder
|
import me.kavishdevar.librepods.utils.RadareOffsetFinder
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
lateinit var serviceConnection: ServiceConnection
|
lateinit var serviceConnection: ServiceConnection
|
||||||
lateinit var connectionStatusReceiver: BroadcastReceiver
|
lateinit var connectionStatusReceiver: BroadcastReceiver
|
||||||
@@ -183,17 +187,30 @@ fun Main() {
|
|||||||
var canDrawOverlays by remember { mutableStateOf(Settings.canDrawOverlays(context)) }
|
var canDrawOverlays by remember { mutableStateOf(Settings.canDrawOverlays(context)) }
|
||||||
val overlaySkipped = remember { mutableStateOf(context.getSharedPreferences("settings", MODE_PRIVATE).getBoolean("overlay_permission_skipped", false)) }
|
val overlaySkipped = remember { mutableStateOf(context.getSharedPreferences("settings", MODE_PRIVATE).getBoolean("overlay_permission_skipped", false)) }
|
||||||
|
|
||||||
val permissionState = rememberMultiplePermissionsState(
|
val bluetoothPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
permissions = listOf(
|
listOf(
|
||||||
"android.permission.BLUETOOTH_CONNECT",
|
"android.permission.BLUETOOTH_CONNECT",
|
||||||
"android.permission.BLUETOOTH_SCAN",
|
"android.permission.BLUETOOTH_SCAN",
|
||||||
"android.permission.BLUETOOTH",
|
"android.permission.BLUETOOTH",
|
||||||
"android.permission.BLUETOOTH_ADMIN",
|
"android.permission.BLUETOOTH_ADMIN",
|
||||||
"android.permission.BLUETOOTH_ADVERTISE",
|
"android.permission.BLUETOOTH_ADVERTISE"
|
||||||
"android.permission.POST_NOTIFICATIONS",
|
|
||||||
"android.permission.READ_PHONE_STATE",
|
|
||||||
"android.permission.ANSWER_PHONE_CALLS",
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
listOf(
|
||||||
|
"android.permission.BLUETOOTH",
|
||||||
|
"android.permission.BLUETOOTH_ADMIN",
|
||||||
|
"android.permission.ACCESS_FINE_LOCATION"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val otherPermissions = listOf(
|
||||||
|
"android.permission.POST_NOTIFICATIONS",
|
||||||
|
"android.permission.READ_PHONE_STATE",
|
||||||
|
"android.permission.ANSWER_PHONE_CALLS"
|
||||||
|
)
|
||||||
|
val allPermissions = bluetoothPermissions + otherPermissions
|
||||||
|
|
||||||
|
val permissionState = rememberMultiplePermissionsState(
|
||||||
|
permissions = allPermissions
|
||||||
)
|
)
|
||||||
val airPodsService = remember { mutableStateOf<AirPodsService?>(null) }
|
val airPodsService = remember { mutableStateOf<AirPodsService?>(null) }
|
||||||
|
|
||||||
|
|||||||
69
linux/BasicControlCommand.hpp
Normal file
69
linux/BasicControlCommand.hpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
|
// Control Command Header
|
||||||
|
namespace ControlCommand
|
||||||
|
{
|
||||||
|
static const QByteArray HEADER = QByteArray::fromHex("040004000900");
|
||||||
|
|
||||||
|
// Helper function to create control command packets
|
||||||
|
static QByteArray createCommand(quint8 identifier, quint8 data1 = 0x00, quint8 data2 = 0x00,
|
||||||
|
quint8 data3 = 0x00, quint8 data4 = 0x00)
|
||||||
|
{
|
||||||
|
QByteArray packet = HEADER;
|
||||||
|
packet.append(static_cast<char>(identifier));
|
||||||
|
packet.append(static_cast<char>(data1));
|
||||||
|
packet.append(static_cast<char>(data2));
|
||||||
|
packet.append(static_cast<char>(data3));
|
||||||
|
packet.append(static_cast<char>(data4));
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse activated/not activated
|
||||||
|
inline std::optional<bool> parseActive(const QByteArray &data)
|
||||||
|
{
|
||||||
|
if (!data.startsWith(ControlCommand::HEADER))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
quint8 statusByte = static_cast<quint8>(data.at(7));
|
||||||
|
switch (statusByte)
|
||||||
|
{
|
||||||
|
case 0x01: // Enabled
|
||||||
|
return true;
|
||||||
|
case 0x02: // Disabled
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <quint8 CommandId>
|
||||||
|
struct BasicControlCommand
|
||||||
|
{
|
||||||
|
static constexpr quint8 ID = CommandId;
|
||||||
|
static const QByteArray HEADER;
|
||||||
|
|
||||||
|
static const QByteArray ENABLED;
|
||||||
|
static const QByteArray DISABLED;
|
||||||
|
|
||||||
|
static QByteArray create(quint8 data1 = 0x00, quint8 data2 = 0x00,
|
||||||
|
quint8 data3 = 0x00, quint8 data4 = 0x00)
|
||||||
|
{
|
||||||
|
return ControlCommand::createCommand(ID, data1, data2, data3, data4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basically returns the byte at the index 7
|
||||||
|
static std::optional<bool> parseState(const QByteArray &data)
|
||||||
|
{
|
||||||
|
return ControlCommand::parseActive(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <quint8 CommandId>
|
||||||
|
const QByteArray BasicControlCommand<CommandId>::HEADER = ControlCommand::HEADER + static_cast<char>(CommandId);
|
||||||
|
|
||||||
|
template <quint8 CommandId>
|
||||||
|
const QByteArray BasicControlCommand<CommandId>::ENABLED = create(0x01);
|
||||||
|
|
||||||
|
template <quint8 CommandId>
|
||||||
|
const QByteArray BasicControlCommand<CommandId>::DISABLED = create(0x02);
|
||||||
@@ -22,6 +22,7 @@ qt_add_executable(applinux
|
|||||||
BluetoothMonitor.cpp
|
BluetoothMonitor.cpp
|
||||||
BluetoothMonitor.h
|
BluetoothMonitor.h
|
||||||
autostartmanager.hpp
|
autostartmanager.hpp
|
||||||
|
BasicControlCommand.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(applinux
|
qt_add_qml_module(applinux
|
||||||
|
|||||||
@@ -226,6 +226,19 @@ ApplicationWindow {
|
|||||||
onCheckedChanged: airPodsTrayApp.notificationsEnabled = checked
|
onCheckedChanged: airPodsTrayApp.notificationsEnabled = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
visible: airPodsTrayApp.airpodsConnected
|
||||||
|
text: "One Bud ANC Mode"
|
||||||
|
checked: airPodsTrayApp.oneBudANCMode
|
||||||
|
onCheckedChanged: airPodsTrayApp.oneBudANCMode = checked
|
||||||
|
|
||||||
|
ToolTip {
|
||||||
|
visible: parent.hovered
|
||||||
|
text: "Enable ANC when using one AirPod\n(More noise reduction, but uses more battery)"
|
||||||
|
delay: 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
spacing: 5
|
spacing: 5
|
||||||
Label {
|
Label {
|
||||||
|
|||||||
@@ -3,18 +3,21 @@
|
|||||||
#define AIRPODS_PACKETS_H
|
#define AIRPODS_PACKETS_H
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "enums.h"
|
#include "enums.h"
|
||||||
|
#include "BasicControlCommand.hpp"
|
||||||
|
|
||||||
namespace AirPodsPackets
|
namespace AirPodsPackets
|
||||||
{
|
{
|
||||||
// Noise Control Mode Packets
|
// Noise Control Mode Packets
|
||||||
namespace NoiseControl
|
namespace NoiseControl
|
||||||
{
|
{
|
||||||
static const QByteArray HEADER = QByteArray::fromHex("0400040009000D"); // Added for parsing
|
static const QByteArray HEADER = ControlCommand::HEADER + 0x0D;
|
||||||
static const QByteArray OFF = HEADER + QByteArray::fromHex("01000000");
|
static const QByteArray OFF = ControlCommand::createCommand(0x0D, 0x01);
|
||||||
static const QByteArray NOISE_CANCELLATION = HEADER + QByteArray::fromHex("02000000");
|
static const QByteArray NOISE_CANCELLATION = ControlCommand::createCommand(0x0D, 0x02);
|
||||||
static const QByteArray TRANSPARENCY = HEADER + QByteArray::fromHex("03000000");
|
static const QByteArray TRANSPARENCY = ControlCommand::createCommand(0x0D, 0x03);
|
||||||
static const QByteArray ADAPTIVE = HEADER + QByteArray::fromHex("04000000");
|
static const QByteArray ADAPTIVE = ControlCommand::createCommand(0x0D, 0x04);
|
||||||
|
|
||||||
static const QByteArray getPacketForMode(AirpodsTrayApp::Enums::NoiseControlMode mode)
|
static const QByteArray getPacketForMode(AirpodsTrayApp::Enums::NoiseControlMode mode)
|
||||||
{
|
{
|
||||||
@@ -35,30 +38,71 @@ namespace AirPodsPackets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversational Awareness Packets
|
// One Bud ANC Mode
|
||||||
|
namespace OneBudANCMode
|
||||||
|
{
|
||||||
|
using Type = BasicControlCommand<0x1B>;
|
||||||
|
static const QByteArray ENABLED = Type::ENABLED;
|
||||||
|
static const QByteArray DISABLED = Type::DISABLED;
|
||||||
|
static const QByteArray HEADER = Type::HEADER;
|
||||||
|
inline std::optional<bool> parseState(const QByteArray &data) { return Type::parseState(data); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volume Swipe (partial - still needs custom interval function)
|
||||||
|
namespace VolumeSwipe
|
||||||
|
{
|
||||||
|
using Type = BasicControlCommand<0x25>;
|
||||||
|
static const QByteArray ENABLED = Type::ENABLED;
|
||||||
|
static const QByteArray DISABLED = Type::DISABLED;
|
||||||
|
static const QByteArray HEADER = Type::HEADER;
|
||||||
|
inline std::optional<bool> parseState(const QByteArray &data) { return Type::parseState(data); }
|
||||||
|
|
||||||
|
// Keep custom interval function
|
||||||
|
static QByteArray getIntervalPacket(quint8 interval)
|
||||||
|
{
|
||||||
|
return ControlCommand::createCommand(0x23, interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adaptive Volume Config
|
||||||
|
namespace AdaptiveVolume
|
||||||
|
{
|
||||||
|
using Type = BasicControlCommand<0x26>;
|
||||||
|
static const QByteArray ENABLED = Type::ENABLED;
|
||||||
|
static const QByteArray DISABLED = Type::DISABLED;
|
||||||
|
static const QByteArray HEADER = Type::HEADER;
|
||||||
|
inline std::optional<bool> parseState(const QByteArray &data) { return Type::parseState(data); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversational Awareness
|
||||||
namespace ConversationalAwareness
|
namespace ConversationalAwareness
|
||||||
{
|
{
|
||||||
static const QByteArray HEADER = QByteArray::fromHex("04000400090028"); // For command/status
|
using Type = BasicControlCommand<0x28>;
|
||||||
static const QByteArray ENABLED = HEADER + QByteArray::fromHex("01000000"); // Command to enable
|
static const QByteArray ENABLED = Type::ENABLED;
|
||||||
static const QByteArray DISABLED = HEADER + QByteArray::fromHex("02000000"); // Command to disable
|
static const QByteArray DISABLED = Type::DISABLED;
|
||||||
static const QByteArray DATA_HEADER = QByteArray::fromHex("040004004B00020001"); // For received speech level data
|
static const QByteArray HEADER = Type::HEADER;
|
||||||
|
static const QByteArray DATA_HEADER = QByteArray::fromHex("040004004B00020001");
|
||||||
|
inline std::optional<bool> parseState(const QByteArray &data) { return Type::parseState(data); }
|
||||||
|
}
|
||||||
|
|
||||||
static std::optional<bool> parseCAState(const QByteArray &data)
|
// Hearing Assist
|
||||||
{
|
namespace HearingAssist
|
||||||
// Extract the status byte (index 7)
|
{
|
||||||
quint8 statusByte = static_cast<quint8>(data.at(HEADER.size())); // HEADER.size() is 7
|
using Type = BasicControlCommand<0x33>;
|
||||||
|
static const QByteArray ENABLED = Type::ENABLED;
|
||||||
|
static const QByteArray DISABLED = Type::DISABLED;
|
||||||
|
static const QByteArray HEADER = Type::HEADER;
|
||||||
|
inline std::optional<bool> parseState(const QByteArray &data) { return Type::parseState(data); }
|
||||||
|
}
|
||||||
|
|
||||||
// Interpret the status byte
|
// Allow Off Option
|
||||||
switch (statusByte)
|
namespace AllowOffOption
|
||||||
{
|
{
|
||||||
case 0x01: // Enabled
|
using Type = BasicControlCommand<0x34>;
|
||||||
return true;
|
static const QByteArray ENABLED = Type::ENABLED;
|
||||||
case 0x02: // Disabled
|
static const QByteArray DISABLED = Type::DISABLED;
|
||||||
return false;
|
static const QByteArray HEADER = Type::HEADER;
|
||||||
default:
|
inline std::optional<bool> parseState(const QByteArray &data) { return Type::parseState(data); }
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection Packets
|
// Connection Packets
|
||||||
@@ -118,65 +162,37 @@ namespace AirPodsPackets
|
|||||||
{
|
{
|
||||||
MagicCloudKeys keys;
|
MagicCloudKeys keys;
|
||||||
|
|
||||||
// Expected size: header (7 bytes) + (1 (tag) + 2 (length) + 1 (reserved) + 16 (value)) * 2 = 47 bytes.
|
if (data.size() < 47 || !data.startsWith(MAGIC_CLOUD_KEYS_HEADER))
|
||||||
if (data.size() < 47)
|
|
||||||
{
|
{
|
||||||
return keys; // or handle error as needed
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check header
|
int index = MAGIC_CLOUD_KEYS_HEADER.size();
|
||||||
if (!data.startsWith(MAGIC_CLOUD_KEYS_HEADER))
|
|
||||||
{
|
|
||||||
return keys; // header mismatch
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = MAGIC_CLOUD_KEYS_HEADER.size(); // Start after header (index 7)
|
// First TLV block (MagicAccIRK)
|
||||||
|
|
||||||
// --- TLV Block 1 (MagicAccIRK) ---
|
|
||||||
// Tag should be 0x01
|
|
||||||
if (static_cast<quint8>(data.at(index)) != 0x01)
|
if (static_cast<quint8>(data.at(index)) != 0x01)
|
||||||
{
|
return keys;
|
||||||
return keys; // unexpected tag
|
|
||||||
}
|
|
||||||
index += 1;
|
index += 1;
|
||||||
|
|
||||||
// Read length (2 bytes, big-endian)
|
|
||||||
quint16 len1 = (static_cast<quint8>(data.at(index)) << 8) | static_cast<quint8>(data.at(index + 1));
|
quint16 len1 = (static_cast<quint8>(data.at(index)) << 8) | static_cast<quint8>(data.at(index + 1));
|
||||||
if (len1 != 16)
|
if (len1 != 16)
|
||||||
{
|
return keys;
|
||||||
return keys; // invalid length
|
index += 3; // Skip length (2 bytes) and reserved byte (1 byte)
|
||||||
}
|
|
||||||
index += 2;
|
|
||||||
|
|
||||||
// Skip reserved byte
|
|
||||||
index += 1;
|
|
||||||
|
|
||||||
// Extract MagicAccIRK (16 bytes)
|
|
||||||
keys.magicAccIRK = data.mid(index, 16);
|
keys.magicAccIRK = data.mid(index, 16);
|
||||||
index += 16;
|
index += 16;
|
||||||
|
|
||||||
// --- TLV Block 2 (MagicAccEncKey) ---
|
// Second TLV block (MagicAccEncKey)
|
||||||
// Tag should be 0x04
|
|
||||||
if (static_cast<quint8>(data.at(index)) != 0x04)
|
if (static_cast<quint8>(data.at(index)) != 0x04)
|
||||||
{
|
return keys;
|
||||||
return keys; // unexpected tag
|
|
||||||
}
|
|
||||||
index += 1;
|
index += 1;
|
||||||
|
|
||||||
// Read length (2 bytes, big-endian)
|
|
||||||
quint16 len2 = (static_cast<quint8>(data.at(index)) << 8) | static_cast<quint8>(data.at(index + 1));
|
quint16 len2 = (static_cast<quint8>(data.at(index)) << 8) | static_cast<quint8>(data.at(index + 1));
|
||||||
if (len2 != 16)
|
if (len2 != 16)
|
||||||
{
|
return keys;
|
||||||
return keys; // invalid length
|
index += 3; // Skip length (2 bytes) and reserved byte (1 byte)
|
||||||
}
|
|
||||||
index += 2;
|
|
||||||
|
|
||||||
// Skip reserved byte
|
|
||||||
index += 1;
|
|
||||||
|
|
||||||
// Extract MagicAccEncKey (16 bytes)
|
|
||||||
keys.magicAccEncKey = data.mid(index, 16);
|
keys.magicAccEncKey = data.mid(index, 16);
|
||||||
index += 16;
|
|
||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class AirPodsTrayApp : public QObject {
|
|||||||
Q_PROPERTY(bool notificationsEnabled READ notificationsEnabled WRITE setNotificationsEnabled NOTIFY notificationsEnabledChanged)
|
Q_PROPERTY(bool notificationsEnabled READ notificationsEnabled WRITE setNotificationsEnabled NOTIFY notificationsEnabledChanged)
|
||||||
Q_PROPERTY(int retryAttempts READ retryAttempts WRITE setRetryAttempts NOTIFY retryAttemptsChanged)
|
Q_PROPERTY(int retryAttempts READ retryAttempts WRITE setRetryAttempts NOTIFY retryAttemptsChanged)
|
||||||
Q_PROPERTY(bool hideOnStart READ hideOnStart CONSTANT)
|
Q_PROPERTY(bool hideOnStart READ hideOnStart CONSTANT)
|
||||||
|
Q_PROPERTY(bool oneBudANCMode READ oneBudANCMode WRITE setOneBudANCMode NOTIFY oneBudANCModeChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AirPodsTrayApp(bool debugMode, bool hideOnStart, QQmlApplicationEngine *parent = nullptr)
|
AirPodsTrayApp(bool debugMode, bool hideOnStart, QQmlApplicationEngine *parent = nullptr)
|
||||||
@@ -146,6 +147,7 @@ public:
|
|||||||
void setNotificationsEnabled(bool enabled) { trayManager->setNotificationsEnabled(enabled); }
|
void setNotificationsEnabled(bool enabled) { trayManager->setNotificationsEnabled(enabled); }
|
||||||
int retryAttempts() const { return m_retryAttempts; }
|
int retryAttempts() const { return m_retryAttempts; }
|
||||||
bool hideOnStart() const { return m_hideOnStart; }
|
bool hideOnStart() const { return m_hideOnStart; }
|
||||||
|
bool oneBudANCMode() const { return m_oneBudANCMode; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool debugMode;
|
bool debugMode;
|
||||||
@@ -227,6 +229,29 @@ public slots:
|
|||||||
emit conversationalAwarenessChanged(enabled);
|
emit conversationalAwarenessChanged(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setOneBudANCMode(bool enabled)
|
||||||
|
{
|
||||||
|
if (m_oneBudANCMode == enabled)
|
||||||
|
{
|
||||||
|
LOG_INFO("One Bud ANC mode is already " << (enabled ? "enabled" : "disabled"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Setting One Bud ANC mode to: " << (enabled ? "enabled" : "disabled"));
|
||||||
|
QByteArray packet = enabled ? AirPodsPackets::OneBudANCMode::ENABLED
|
||||||
|
: AirPodsPackets::OneBudANCMode::DISABLED;
|
||||||
|
|
||||||
|
if (writePacketToSocket(packet, "One Bud ANC mode packet written: "))
|
||||||
|
{
|
||||||
|
m_oneBudANCMode = enabled;
|
||||||
|
emit oneBudANCModeChanged(enabled);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_ERROR("Failed to send One Bud ANC mode command: socket not open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setRetryAttempts(int attempts)
|
void setRetryAttempts(int attempts)
|
||||||
{
|
{
|
||||||
if (m_retryAttempts != attempts)
|
if (m_retryAttempts != attempts)
|
||||||
@@ -440,6 +465,7 @@ private slots:
|
|||||||
trayManager->showNotification(
|
trayManager->showNotification(
|
||||||
tr("AirPods Disconnected"),
|
tr("AirPods Disconnected"),
|
||||||
tr("Your AirPods have been disconnected"));
|
tr("Your AirPods have been disconnected"));
|
||||||
|
trayManager->resetTrayIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
void bluezDeviceDisconnected(const QString &address, const QString &name)
|
void bluezDeviceDisconnected(const QString &address, const QString &name)
|
||||||
@@ -628,7 +654,7 @@ private slots:
|
|||||||
}
|
}
|
||||||
// Get CA state
|
// Get CA state
|
||||||
else if (data.startsWith(AirPodsPackets::ConversationalAwareness::HEADER)) {
|
else if (data.startsWith(AirPodsPackets::ConversationalAwareness::HEADER)) {
|
||||||
auto result = AirPodsPackets::ConversationalAwareness::parseCAState(data);
|
auto result = AirPodsPackets::ConversationalAwareness::parseState(data);
|
||||||
if (result.has_value()) {
|
if (result.has_value()) {
|
||||||
m_conversationalAwareness = result.value();
|
m_conversationalAwareness = result.value();
|
||||||
LOG_INFO("Conversational awareness state received: " << m_conversationalAwareness);
|
LOG_INFO("Conversational awareness state received: " << m_conversationalAwareness);
|
||||||
@@ -697,6 +723,19 @@ private slots:
|
|||||||
}
|
}
|
||||||
emit airPodsStatusChanged();
|
emit airPodsStatusChanged();
|
||||||
}
|
}
|
||||||
|
else if (data.startsWith(AirPodsPackets::OneBudANCMode::HEADER)) {
|
||||||
|
auto result = AirPodsPackets::OneBudANCMode::parseState(data);
|
||||||
|
if (result.has_value())
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_DEBUG("Unrecognized packet format: " << data.toHex());
|
LOG_DEBUG("Unrecognized packet format: " << data.toHex());
|
||||||
@@ -926,6 +965,7 @@ signals:
|
|||||||
void crossDeviceEnabledChanged(bool enabled);
|
void crossDeviceEnabledChanged(bool enabled);
|
||||||
void notificationsEnabledChanged(bool enabled);
|
void notificationsEnabledChanged(bool enabled);
|
||||||
void retryAttemptsChanged(int attempts);
|
void retryAttemptsChanged(int attempts);
|
||||||
|
void oneBudANCModeChanged(bool enabled);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QBluetoothSocket *socket = nullptr;
|
QBluetoothSocket *socket = nullptr;
|
||||||
@@ -953,6 +993,7 @@ private:
|
|||||||
bool m_secoundaryInEar = false;
|
bool m_secoundaryInEar = false;
|
||||||
QByteArray m_magicAccIRK;
|
QByteArray m_magicAccIRK;
|
||||||
QByteArray m_magicAccEncKey;
|
QByteArray m_magicAccEncKey;
|
||||||
|
bool m_oneBudANCMode = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resetTrayIcon()
|
||||||
|
{
|
||||||
|
trayIcon->setIcon(QIcon(":/icons/assets/airpods.png"));
|
||||||
|
trayIcon->setToolTip("");
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void notificationsEnabledChanged(bool enabled);
|
void notificationsEnabledChanged(bool enabled);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user