Files

137 lines
5.0 KiB
QML

// Bluetooth, at page size.
//
// Paired devices first, because reconnecting to something you already own is
// what you are here for nine times out of ten; discovered devices follow.
// Battery is shown where BlueZ reports it, which is the one thing people
// routinely open a terminal for.
import QtQuick
import Quickshell
import Quickshell.Bluetooth
import qs.config
import qs.services
Column {
id: root
spacing: 0
// Connected and paired devices always render (they lead the sort, so the
// slice keeps them); unpaired strangers fill up to the cap and the rest
// wait behind the "nearby devices" row. Discovery in a busy room finds
// dozens of phones and TVs that are not yours.
property bool showAll: false
readonly property int visibleCap: 5
readonly property var shown: {
const list = Connectivity.bluetoothDevices;
if (root.showAll || list.length <= root.visibleCap)
return list;
const pinned = list.filter(device => device.connected || device.paired).length;
return list.slice(0, Math.max(root.visibleCap, pinned));
}
readonly property int hiddenCount: Connectivity.bluetoothDevices.length - root.shown.length
function primaryAction(device: var): void {
if (device.connected) {
device.disconnect();
return;
}
if (device.paired) {
device.connect();
return;
}
device.pair();
}
function stateLabel(device: var): string {
if (device.pairing)
return "Pairing…";
if (device.connected)
return device.batteryAvailable
? `Connected · ${Math.round(device.battery * 100)}% battery`
: "Connected";
if (device.paired)
return "Paired";
return device.address || "Not paired";
}
Repeater {
model: root.shown
SettingRow {
id: entry
required property var modelData
required property int index
width: parent.width
label: entry.modelData.name || entry.modelData.address || "Unknown device"
// Armed, the detail names what Forget costs. "Forget" reads like
// "hide from this list"; it is the pairing itself that goes, and
// the device has to be put back into pairing mode to return.
detail: forgetConfirm.armed
? "Drops the pairing — the device must be put back into pairing mode to return"
: root.stateLabel(entry.modelData)
divider: entry.index < root.shown.length - 1
|| root.hiddenCount > 0 || root.showAll
// Room for Keep and the confirming press while the row is armed.
controlWidth: forgetConfirm.armed ? 290 : 200
activatable: !entry.modelData.pairing
onActivated: root.primaryAction(entry.modelData)
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 7
SettingsButton {
anchors.verticalCenter: parent.verticalCenter
enabled: !entry.modelData.pairing
text: entry.modelData.connected
? "Disconnect"
: (entry.modelData.paired ? "Connect" : "Pair")
onClicked: root.primaryAction(entry.modelData)
}
ConfirmAction {
id: forgetConfirm
anchors.verticalCenter: parent.verticalCenter
visible: entry.modelData.paired
actionId: "bluetooth-forget:"
+ (entry.modelData.address || entry.modelData.name || "")
armText: "Forget…"
confirmText: "Forget it"
enabled: !entry.modelData.pairing
onConfirmed: entry.modelData.forget()
}
}
}
}
SettingRow {
width: parent.width
visible: root.hiddenCount > 0
|| (root.showAll && Connectivity.bluetoothDevices.length > root.visibleCap)
label: root.showAll
? "Show fewer devices"
: root.hiddenCount + (root.hiddenCount === 1 ? " more nearby device" : " more nearby devices")
detail: root.showAll ? "" : "Unpaired devices in range, folded to keep the list short"
activatable: true
onActivated: root.showAll = !root.showAll
divider: false
}
SettingRow {
width: parent.width
visible: Connectivity.bluetoothDevices.length === 0
label: !Connectivity.adapter
? "No Bluetooth adapter"
: (Connectivity.adapter.enabled ? "Looking for devices…" : "Bluetooth is off")
detail: Connectivity.adapter && !Connectivity.adapter.enabled
? "Turn it on above to discover devices"
: "Put the device into pairing mode to make it appear"
divider: false
}
}