Files
Panama/config/dot/quickshell/modules/quicksettings/BluetoothList.qml
T
Gabriel Brown d96863b687 Convert British spellings to American across the repo
colour -> color, behaviour -> behavior, centre -> center, favourite ->
favorite, and about twenty other pairs, applied consistently across
comments, docs, error/UI copy, and a handful of QML identifiers that
used the British spelling as their actual name: SystemSettings'
serialiseValue/serialiseTable/normaliseGradient, Displays'
normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's
serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's
_normalise helper, and ShortcutCapture's cancelled signal (with its
onCancelled handler in ShortcutsPage.qml). Every call site and the two
tests that assert on the literal source text (settings-ownership and
settings-backup-live contracts) were updated in lockstep.

Left untouched: config/dot/espanso/match/packages/misspell-en/ is a
vendored third-party autocorrect dictionary -- its entries are typo
corrections, not our prose, and rewriting them would fight the
package's own purpose (and any future re-sync from upstream).

The already-American `favorites` property (Home page pinned
accessories) was never actually misspelled -- only nearby comments and
error strings said "favourites" -- so no data migration was needed
there.

Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
2026-08-19 08:07:55 -04:00

140 lines
4.5 KiB
QML

// Bluetooth device picker. Paired devices first (that's what you actually
// reconnect to), then whatever discovery has turned up.
import QtQuick
import Quickshell
import Quickshell.Widgets
import Quickshell.Bluetooth
import qs.config
Item {
id: root
// True while the section is open. Discovery is expensive, so it only runs
// while the list is on screen — same as the GNOME panel.
property bool active: false
property alias maxHeight: list.maxHeight
readonly property var adapter: Bluetooth.defaultAdapter
property bool discoveryOwned: false
implicitHeight: list.implicitHeight
readonly property var devices: {
const list = Bluetooth.devices.values.slice();
list.sort((a, b) => {
if (a.connected !== b.connected)
return a.connected ? -1 : 1;
if (a.paired !== b.paired)
return a.paired ? -1 : 1;
return (a.name || "").localeCompare(b.name || "");
});
return list;
}
function syncDiscovery(): void {
if (!root.adapter)
return;
const shouldDiscover = root.active && root.adapter.enabled;
if (shouldDiscover && !root.adapter.discovering) {
root.adapter.discovering = true;
root.discoveryOwned = true;
} else if (!shouldDiscover && root.discoveryOwned && root.adapter.discovering) {
root.adapter.discovering = false;
root.discoveryOwned = false;
}
}
onActiveChanged: root.syncDiscovery()
onAdapterChanged: {
if (root.active)
root.syncDiscovery();
}
Component.onDestruction: {
if (root.adapter && root.discoveryOwned && root.adapter.discovering)
root.adapter.discovering = false;
}
function stateText(device): string {
if (device.pairing)
return "Pairing…";
switch (device.state) {
case BluetoothDeviceState.Connecting:
return "Connecting…";
case BluetoothDeviceState.Disconnecting:
return "Disconnecting…";
case BluetoothDeviceState.Connected:
return device.batteryAvailable ? "Connected · " + Math.round(device.battery * 100) + "%" : "Connected";
default:
return device.paired ? "Paired" : "";
}
}
// Click cycles the obvious next step: pair, connect, disconnect.
function activate(device): void {
if (device.connected)
device.disconnect();
else if (device.paired)
device.connect();
else
device.pair();
}
ScrollColumn {
id: list
anchors.fill: parent
maxHeight: 300
Text {
width: parent.width
visible: root.devices.length === 0
topPadding: 12
bottomPadding: 12
horizontalAlignment: Text.AlignHCenter
text: {
if (!root.adapter)
return "No Bluetooth adapter";
if (!root.adapter.enabled)
return "Bluetooth is off";
return "Searching…";
}
color: Theme.fgMuted
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSize
}
Repeater {
model: root.devices
RowButton {
id: deviceRow
required property var modelData
width: parent.width
// BlueZ reports a plain freedesktop name ("audio-headphones");
// the symbolic variant is the one that can be recolored.
icon: deviceRow.modelData.icon !== "" ? deviceRow.modelData.icon + "-symbolic" : "bluetooth-symbolic"
iconFallback: "bluetooth-symbolic"
label: deviceRow.modelData.name || deviceRow.modelData.address
sublabel: root.stateText(deviceRow.modelData)
selected: deviceRow.modelData.connected
onClicked: root.activate(deviceRow.modelData)
// Forgetting is destructive enough that it gets its own
// control rather than sharing the row click.
IconButton {
anchors.verticalCenter: parent.verticalCenter
visible: deviceRow.modelData.paired
size: 26
iconSize: 13
icon: "user-trash-symbolic"
iconFallback: "window-close-symbolic"
onClicked: deviceRow.modelData.forget()
}
}
}
}
}