A GNOME extension that displays indicator icons in the system tray, acting as a meter for Bluetooth device battery levels. It also provides detailed battery information via icons and text in the Bluetooth quick settings menu Note: Certain Bluetooth devices do not report battery level until Bluez's experimental features are enabled in system. Check Readme for details. The extension also supports multiple device-specific modes to provide detailed battery reporting and device controls, depending on the capabilities exposed by the device: UPower: Devices such as Logitech Lightspeed keyboards and mice that report battery levels through UPower. GATT Battery Service: Devices that expose one or more battery levels via the standard GATT Battery Service (BAS), such as ZMK-based split keyboards. Socket Mode: Provides multiple battery levels and advanced device controls—such as Active Noise Cancellation (ANC) and other supported features using a custom socket-based protocol. Supported devices: - AirPods - Beats earbuds / headphones - Sony earbuds / headphones - Nothing earbuds / headphones - CMF earbuds / headphones - Samsung Galaxy Buds
Note: Binary files aren't shown on the web site. To see all files, please download the extension zipfile.
EGO026 warning
JavaScript files should be reachable from extension.js or prefs.js
Some JavaScript files are not reachable from `extension.js` or `prefs.js` imports.
Don't include unnecessary files
script/moreSettings.js
EGO008 warning
extensions using unlock-dialog must document it in disable() comments
Extensions using `unlock-dialog` should document the reason in `disable()` comments.
extension.js
unlock-dialog declared but no nearby disable() comment found
EGO015 warning
signals connected by extension should be disconnected in disable()
Signals assigned in `enable()` are missing matching disconnect calls in `disable()` or its helper methods.
preferences/batteryWidgetSettings.js:61
colorButton.connect(signalEmitted, () => {
const color = colorButton.get_rgba();
const hexColor = rgbaToHex(color);
entry.set_placeholder_text(hexColor);
const colors = settings.get_strv(colorKey);
colors[this._idx] = hexColor;
preferences/batteryWidgetSettings.js:70
entry.connect('changed', () => {
const text = entry.get_text().trim();
if (isValidHexColor(text)) {
const colors = settings.get_strv(colorKey);
colors[this._idx] = text;
settings.set_strv(colorKey, colors);
preferences/batteryWidgetSettings.js:80
settings.connect('changed::level-indicator-custom-colors', () => {
const colors = settings.get_strv(colorKey);
const hex = colors[this._idx] || '#000000';
entry.set_placeholder_text(hex);
colorButton.set_rgba(hexToRgba(hex));
})
preferences/device.js:100
button.connect('clicked', () => {
popover.hide();
const pairedDevice = settings.get_strv('device-list');
const existingPathIndex =
pairedDevice.findIndex(item => JSON.parse(item).path === pathInfo.path);
preferences/device.js:133
quickSettingsSwitchRow.connect('notify::active', () => {
const pairedDevice = settings.get_strv('device-list');
const existingPathIndex =
pairedDevice.findIndex(item => JSON.parse(item).path === pathInfo.path);
if (existingPathIndex !== -1)
preferences/device.js:176
dropDown.connect('notify::selected', () => {
const index = dropDown.get_selected();
const selectedId = indicatorOptions[index].id;
const pairedDevice = settings.get_strv('device-list');
const existingPathIndex =
pairedDevice.fin
preferences/device.js:216
this._customiseButton.connect('clicked', () => {
const parentWindow = this._customiseButton.get_ancestor(Gtk.Window);
const configureWindow =
new ConfigureWindow(settings, this._macAddress,
deviceItem, this._pathInfo, parentWindow);
preferences/device.js:232
this._deleteButton.connect('clicked', () => {
const pairedDevices = settings.get_strv('device-list');
const existingPathIndex = pairedDevices.findIndex(entry => {
const parsedEntry = JSON.parse(entry);
return parsedEntry.path === pathIn
preferences/devices/airpods/devicePrefs.js:29
this._customiseButton.connect('clicked', () => {
const parentWindow = this._customiseButton.get_ancestor(Gtk.Window);
const configureWindow = new ConfigureWindow(settings, this._macAddress,
pathInfo.path, parentWindow, _, true);
configureW
preferences/devices/airpods/devicePrefs.js:45
this._deleteButton.connect('clicked', () => {
const pairedDevices = settings.get_strv('airpods-list');
const existingPathIndex = pairedDevices.findIndex(entry => {
const parsedEntry = JSON.parse(entry);
return parsedEntry.path === pathI
preferences/devices/galaxyBuds/devicePrefs.js:29
this._customiseButton.connect('clicked', () => {
const parentWindow = this._customiseButton.get_ancestor(Gtk.Window);
const configureWindow = new ConfigureWindow(settings, this._macAddress,
pathInfo.path, parentWindow, _, true);
configureW
preferences/devices/galaxyBuds/devicePrefs.js:46
this._deleteButton.connect('clicked', () => {
const pairedDevices = settings.get_strv('galaxy-buds-list');
const existingPathIndex = pairedDevices.findIndex(entry => {
const parsedEntry = JSON.parse(entry);
return parsedEntry.path === p
preferences/devices/nothingBuds/devicePrefs.js:29
this._customiseButton.connect('clicked', () => {
const parentWindow = this._customiseButton.get_ancestor(Gtk.Window);
const configureWindow = new ConfigureWindow(settings, this._macAddress,
pathInfo.path, parentWindow, _, true);
configureW
preferences/devices/nothingBuds/devicePrefs.js:46
this._deleteButton.connect('clicked', () => {
const pairedDevices = settings.get_strv('nothing-buds-list');
const existingPathIndex = pairedDevices.findIndex(entry => {
const parsedEntry = JSON.parse(entry);
return parsedEntry.path ===
preferences/devices/sony/devicePrefs.js:29
this._customiseButton.connect('clicked', () => {
const parentWindow = this._customiseButton.get_ancestor(Gtk.Window);
const configureWindow = new ConfigureWindow(settings, this._macAddress,
pathInfo.path, parentWindow, _, true);
configureW
preferences/devices/sony/devicePrefs.js:45
this._deleteButton.connect('clicked', () => {
const pairedDevices = settings.get_strv('sony-list');
const existingPathIndex = pairedDevices.findIndex(entry => {
const parsedEntry = JSON.parse(entry);
return parsedEntry.path === pathInfo
preferences/gattBas.js:134
button.connect('clicked', () => {
popover.hide();
const pairedDevice = settings.get_strv('gattbas-list');
const existingPathIndex =
pairedDevice.findIndex(item => JSON.parse(item).path === pathInfo.path);
con
preferences/gattBas.js:177
this._customiseButton.connect('clicked', () => {
const parentWindow = this._customiseButton.get_ancestor(Gtk.Window);
const configureWindow =
new ConfigureWindow(settings, this._macAddress, deviceItem,
this._pathInfo, parentWindow);
preferences/gattBas.js:193
this._deleteButton.connect('clicked', () => {
const pairedDevices = settings.get_strv('gattbas-list');
const existingPathIndex = pairedDevices.findIndex(entry => {
const parsedEntry = JSON.parse(entry);
return parsedEntry.path === pathI
preferences/upowerDevices.js:111
aliasRow.connect('apply', row => {
const newAlias = row.text.trim();
const onlineDevice = settings.get_strv('upower-device-list');
const existingPathIndex =
onlineDevice.findIndex(item => JSON.parse(item).path === pathInfo.path);
if (e
preferences/upowerDevices.js:81
button.connect('clicked', () => {
popover.hide();
const onlineDevice = settings.get_strv('upower-device-list');
const existingPathIndex =
onlineDevice.findIndex(item => JSON.parse(item).path === pathInfo.path);
preferences/upowerDevices.js:149
dropDown.connect('notify::selected', () => {
const index = dropDown.get_selected();
const selectedId = indicatorOptions[index].id;
const onlineDevice = settings.get_strv('upower-device-list');
const existingPathIndex =
onlineDe
preferences/upowerDevices.js:190
this._customiseButton.connect('clicked', () => {
const parentWindow = this._customiseButton.get_ancestor(Gtk.Window);
const configureWindow =
new ConfigureWindow(settings, deviceItem, this._pathInfo, parentWindow);
configureWindow.present()
preferences/upowerDevices.js:205
this._deleteButton.connect('clicked', () => {
const upowerDevices = settings.get_strv('upower-device-list');
const existingPathIndex = upowerDevices.findIndex(entry => {
const parsedEntry = JSON.parse(entry);
return parsedEntry.path ===
EGO033 warning
preferences classes should not retain window-scoped objects on instance fields without close-request cleanup
Preferences code stores window-scoped objects on the exported prefs class without `close-request` cleanup.
prefs.js:84
this._sidebarListBox = new Gtk.ListBox()
prefs.js:94
this._contentToastOverlay = new Adw.ToastOverlay()
| Version | Status |
|---|---|
| 45 | Unreviewed |
| 44 | Active |
| 43 | Active |
| 42 | Active |
| 41 | Active |
| 40 | Active |
| 39 | Active |
| 38 | Active |
| 37 | Rejected |
| 36 | Active |
| 35 | Active |
| 34 | Rejected |
| 33 | Rejected |
| 32 | Active |
| 31 | Rejected |
| 30 | Active |
| 29 | Active |
| 28 | Active |
| 27 | Active |
| 26 | Inactive |
| 25 | Inactive |
| 24 | Rejected |
| 23 | Rejected |
| 22 | Inactive |
| 21 | Active |
| 20 | Inactive |
| 19 | Inactive |
| 18 | Inactive |
| 17 | Inactive |
| 16 | Inactive |
| 15 | Inactive |
| 14 | Inactive |
| 13 | Inactive |
| 12 | Inactive |
| 11 | Inactive |
| 10 | Inactive |
| 9 | Inactive |
| 8 | Inactive |
| 7 | Inactive |
| 6 | Inactive |
| 5 | Inactive |
| 4 | Rejected |
| 3 | Inactive |
| 2 | Inactive |
| 1 | Inactive |