From 3f4e3514a20f1030a1f5c8ee748baeea5ac42e65 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 27 Aug 2026 11:45:14 +0100 Subject: [PATCH] Fix: Icons now map correctly to device --- .../.config/quickshell/modules/bar/Volume.qml | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/dotfiles/.config/quickshell/modules/bar/Volume.qml b/dotfiles/.config/quickshell/modules/bar/Volume.qml index bdbf0e7..9759de1 100644 --- a/dotfiles/.config/quickshell/modules/bar/Volume.qml +++ b/dotfiles/.config/quickshell/modules/bar/Volume.qml @@ -73,27 +73,52 @@ Item { p["device.icon-name"] || "", p["device.product.name"] || ""].join(" ").toLowerCase() } + // Quickshell's Pipewire API only exposes a node's own properties, not + // its parent Device — so device.api (unlike device.form-factor or the + // richer device.icon-name, which only live on the Device object) is the + // one reliable Bluetooth signal actually present here: it's set on + // every node PipeWire hands off to a bluez5-backed sink/source. Checking + // it directly beats sniffing the word "bluetooth" out of node names, + // which usually just say "bluez_output..." and never match. + function isBluetoothNode(node) { + var p = (node && node.properties) || {} + return p["device.api"] === "bluez5" + } + + // PipeWire's own device.form-factor (which would call ACCENTUM a + // "headset" outright) isn't reachable from here for the same reason — + // it lives on the Device object, not the node. And some devices (a + // generic USB DAC like an EKSA headset) have no form-factor anywhere in + // PipeWire at all, headset or not, so no property-based check can catch + // them. This override list is the deliberate manual escape hatch for + // exactly that case — add a distinctive substring from the product name + // (device.product.name/description, matched case-insensitively via + // deviceBlob) whenever a new no-metadata device shows up misclassified. + readonly property var headphoneNameOverrides: ["e900plus"] + function isHeadphoneNode(node) { var blob = deviceBlob(node) - return blob.indexOf("headphone") !== -1 || blob.indexOf("headset") !== -1 + if (blob.indexOf("headphone") !== -1 || blob.indexOf("headset") !== -1 || blob.indexOf("earbud") !== -1 || blob.indexOf("earphone") !== -1 - || blob.indexOf("airpod") !== -1 + || blob.indexOf("airpod") !== -1) + return true + return root.headphoneNameOverrides.some(function(needle) { return blob.indexOf(needle) !== -1 }) } function sinkIcon(node) { if (!node) return "speaker" if (isHeadphoneNode(node)) return "headphones" + if (isBluetoothNode(node)) return "bluetooth_audio" var blob = deviceBlob(node) - if (blob.indexOf("bluetooth") !== -1) return "bluetooth_audio" if (blob.indexOf("hdmi") !== -1 || blob.indexOf("display") !== -1) return "tv" return "speaker" } function sourceIcon(node) { if (!node) return "mic" + if (isHeadphoneNode(node)) return "headphones" + if (isBluetoothNode(node)) return "bluetooth_audio" var blob = deviceBlob(node) - if (blob.indexOf("headset") !== -1) return "headphones" - if (blob.indexOf("bluetooth") !== -1) return "bluetooth_audio" if (blob.indexOf("webcam") !== -1 || blob.indexOf("camera") !== -1) return "videocam" return "mic" }