Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ facter = "facter" # nixos-facter-modules
crypted = "crypted" # LUKS device mapper name
wdth = "wdth" # CSS font-variation-settings property
substituters = "substituters" # nix substituters
ANC = "ANC" # Active Noise Cancellation (AirPods)
# Hashes/keys (extend-ignore-re should catch most, but explicit for safety)
"981DE78A201C2B735FF0B545A3967CCA47D5275F" = "981DE78A201C2B735FF0B545A3967CCA47D5275F"

Expand Down
19 changes: 19 additions & 0 deletions modules/apps/librepods.nix
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,25 @@ let

config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];

# AirPods Max 2 (model A3454) outputs left-channel-only audio when the
# WirePlumber/BlueZ stack picks the non-standard SBC-XQ codec, which is
# selected by default on Linux when both peers advertise it. Reported
# at https://github.com/kavishdevar/librepods/pull/519#issuecomment-4230312279
# by the PR author after testing on Arch with the same WirePlumber
# build NixOS ships. `bluez5.enable-sbc-xq` is a daemon-wide property
# in WirePlumber's BlueZ monitor, so this disables SBC-XQ for every
# bluetooth sink rather than just the Max 2; it is the same scope the
# upstream workaround uses. Other sinks negotiate plain SBC (or AAC if
# both ends support it), which is the codec all non-Max AirPods and
# most generic bluetooth speakers default to anyway. Drop this block
# if upstream librepods/WirePlumber gain a per-device opt-out, or if
# the Max 2 firmware ships a fix for the SBC-XQ stereo handling.
services.pipewire.wireplumber.extraConfig."51-disable-sbc-xq" = {
"monitor.bluez.properties" = {
"bluez5.enable-sbc-xq" = false;
};
};
};
};
in
Expand Down
9 changes: 8 additions & 1 deletion modules/base/custom-packages-overlay.nix
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,21 @@ _: {
# qtquick3d for qtdeclarative + qttools and adds Widgets/DBus.
# Dep lookups go through `final` so any later overlay (e.g. an `openssl`
# CVE patch) feeds into this build instead of being silently bypassed.
librepods = prev.librepods.overrideAttrs (_old: rec {
# The Max-2 patch backports https://github.com/kavishdevar/librepods/pull/519
# so AirPods Max 2 (BLE 0x2D20 / model A3454) is recognised; without it
# the device falls through to the unknown-model defaults. Drop the patch
# once upstream merges PR #519 and a release pinning it ships.
librepods = prev.librepods.overrideAttrs (old: rec {
version = "0.2.5";
src = final.fetchFromGitHub {
owner = "kavishdevar";
repo = "librepods";
tag = "v${version}";
hash = "sha256-6l1WjwjDbv5e3tDaWo9+XSEjr9ge/hKysIkeUqyiO4U=";
};
patches = (old.patches or [ ]) ++ [
../../packages/librepods/airpods-max-2.patch
];
Comment thread
Bad3r marked this conversation as resolved.
buildInputs = [
final.libpulseaudio
final.openssl
Expand Down
78 changes: 78 additions & 0 deletions packages/librepods/airpods-max-2.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
Subject: [PATCH] add basic AirPods Max 2 support and stub max_case.png

Two related fixes squashed into one patch:

1. Backport https://github.com/kavishdevar/librepods/pull/519 onto v0.2.5 so
AirPods Max 2nd Gen (BLE 0x2D20, model A3454) is recognised. Without this
the device falls through to the Unknown enum branch.

2. Replace the dangling `max_case.png` reference in `getModelIcon()` with the
existing `podmax.png` asset. Upstream never shipped `max_case.png`: it does
not exist on `main`, on `linux/rust`, or in the Android `res-apple`
drawables (which only carry case art for AirPods 1/2/3/4/Pro 1/2/3, never
Max). AirPods Max has no charging case, just a non-electronic Smart Case
sleeve, so there is no canonical Apple artwork for a "Max case battery".
The case PodColumn is hidden for Max anyway (`caseAvailable` stays false
because battery is reported via the Headset component), but QQuickImage
still resolves the source URL on creation, producing
`qrc:/icons/assets/max_case.png is not installed` warnings. Pointing both
slots at `podmax.png` silences the warning without inventing an asset and
stays semantically Max-shaped if the visibility logic ever changes.

Patch paths are relative to the `linux/` build root because the nixpkgs
librepods derivation sets `sourceRoot = "source/linux"`. Upstream paths like
`linux/ble/blemanager.cpp` therefore become `ble/blemanager.cpp` here. The
upstream PR's `Proximity Pairing Message.md` docs change is omitted because
it lives outside the build root.

Drop this patch once upstream PR #519 merges, a release pinning it ships, and
upstream stops referencing `max_case.png` (or actually adds the asset).

diff --git a/ble/blemanager.cpp b/ble/blemanager.cpp
--- a/ble/blemanager.cpp
+++ b/ble/blemanager.cpp
@@ -16,6 +16,7 @@ AirpodsTrayApp::Enums::AirPodsModel getModelName(quint16 modelId)
{0x1B20, AirPodsModel::AirPods4ANC},
{0x0A20, AirPodsModel::AirPodsMaxLightning},
{0x1F20, AirPodsModel::AirPodsMaxUSBC},
+ {0x2D20, AirPodsModel::AirPodsMax2},
{0x0E20, AirPodsModel::AirPodsPro},
{0x1420, AirPodsModel::AirPodsPro2Lightning},
{0x2420, AirPodsModel::AirPodsPro2USBC}
diff --git a/enums.h b/enums.h
--- a/enums.h
+++ b/enums.h
@@ -32,6 +32,7 @@ namespace AirpodsTrayApp
AirPodsPro2USBC,
AirPodsMaxLightning,
AirPodsMaxUSBC,
+ AirPodsMax2,
Comment thread
Bad3r marked this conversation as resolved.
AirPods4,
AirPods4ANC
};
@@ -50,6 +51,7 @@ namespace AirpodsTrayApp
{"A2083", AirPodsModel::AirPodsPro},
{"A2096", AirPodsModel::AirPodsMaxLightning},
{"A3184", AirPodsModel::AirPodsMaxUSBC},
+ {"A3454", AirPodsModel::AirPodsMax2},
{"A2565", AirPodsModel::AirPods3},
{"A2564", AirPodsModel::AirPods3},
{"A3047", AirPodsModel::AirPodsPro2USBC},
@@ -85,7 +87,8 @@ namespace AirpodsTrayApp
return {"podpro.png", "podpro_case.png"};
case AirPodsModel::AirPodsMaxLightning:
case AirPodsModel::AirPodsMaxUSBC:
- return {"podmax.png", "max_case.png"};
+ case AirPodsModel::AirPodsMax2:
+ return {"podmax.png", "podmax.png"};
default:
return {"pod.png", "pod_case.png"}; // Default icon for unknown models
}
@@ -97,6 +100,7 @@ namespace AirpodsTrayApp
switch (model) {
case AirPodsModel::AirPodsMaxLightning:
case AirPodsModel::AirPodsMaxUSBC:
+ case AirPodsModel::AirPodsMax2:
return true;
default:
return false;