Skip to content

Earbuds

P3DROVFX edited this page Oct 12, 2026 · 1 revision

Earbuds and Headphones

Bluetooth audio devices get more than a "connected" badge: battery per bud and for the case, noise control (noise cancelling / transparency / off, and on some models adaptive or more states), and on some devices conversation awareness and an equalizer — in the Dashboard, the bar, the Dynamic Island, the connection popup, a desktop widget and Settings. The same controls appear everywhere because every surface talks to one router (EarbudsControlService) instead of knowing about brands.

Bluetooth itself is always BlueZ's job — pairing, connecting and disconnecting go through the normal Bluetooth stack and the shell's native Bluetooth service. The earbuds services only add telemetry and controls on top; with none of them, you still get a generic device with whatever BlueZ reports.

Providers, in order of preference

For each connected device the router picks the first provider that claims it:

# Provider Handles Needs
1 BudsLink (budslink) Whatever the optional BudsLink companion app supports — Samsung Galaxy Buds, Sony, Pixel Buds, Nothing, AirPods… with enriched controls BudsLink installed and running (session D-Bus service io.github.maniacx.BudsLink), Enable BudsLink on
2 Soundcore (soundcore) Anker Soundcore headphones — developed for the Life Q30 — ANC mode: Normal, Transparency, Noise Cancelling python3 (no extra package)
3 Built-in buds support (legacyBuds) Galaxy Buds, Nothing Buds, Pixel Buds, Sony WH/WF, AirPods and Beats, LinkBuds, other "buds/ear" devices — noise control via the vendors' own Bluetooth protocols gjs (GNOME JavaScript)
4 Generic BlueZ Anything else — battery as BlueZ reports it —

If BudsLink claims a device, the built-in buds support steps aside for it (and stays parked while BudsLink is still being probed), because both want the same BlueZ RFCOMM profile and BlueZ allows only one owner — whoever lost the race would silently end up without the device.

What you get

  • Battery — left, right and case with charging indicators (BluetoothBatteryBreakdown), when the device reports them; otherwise the single BlueZ percentage.
  • Noise control — a selector with 2, 3 or 4 states depending on the model (for example Off / Transparency / ANC, plus Adaptive), with a continuous slider where the device exposes a level.
  • Conversation awareness (speak-to-chat / voice detection) — a standard toggle.
  • BudsLink extras — the controls the device itself publishes (extension toggles, sliders, check boxes, radio buttons and the equalizer) are drawn dynamically in Material You; a Device settings button opens the device's own settings window.

Where it shows up

Surface What
Dashboard quick toggles The Bluetooth toggle's panel (battery + noise control), an optional Soundcore ANC toggle that cycles the modes, and an earbuds "stem" battery toggle
Bar The Bluetooth devices widget and popup (battery + controls; bluetooth.budsLink.showControlsInBarPopup)
Bluetooth connection popup A card when the device connects: battery, noise control, status (showControlsInConnectionPopup)
Dynamic Island An Earbuds bubble / notch (dynamicIsland.widgets.earbuds, side left or right)
Desktop widgets Bluetooth Earbuds Stem, headphone and cookie-style battery widgets ([[Desktop Widgets
Settings → Network → Bluetooth Per connected device: the Noise Control, Conversation Awareness and Device settings card (showEnhancedSettingsCard)
Modes & Routines An earbuds noise-control action ([[Modes & Routines

Using BudsLink

BudsLink is deliberately opt-in and niche: the shell never installs or starts it.

  1. Install BudsLink and make sure its session D-Bus service is available.
  2. Settings → Network → Bluetooth → Enable BudsLink (bluetooth.budsLink.enabled, default off). With it off, the bridge process never spawns and everything falls back to the built-in providers or generic BlueZ.
  3. The shell starts a small bridge (scripts/budslink/bridge.js, GJS) that holds the BudsLink service, talks to it over D-Bus and speaks JSON lines to the shell. A 120-second heartbeat keeps the lease alive; the bridge releases the service on disconnect. The service version must be the supported one (0.0.1); an incompatible BudsLink is reported contextually, without breaking generic Bluetooth.

Only a short allow-list of actions can be sent back to the device (two toggles, sliders and their drag state, check boxes, radio buttons, "settings button clicked"); everything else a UI might try to send is refused.

Options (Config.options.bluetooth.budsLink): enabled, preferBudsLink (let BudsLink win over the built-in support, default on), showIntegrationNotices, showBatteryBreakdown, showControlsInBarPopup, showControlsInConnectionPopup, showEnhancedSettingsCard.

Soundcore headphones

Detection is by name (a connected device whose name contains "soundcore" or "q30"). Setting a mode sends the Soundcore protocol packet directly over RFCOMM from scripts/soundcore/soundcore_ctl.py — it deliberately avoids registering a BlueZ profile so it can't collide with BudsLink — trying channel 13 first and then 1, 2, 3. The last mode set is cached per device (/tmp/quickshell-soundcore/), because the headphones don't announce changes made with their own buttons; the toggle updates optimistically. Modes: Normal, Transparency, Noise Cancelling. If soundcore_ctl.py is missing, the wrapper falls back to the openscq30_cli command-line tool. Config.options.soundcore.macAddress / model remember a device and model.

Built-in buds support

scripts/buds/core.js (GJS) speaks the vendors' protocols over BlueZ sockets: Samsung Galaxy Buds, Nothing, Google Fast Pair / Pixel Buds, Sony (two protocol generations) and Apple AirPods / Beats. Invocation: gjs -m core.js get <MAC> and gjs -m core.js set <MAC> <anc|transparency|off|adaptive>; the supported modes are reported per device so the selector offers only what the model has.

Under the hood

Piece Role
services/EarbudsControlService.qml The single router: provider resolution, normalized state and actions for every UI
services/BudsLinkService.qml, services/budslink/BudsLinkSemantics.js, scripts/budslink/bridge.js BudsLink companion
services/SoundcoreService.qml, scripts/soundcore/ Soundcore
services/BudsService.qml, scripts/buds/ Built-in vendor protocols
services/BluetoothStatus.qml Connected devices and BlueZ state (shared with the rest of the shell)
modules/common/widgets/bluetooth/ BluetoothBatteryBreakdown, EarbudsNoiseControlSelector, EarbudsConversationAwareness, MultiStateSlider, budslink/BudsLinkDynamicControls

Related pages

← Back to Home

Clone this wiki locally