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() } } } } }