Files
DankMaterialShell/quickshell/scripts/bluez-card-profile.lua
T
lingdianshiren 300544bc8c bluetooth: identify codecs from the profile description (#3643)
* bluetooth: identify codecs from the profile description

PipeWire describes profiles in the daemon's locale and only names the codec in
that description for the bare profile (LHDC settles on "a2dp-sink"), so the old
English "codec %s" match dropped LHDC profiles on non-English systems. Parsing
the translated shape instead only moves the coupling to word order and
punctuation.

Emit the profile index and raw description from the lua script and let the UI
name the codec by matching the untranslated literal from one codec table.
Switch by index, falling back to the profile name or codec literal, so no
translated text is parsed. Also teach the BlueZ fallback the Savitech ids
(LHDC v5 and v3).

Test: quickshell/tests/bluez-codec.test.mjs

* bluetooth: show unknown codec without masking bluez

* bluetooth: ignore stale codec query results
2026-10-02 19:07:28 -04:00

144 lines
3.5 KiB
Lua
Executable File

#!/usr/bin/wpexec
cutils = require("common-utils")
local args = (...):parse()
local mode = args.mode or "list"
local dev_name = args.device or ""
local target = args.target or ""
local function fail(msg)
print("ERROR: " .. msg)
Core.quit()
end
if dev_name == "" then
fail("missing device")
return
end
local om = ObjectManager {
Interest {
type = "device",
Constraint { "media.class", "=", "Audio/Device", type = "pw-global" },
}
}
local handled = false
local function profile_available(profile)
return profile and profile.available ~= "no"
end
local function handle_device(device)
if handled then
return
end
local name = device.properties["device.name"] or ""
if name ~= dev_name then
return
end
handled = true
if mode == "list" then
local active_index = nil
for p in device:iterate_params("Profile") do
local active = cutils.parseParam(p, "Profile")
if active then
active_index = active.index
end
end
for p in device:iterate_params("EnumProfile") do
local profile = cutils.parseParam(p, "EnumProfile")
if profile_available(profile) then
local current = (active_index ~= nil and profile.index == active_index) and "1" or "0"
print(string.format("CODEC\t%s\t%s\t%s\t%s",
tostring(profile.index), profile.name or "", profile.description or "", current))
end
end
Core.quit()
return
end
if mode == "set" then
local target_index = tonumber(args.index)
local target_codec = tostring(args.codec or "")
if target == "" and target_index == nil and target_codec == "" then
fail("missing target")
return
end
-- A stale index must not outrank the profile the UI picked: try the exact
-- profile name first, then the codec literal, then the index.
local target_lower = tostring(target):lower()
local match = nil
local best_rank = 4
for p in device:iterate_params("EnumProfile") do
local profile = cutils.parseParam(p, "EnumProfile")
if profile_available(profile) then
local pname = profile.name or ""
local description = tostring(profile.description or ""):lower()
local rank = 4
if target ~= "" and (pname == target or pname:lower() == target_lower) then
rank = 1
elseif target_codec ~= "" and description:find(target_codec:lower(), 1, true) then
rank = 2
elseif target_index ~= nil and tonumber(profile.index) == target_index then
rank = 3
end
if rank < best_rank then
best_rank = rank
match = profile
if rank == 1 then
break
end
end
end
end
if not match then
fail("profile not found: " .. (target ~= "" and target or target_codec))
return
end
local param = Pod.Object {
"Spa:Pod:Object:Param:Profile", "Profile",
index = tonumber(match.index),
save = true,
}
device:set_param("Profile", param)
Core.sync(function()
print("OK\t" .. (match.name or target))
Core.quit()
end)
return
end
fail("unknown mode " .. mode)
end
om:connect("object-added", function(_, device)
handle_device(device)
end)
om:connect("installed", function()
if handled then
return
end
for device in om:iterate() do
handle_device(device)
if handled then
return
end
end
fail("device not found: " .. dev_name)
end)
om:activate()
Core.timeout_add(3000, function()
if not handled then
fail("timed out waiting for device " .. dev_name)
end
return false
end)