Skip to content

AirPods Max support for linux-rust (encrypted AAP, battery, NC, on-head, head-tracking scaffold)#617

Open
Dino-dev66 wants to merge 1 commit into
kavishdevar:linux/rustfrom
Dino-dev66:feat/airpods-max-support
Open

AirPods Max support for linux-rust (encrypted AAP, battery, NC, on-head, head-tracking scaffold)#617
Dino-dev66 wants to merge 1 commit into
kavishdevar:linux/rustfrom
Dino-dev66:feat/airpods-max-support

Conversation

@Dino-dev66

Copy link
Copy Markdown

Summary

Adds working AirPods Max support to the linux-rust client, tested against a real AirPods Max (A2096, Lightning).

Root cause that unlocked everything

The L2CAP channel was opened unencrypted (BT_SECURITY defaulted to Low). AirPods accept writes over an unencrypted channel (so changing noise control appeared to work) but never emit the AAP notification stream — no battery, ear detection, mode readback, metadata or proximity keys. Setting the socket to Security::Medium before connect() (the equivalent of what proximity_keys.py/bumble do with authenticate() + encrypt()) makes the Max stream everything.

Now working on AirPods Max

  • Battery monitoring — single Headphone (0x01) component, count=1
  • Noise control — set + live readback (Off/Transparency/NC)
  • On-head detection — drives media play/pause
  • Connect/disconnect desktop notifications (notify-send, transient)

Head tracking / spatial audio

  • Implemented the Pro-style 0x17 stream end to end: parse, start/stop senders, an AACPEvent::HeadTracking, a live pitch/yaw/roll visualiser, re-center, and a nod/shake GestureDetector mapped to media controls. This path is for models that support it (e.g. AirPods Pro).
  • Gated off for AirPods Max (model A2096/A3184) with an in-app explanation: the Max exposes motion via a ProtectedAccess HID-over-AAP relay (cma / B2PRelayHIDDevice) that doesn't stream to an unauthenticated host. The Pro-style START packet only makes it re-advertise its HID descriptors.

Known hardware limits on the Max (not bugs)

  • Conversational Awareness — the Max never advertises ConversationDetectConfig (H1 chip; Apple gates CA to H2).
  • Digital Crown volume / play-pause are handled by the OS via AVRCP and send no AAP packets (nothing to wire app-side).

Notes

  • No model gating existed before; the head-tracking UI is gated by the 0x1D model number.
  • GNOME ignores libnotify expire_timeout, so notifications use the transient hint and are limited to connect/disconnect.

🤖 Generated with Claude Code

Force L2CAP security to Medium before connecting. AirPods refuse to emit
the AAP notification stream over an unencrypted channel, so the Max only
accepted writes (NC) and reported nothing back. With encryption it now
streams battery, ear/on-head detection, listening mode, metadata and keys.

What now works on AirPods Max:
- Battery monitoring (single Headphone component, count=1)
- Noise control set + live readback
- On-head detection (drives media play/pause)
- Connect/disconnect desktop notifications (notify-send)

Spatial audio / head tracking:
- Implemented the Pro-style 0x17 head-tracking stream: parse, start/stop
  senders, an AACPEvent, a live orientation visualiser, re-center, and
  nod/shake gesture detection mapped to media controls.
- Gated off for AirPods Max (model A2096/A3184) with an explanatory note:
  the Max exposes motion via a ProtectedAccess HID-over-AAP relay that no
  host can open. Conversational Awareness is likewise unavailable (H1 chip).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant