feat(bbm): Bluetooth Battery Meter integration - #1
Closed
silentone12725 wants to merge 18 commits into
Closed
silentone12725 wants to merge 18 commits into
silentone12725 wants to merge 18 commits into
Conversation
Integrates the Bluetooth Battery Meter daemon (org.bbm D-Bus service)
into the Bluetooth popout. When the daemon is running and a supported
device is connected, a collapsible row appears beneath the device entry
showing:
- Per-component battery bars (L / R / case) with SVG icons, percentage,
and a bolt icon when charging
- ANC / Noise Control toggle pills (Off / Transparency / NC) with the
device's own icon alongside the label
- NC intensity sub-row (Low / Mid / High / Adaptive) for devices that
support multiple cancellation levels, using the secondary accent to
visually distinguish it from the mode row
The integration degrades gracefully: when the daemon is not running or
the device is not supported the Bluetooth popout renders exactly as
before. BbmService retries the D-Bus connection every 5 s so it
recovers automatically if the daemon starts later.
New files:
services/BbmService.qml – singleton owning org.bbm session-bus
connection; GVariant text parser;
icon URL helpers (batteryIconUrl /
toggleIconUrl) with encodeURI so
paths containing spaces work
modules/bar/popouts/BbmExpandedRow.qml – expanded device row; inline
BbmSvgIcon component uses QtQuick.
Effects MultiEffect for theme-aware
recolouring of symbolic SVG icons
Modified files:
modules/bar/popouts/Bluetooth.qml – delegate changed from RowLayout
to ColumnLayout; Loader added for
BbmExpandedRow
modules/ServiceLoader.qml – eagerly instantiates BbmService so
battery data is ready before the user
first opens the Bluetooth popout
Requires: bbm-daemon ≥ commit that adds GetIconPath() and AncLevel*
fields to the org.bbm.Manager D-Bus interface.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ANC toggle pills are now icon-only (no text label) with Tokens.rounding.medium radius so they look like rounded-square chips rather than full pills - NC intensity level pills also use Tokens.rounding.medium for visual consistency - NC level section animates in/out (scale 0.92→1 + opacity 0→1) and is only shown when OptionsBoxVisible === 1 (i.e. NC mode is the active ANC state) - Card background uses StyledRect with m3surfaceContainer + Tokens.rounding.large - All icons theme-aware via MultiEffect.colorization: 1.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch BbmIcon from raw MultiEffect to Colouriser (which applies brightness pre-correction based on sourceColor.hslLightness) so dark SVG fills (#2e3436) are normalised to near-white before the colorizationColor is applied. Without this, dark source pixels stayed dark regardless of the theme token. - Battery icons: 16px, m3onSurface colour (correctly follows light/dark theme) - Toggle pill icons: 18px, same colour semantics - Pill size grows to accommodate 18px icons with padding - Bluetooth popout width: 300 → 360 to fit Low/Mid/High/Adaptive row Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Battery row now shows when Battery1Level > 0 (was Battery2Level > 0), so single-battery devices (headphones, neckbands, ear sticks) display their battery component instead of a blank row - BbmIcon hides itself on Image.Error so a missing/broken SVG path never shows Qt's broken-image placeholder (fixes stray '+' artifact) - Remove redundant Layout.rightMargin from BbmExpandedRow; parent device ColumnLayout now owns 2px horizontal margins on both sides - Bluetooth.qml device delegate: Layout.leftMargin/rightMargin = 2px each so the device list has 2px breathing room from the panel edges Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hardcoded sourceColor: "#2e3436" with ImageAnalyser.dominantColour, mirroring ColouredIcon.qml. The analyser reads the actual rendered pixels to derive the dominant source colour, so Colouriser's brightness compensation is always accurate regardless of which BBM SVG variant is loaded. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BbmExpandedRow.qml: - Pill implicitHeight/implicitWidth now derived from pillIcon.size instead of repeating the magic number 18 in three places - Remove font.pixelSize: 12 from bolt MaterialIcon (inherits from context) BbmService.qml: - Extract D-Bus dest/path/interface to readonly property constants (_dest, _path, _iface) and reference them across all 5 gdbus commands and the signal monitor regex — single edit point if the bus name ever changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BbmService.qml spawns gdbus (from glib) to communicate with the org.bbm D-Bus service. Declare it explicitly so the Nix-wrapped binary has it in PATH on NixOS. callPackage injects pkgs.glib automatically by name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bundle bbm/daemon + bbm/lib + bbm/icons from Bluetooth-Battery-Meter
- Add nix/bbm.nix derivation: compiles schemas, wraps gjs as bbm-daemon binary
- Expose bbm-daemon package in flake.nix
- Add programs.caelestia.bbm option to hm-module.nix with auto-starting
systemd user service (After=bluetooth.target, PartOf=graphical-session)
- CMakeLists: install bbm/ alongside shell QML dirs for non-Nix installs
- BbmService.qml: fix _parseDeviceData to handle GVariant type-annotated
integers ("int32 65", "uint32 3", etc.) — battery levels and toggle states
were stored as raw strings, breaking level filters and state comparisons
- Bluetooth.qml: add matching leftMargin=extraSmall wherever rightMargin
was set, making horizontal padding symmetric
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bluetooth.qml: change device row margins from hardcoded 2px to Tokens.padding.extraSmall so they match title/toggle/device-count margins and all horizontal padding is uniform - BbmService.qml: prefix gdbus monitor with stdbuf -oL to force line-buffering — without it, gdbus switches to block-buffered stdout when piped (not a tty) and DeviceChanged signals queue silently in a 4-8KB buffer before flushing, causing battery/ANC state to appear frozen despite the daemon emitting updates in real time Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DataHandler rewire on reconnect — see BBM repo commit for full detail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract BbmIcon inline component to shared BbmIcon.qml - Add batteriesFor(), togglesFor() helpers to BbmService singleton - Add STATUS_CHARGING and WIDGET_ANC_LEVEL constants to BbmService - Replace hardcoded retryTimer interval with _retryIntervalMs constant - Fix _init() race: start monitor before listProc via Qt.callLater - Remove redundant batteryIconUrl visibility check in BtDeviceHover - batteryColorForLevel kept local in each file (Colours singleton not safely importable from within qs.services without circular dep risk) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tests/bbm/test-bbm-logic.mjs — 35 Node.js tests covering:
- BbmService constants (STATUS_CHARGING, WIDGET_ANC_LEVEL, _retryIntervalMs)
- batteriesFor(): null safety, level=0 filter, charging flag, all 3 slots,
icon defaults, mixed levels, no mutation
- togglesFor(): null safety, invisible/empty-button filter, widgetId per
slot, field defaults, ordering
- batteryColorForLevel(): lerp boundaries at 0/15/40/100, midpoints,
NaN/negative/over-100 clamping, string input
tests/bbm/tst_BbmIcon.qml — 8 Qt6 QML tests via qmltestrunner covering:
- BbmIcon loads without Quickshell runtime (using stubs)
- default size=18 drives implicitWidth/implicitHeight
- size property is reactive
- url and colour properties settable
- empty url does not crash
Run with:
node tests/bbm/test-bbm-logic.mjs
/usr/lib/qt6/bin/qmltestrunner \
-import tests/bbm/stubs -import tests/bbm \
-input tests/bbm/tst_BbmIcon.qml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lists all paired devices with BBM icon fallback and navigation to per-device settings subpage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds blank separators between QML object sections and fixes section ordering to match the Qt coding conventions (id → properties → functions → bindings → child objects). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Integrates the Bluetooth Battery Meter (
org.bbm) GJS daemon intocaelestia-shell, adding per-component battery indicators and device
controls to the Bluetooth bar popout and hover cards.
BbmService.qml— new singleton monitoringorg.bbmD-Bus viagdbus; exposesdeviceMap,batteriesFor(),togglesFor(),sendUIAction(), constants, and icon URL helpersBbmExpandedRow.qml— expandable in-popout panel showing up to3 per-component batteries (level-coloured rings) and 2 toggle control
groups (ANC, spatial audio, etc.)
BtDeviceHover.qml— hover card extended with BBM battery rowsand toggle controls, falling back to native BT battery when BBM is
unavailable
BbmIcon.qml— shared component for themed SVG device icons(Colouriser + ImageAnalyser layer effect), extracted from the two
places that previously inlined it
BluetoothStatus.qml/Bar.qml— BBM-aware batteryindicator in the status bar (rings, text, icon; configurable)
ServicesPage.qml/BtDeviceDefaultsPage.qml— nexussettings page listing paired devices with BBM icons and navigation to
per-device settings
nix/bbm.nixwraps the daemon;hm-module.nixwiressystemd user service and GSettings schema
Breaking changes / side effects
None. All BBM paths are guarded by
BbmService.available; the UIdegrades gracefully to native BlueZ battery data when the daemon is not
running.
How to use
org.bbmdaemon (seebbm/daemon/) or installvia the home-manager module (
nix/hm-module.nix)BbmServicepicks uporg.bbmautomaticallyclick the device row to expand ANC / mode toggles
Test plan
node tests/bbm/test-bbm-logic.mjs→ 35/35 pass/usr/lib/qt6/bin/qmltestrunner -import tests/bbm/stubs -import tests/bbm -input tests/bbm/tst_BbmIcon.qml→ 8/8 passBbmServicere-initialises cleanly