[Linux] Implement renaming airpods (#88)

* [Linux] Implement rename airpods

* [Linux] Get airpods name from airpods metadata

* [Linux] Rename AirPods: Ui improvements

---------

Co-authored-by: Tim Gromeyer <tim.gromeyer@trans4mation.de>
This commit is contained in:
Tim Gromeyer
2025-03-28 07:04:17 +01:00
committed by GitHub
parent 5a71d9630d
commit 543362da69
3 changed files with 143 additions and 0 deletions

View File

@@ -72,5 +72,23 @@ ApplicationWindow {
anchors.top: parent.bottom
}
}
Row {
spacing: 10
TextField {
id: newNameField
placeholderText: airPodsTrayApp.deviceName
maximumLength: 32
}
Button {
text: "Rename"
onClicked: {
airPodsTrayApp.renameAirPods(newNameField.text)
// Optional: newNameField.text = "" // Clear field after rename
}
}
}
}
}

View File

@@ -74,11 +74,26 @@ namespace AirPodsPackets
}
}
namespace Rename
{
static QByteArray getPacket(const QString &newName)
{
QByteArray nameBytes = newName.toUtf8(); // Convert name to UTF-8
quint8 size = static_cast<char>(nameBytes.size()); // Name length (1 byte)
QByteArray packet = QByteArray::fromHex("040004001A0001"); // Header
packet.append(size); // Append size byte
packet.append('\0'); // Append null byte
packet.append(nameBytes); // Append name bytes
return packet;
}
}
// Parsing Headers
namespace Parse
{
static const QByteArray EAR_DETECTION = QByteArray::fromHex("040004000600");
static const QByteArray BATTERY_STATUS = QByteArray::fromHex("040004000400");
static const QByteArray METADATA = QByteArray::fromHex("040004001d");
}
}

View File

@@ -20,6 +20,7 @@ class AirPodsTrayApp : public QObject {
Q_PROPERTY(bool conversationalAwareness READ conversationalAwareness WRITE setConversationalAwareness NOTIFY conversationalAwarenessChanged)
Q_PROPERTY(int adaptiveNoiseLevel READ adaptiveNoiseLevel WRITE setAdaptiveNoiseLevel NOTIFY adaptiveNoiseLevelChanged)
Q_PROPERTY(bool adaptiveModeActive READ adaptiveModeActive NOTIFY noiseControlModeChanged)
Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
public:
AirPodsTrayApp(bool debugMode) : debugMode(debugMode) {
@@ -100,6 +101,7 @@ public:
bool conversationalAwareness() const { return m_conversationalAwareness; }
bool adaptiveModeActive() const { return m_noiseControlMode == NoiseControlMode::Adaptive; }
int adaptiveNoiseLevel() const { return m_adaptiveNoiseLevel; }
QString deviceName() const { return m_deviceName; }
private:
bool debugMode;
@@ -272,6 +274,37 @@ public slots:
}
}
void renameAirPods(const QString &newName)
{
if (newName.isEmpty())
{
LOG_WARN("Cannot set empty name");
return;
}
if (newName.size() > 32)
{
LOG_WARN("Name is too long, must be 32 characters or less");
return;
}
if (newName == m_deviceName)
{
LOG_INFO("Name is already set to: " << newName);
return;
}
QByteArray packet = AirPodsPackets::Rename::getPacket(newName);
if (writePacketToSocket(packet, "Rename packet written: "))
{
LOG_INFO("Sent rename command for new name: " << newName);
m_deviceName = newName;
emit deviceNameChanged(newName);
}
else
{
LOG_ERROR("Failed to send rename command: socket not open");
}
}
bool writePacketToSocket(const QByteArray &packet, const QString &logMessage)
{
if (socket && socket->isOpen())
@@ -363,6 +396,77 @@ private slots:
}
}
void parseMetadata(const QByteArray &data)
{
// Verify the data starts with the METADATA header
if (!data.startsWith(AirPodsPackets::Parse::METADATA))
{
LOG_ERROR("Invalid metadata packet: Incorrect header");
return;
}
int pos = AirPodsPackets::Parse::METADATA.size(); // Start after the header
// Check if there is enough data to skip the initial bytes (based on example structure)
if (data.size() < pos + 6)
{
LOG_ERROR("Metadata packet too short to parse initial bytes");
return;
}
pos += 6; // Skip 6 bytes after the header as per example structure
auto extractString = [&data, &pos]() -> QString
{
if (pos >= data.size())
{
return QString();
}
int start = pos;
while (pos < data.size() && data.at(pos) != '\0')
{
++pos;
}
QString str = QString::fromUtf8(data.mid(start, pos - start));
if (pos < data.size())
{
++pos; // Move past the null terminator
}
return str;
};
m_deviceName = extractString();
QString modelNumber = extractString();
QString manufacturer = extractString();
QString hardwareVersion = extractString();
QString firmwareVersion = extractString();
QString firmwareVersion2 = extractString();
QString softwareVersion = extractString();
QString appIdentifier = extractString();
QString serialNumber1 = extractString();
QString serialNumber2 = extractString();
QString unknownNumeric = extractString();
QString unknownHash = extractString();
QString trailingByte = extractString();
emit deviceNameChanged(m_deviceName);
// Log extracted metadata
LOG_INFO("Parsed AirPods metadata:");
LOG_INFO("Device Name: " << m_deviceName);
LOG_INFO("Model Number: " << modelNumber);
LOG_INFO("Manufacturer: " << manufacturer);
LOG_INFO("Hardware Version: " << hardwareVersion);
LOG_INFO("Firmware Version: " << firmwareVersion);
LOG_INFO("Firmware Version2: " << firmwareVersion2);
LOG_INFO("Software Version: " << softwareVersion);
LOG_INFO("App Identifier: " << appIdentifier);
LOG_INFO("Serial Number 1: " << serialNumber1);
LOG_INFO("Serial Number 2: " << serialNumber2);
LOG_INFO("Unknown Numeric: " << unknownNumeric);
LOG_INFO("Unknown Hash: " << unknownHash);
LOG_INFO("Trailing Byte: " << trailingByte);
}
void connectToDevice(const QBluetoothDeviceInfo &device) {
if (socket && socket->isOpen() && socket->peerAddress() == device.address()) {
LOG_INFO("Already connected to the device: " << device.name());
@@ -490,6 +594,10 @@ private slots:
LOG_INFO("Received conversational awareness data");
mediaController->handleConversationalAwareness(data);
}
else if (data.startsWith(AirPodsPackets::Parse::METADATA))
{
parseMetadata(data);
}
else
{
LOG_DEBUG("Unrecognized packet format: " << data.toHex());
@@ -714,6 +822,7 @@ signals:
void batteryStatusChanged(const QString &status);
void conversationalAwarenessChanged(bool enabled);
void adaptiveNoiseLevelChanged(int level);
void deviceNameChanged(const QString &name);
private:
QSystemTrayIcon *trayIcon;
@@ -733,6 +842,7 @@ private:
NoiseControlMode m_noiseControlMode = NoiseControlMode::Off;
bool m_conversationalAwareness = false;
int m_adaptiveNoiseLevel = 50;
QString m_deviceName;
};
int main(int argc, char *argv[]) {