188 lines
6.1 KiB
QML
188 lines
6.1 KiB
QML
import Quickshell.Bluetooth
|
||
import QtQuick
|
||
import QtQuick.Layouts
|
||
import "../components"
|
||
|
||
// Bluetooth panel – a plain Item so it can live inside an expanding section border.
|
||
Item {
|
||
id: root
|
||
|
||
implicitWidth: 300
|
||
implicitHeight: btCol.implicitHeight + 24
|
||
|
||
// Driven by the popup's open state — only scan for nearby devices while visible.
|
||
property bool scanning: false
|
||
|
||
readonly property var adapter: Bluetooth.defaultAdapter
|
||
readonly property bool powered: adapter?.enabled ?? false
|
||
|
||
// Run device discovery only while the popup is open and the adapter is on.
|
||
Binding {
|
||
target: root.adapter
|
||
property: "discovering"
|
||
value: root.scanning && root.powered
|
||
when: root.adapter !== null
|
||
}
|
||
|
||
// \uf293 = FA bluetooth-b, \uf294 = FA bluetooth
|
||
function devIcon(dev) {
|
||
return dev.connected ? "\uf293" : "\uf294"
|
||
}
|
||
|
||
ColumnLayout {
|
||
id: btCol
|
||
anchors { fill: parent; margins: 12 }
|
||
spacing: 4
|
||
|
||
RowLayout {
|
||
Layout.fillWidth: true
|
||
Layout.bottomMargin: 2
|
||
|
||
Text {
|
||
text: "Bluetooth"
|
||
color: Theme.textDim
|
||
font.pixelSize: 10
|
||
Layout.fillWidth: true
|
||
}
|
||
Text {
|
||
text: root.powered ? "On" : "Off"
|
||
font.pixelSize: 10
|
||
color: root.powered ? Theme.accent : Theme.textDim
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
cursorShape: Qt.PointingHandCursor
|
||
onClicked: if (root.adapter) root.adapter.enabled = !root.adapter.enabled
|
||
}
|
||
}
|
||
}
|
||
|
||
Text {
|
||
visible: !root.powered
|
||
text: "Adapter disabled"
|
||
color: Theme.textDim
|
||
font.pixelSize: 11
|
||
Layout.fillWidth: true
|
||
}
|
||
|
||
// ── Paired devices ────────────────────────────────────────────
|
||
Text {
|
||
visible: root.powered
|
||
text: "Paired"
|
||
color: Theme.textDim
|
||
font.pixelSize: 10
|
||
Layout.fillWidth: true
|
||
Layout.topMargin: 6
|
||
Layout.bottomMargin: 2
|
||
}
|
||
|
||
Repeater {
|
||
model: Bluetooth.devices
|
||
delegate: DeviceRow {
|
||
required property var modelData
|
||
Layout.fillWidth: true
|
||
device: modelData
|
||
visible: root.powered && modelData.bonded
|
||
}
|
||
}
|
||
|
||
// ── Available (discoverable) devices ──────────────────────────
|
||
RowLayout {
|
||
visible: root.powered
|
||
Layout.fillWidth: true
|
||
Layout.topMargin: 6
|
||
Layout.bottomMargin: 2
|
||
|
||
Text {
|
||
text: "Available"
|
||
color: Theme.textDim
|
||
font.pixelSize: 10
|
||
Layout.fillWidth: true
|
||
}
|
||
Text {
|
||
visible: root.adapter?.discovering ?? false
|
||
text: "scanning…"
|
||
color: Theme.textDim
|
||
font.pixelSize: 10
|
||
}
|
||
}
|
||
|
||
Repeater {
|
||
model: Bluetooth.devices
|
||
delegate: DeviceRow {
|
||
required property var modelData
|
||
Layout.fillWidth: true
|
||
device: modelData
|
||
// Only named devices — hides the swarm of random-address BLE beacons.
|
||
visible: root.powered && !modelData.bonded
|
||
&& (modelData.deviceName ?? "").length > 0
|
||
}
|
||
}
|
||
}
|
||
|
||
component DeviceRow: RowLayout {
|
||
id: drow
|
||
required property var device
|
||
spacing: 8
|
||
implicitHeight: 28
|
||
|
||
// Once a freshly-paired device bonds, trust it (so it can reconnect
|
||
// without an agent) and bring up the connection automatically.
|
||
Connections {
|
||
target: drow.device
|
||
function onBondedChanged() {
|
||
if (!drow.device.bonded) return
|
||
if (!drow.device.trusted) drow.device.trusted = true
|
||
if (!drow.device.connected) drow.device.connect()
|
||
}
|
||
}
|
||
|
||
Text {
|
||
text: root.devIcon(drow.device)
|
||
font.family: "JetBrainsMono Nerd Font Mono"
|
||
font.pixelSize: 13
|
||
color: drow.device.connected ? Theme.accent : Theme.textDim
|
||
}
|
||
|
||
Text {
|
||
text: drow.device.name || drow.device.address || "?"
|
||
font.pixelSize: 11
|
||
color: drow.device.connected ? Theme.text : Theme.textDim
|
||
Layout.fillWidth: true
|
||
elide: Text.ElideRight
|
||
}
|
||
|
||
Text {
|
||
visible: drow.device.batteryAvailable
|
||
text: Math.round(drow.device.battery * 100) + "%"
|
||
font.pixelSize: 10
|
||
color: Theme.textDim
|
||
Layout.preferredWidth: 30
|
||
horizontalAlignment: Text.AlignRight
|
||
}
|
||
|
||
Text {
|
||
text: drow.device.pairing ? "pairing…"
|
||
: drow.device.state === BluetoothDeviceState.Connecting ? "…"
|
||
: drow.device.state === BluetoothDeviceState.Disconnecting ? "…"
|
||
: !drow.device.bonded ? "Pair"
|
||
: drow.device.connected ? "Disconnect"
|
||
: "Connect"
|
||
font.pixelSize: 10
|
||
color: drow.device.connected ? Theme.text : Theme.textDim
|
||
Layout.preferredWidth: 70
|
||
horizontalAlignment: Text.AlignRight
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
cursorShape: Qt.PointingHandCursor
|
||
onClicked: {
|
||
const d = drow.device
|
||
if (d.pairing) d.cancelPair()
|
||
else if (!d.bonded) d.pair()
|
||
else if (d.connected) d.disconnect()
|
||
else d.connect()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|