@@ -20,6 +20,7 @@ class AirPodsTrayApp : public QObject {
2020 Q_PROPERTY (bool conversationalAwareness READ conversationalAwareness WRITE setConversationalAwareness NOTIFY conversationalAwarenessChanged)
2121 Q_PROPERTY (int adaptiveNoiseLevel READ adaptiveNoiseLevel WRITE setAdaptiveNoiseLevel NOTIFY adaptiveNoiseLevelChanged)
2222 Q_PROPERTY (bool adaptiveModeActive READ adaptiveModeActive NOTIFY noiseControlModeChanged)
23+ Q_PROPERTY (QString deviceName READ deviceName NOTIFY deviceNameChanged)
2324
2425public:
2526 AirPodsTrayApp (bool debugMode) : debugMode (debugMode) {
@@ -100,6 +101,7 @@ class AirPodsTrayApp : public QObject {
100101 bool conversationalAwareness () const { return m_conversationalAwareness; }
101102 bool adaptiveModeActive () const { return m_noiseControlMode == NoiseControlMode::Adaptive; }
102103 int adaptiveNoiseLevel () const { return m_adaptiveNoiseLevel; }
104+ QString deviceName () const { return m_deviceName; }
103105
104106private:
105107 bool debugMode;
@@ -272,6 +274,37 @@ public slots:
272274 }
273275 }
274276
277+ void renameAirPods (const QString &newName)
278+ {
279+ if (newName.isEmpty ())
280+ {
281+ LOG_WARN (" Cannot set empty name" );
282+ return ;
283+ }
284+ if (newName.size () > 32 )
285+ {
286+ LOG_WARN (" Name is too long, must be 32 characters or less" );
287+ return ;
288+ }
289+ if (newName == m_deviceName)
290+ {
291+ LOG_INFO (" Name is already set to: " << newName);
292+ return ;
293+ }
294+
295+ QByteArray packet = AirPodsPackets::Rename::getPacket (newName);
296+ if (writePacketToSocket (packet, " Rename packet written: " ))
297+ {
298+ LOG_INFO (" Sent rename command for new name: " << newName);
299+ m_deviceName = newName;
300+ emit deviceNameChanged (newName);
301+ }
302+ else
303+ {
304+ LOG_ERROR (" Failed to send rename command: socket not open" );
305+ }
306+ }
307+
275308 bool writePacketToSocket (const QByteArray &packet, const QString &logMessage)
276309 {
277310 if (socket && socket->isOpen ())
@@ -363,6 +396,77 @@ private slots:
363396 }
364397 }
365398
399+ void parseMetadata (const QByteArray &data)
400+ {
401+ // Verify the data starts with the METADATA header
402+ if (!data.startsWith (AirPodsPackets::Parse::METADATA))
403+ {
404+ LOG_ERROR (" Invalid metadata packet: Incorrect header" );
405+ return ;
406+ }
407+
408+ int pos = AirPodsPackets::Parse::METADATA.size (); // Start after the header
409+
410+ // Check if there is enough data to skip the initial bytes (based on example structure)
411+ if (data.size () < pos + 6 )
412+ {
413+ LOG_ERROR (" Metadata packet too short to parse initial bytes" );
414+ return ;
415+ }
416+ pos += 6 ; // Skip 6 bytes after the header as per example structure
417+
418+ auto extractString = [&data, &pos]() -> QString
419+ {
420+ if (pos >= data.size ())
421+ {
422+ return QString ();
423+ }
424+ int start = pos;
425+ while (pos < data.size () && data.at (pos) != ' \0 ' )
426+ {
427+ ++pos;
428+ }
429+ QString str = QString::fromUtf8 (data.mid (start, pos - start));
430+ if (pos < data.size ())
431+ {
432+ ++pos; // Move past the null terminator
433+ }
434+ return str;
435+ };
436+
437+ m_deviceName = extractString ();
438+ QString modelNumber = extractString ();
439+ QString manufacturer = extractString ();
440+ QString hardwareVersion = extractString ();
441+ QString firmwareVersion = extractString ();
442+ QString firmwareVersion2 = extractString ();
443+ QString softwareVersion = extractString ();
444+ QString appIdentifier = extractString ();
445+ QString serialNumber1 = extractString ();
446+ QString serialNumber2 = extractString ();
447+ QString unknownNumeric = extractString ();
448+ QString unknownHash = extractString ();
449+ QString trailingByte = extractString ();
450+
451+ emit deviceNameChanged (m_deviceName);
452+
453+ // Log extracted metadata
454+ LOG_INFO (" Parsed AirPods metadata:" );
455+ LOG_INFO (" Device Name: " << m_deviceName);
456+ LOG_INFO (" Model Number: " << modelNumber);
457+ LOG_INFO (" Manufacturer: " << manufacturer);
458+ LOG_INFO (" Hardware Version: " << hardwareVersion);
459+ LOG_INFO (" Firmware Version: " << firmwareVersion);
460+ LOG_INFO (" Firmware Version2: " << firmwareVersion2);
461+ LOG_INFO (" Software Version: " << softwareVersion);
462+ LOG_INFO (" App Identifier: " << appIdentifier);
463+ LOG_INFO (" Serial Number 1: " << serialNumber1);
464+ LOG_INFO (" Serial Number 2: " << serialNumber2);
465+ LOG_INFO (" Unknown Numeric: " << unknownNumeric);
466+ LOG_INFO (" Unknown Hash: " << unknownHash);
467+ LOG_INFO (" Trailing Byte: " << trailingByte);
468+ }
469+
366470 void connectToDevice (const QBluetoothDeviceInfo &device) {
367471 if (socket && socket->isOpen () && socket->peerAddress () == device.address ()) {
368472 LOG_INFO (" Already connected to the device: " << device.name ());
@@ -490,6 +594,10 @@ private slots:
490594 LOG_INFO (" Received conversational awareness data" );
491595 mediaController->handleConversationalAwareness (data);
492596 }
597+ else if (data.startsWith (AirPodsPackets::Parse::METADATA))
598+ {
599+ parseMetadata (data);
600+ }
493601 else
494602 {
495603 LOG_DEBUG (" Unrecognized packet format: " << data.toHex ());
@@ -714,6 +822,7 @@ private slots:
714822 void batteryStatusChanged (const QString &status);
715823 void conversationalAwarenessChanged (bool enabled);
716824 void adaptiveNoiseLevelChanged (int level);
825+ void deviceNameChanged (const QString &name);
717826
718827private:
719828 QSystemTrayIcon *trayIcon;
@@ -733,6 +842,7 @@ private slots:
733842 NoiseControlMode m_noiseControlMode = NoiseControlMode::Off;
734843 bool m_conversationalAwareness = false ;
735844 int m_adaptiveNoiseLevel = 50 ;
845+ QString m_deviceName;
736846};
737847
738848int main (int argc, char *argv[]) {
0 commit comments