import QtQuick import Quickshell import Quickshell.Bluetooth import qs.Common import qs.Services import qs.Widgets Item { id: widget readonly property var adapter: BluetoothService.adapter readonly property bool adapterEnabled: BluetoothService.enabled readonly property var devices: BluetoothService.pairedDevices || [] function batteryText(device) { if (device && device.batteryAvailable && device.battery > 0) return Math.round(device.battery * 100) + "%"; return ""; } Row { id: header anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right height: 34 spacing: Theme.spacingS DankIcon { anchors.verticalCenter: parent.verticalCenter name: widget.adapterEnabled ? "bluetooth" : "bluetooth_disabled" size: Theme.iconSize - 6 color: widget.adapterEnabled ? Theme.primary : Theme.surfaceVariantText } StyledText { anchors.verticalCenter: parent.verticalCenter width: parent.width - Theme.iconSize - scanButton.width - toggle.width - parent.spacing * 3 text: widget.adapter ? (widget.adapterEnabled ? "On" : "Off") : "No adapter" font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText elide: Text.ElideRight } DankActionButton { id: scanButton anchors.verticalCenter: parent.verticalCenter iconName: "search" buttonSize: 30 iconSize: 17 iconColor: BluetoothService.discovering ? Theme.primary : Theme.surfaceText enabled: widget.adapterEnabled onClicked: { if (widget.adapter) widget.adapter.discovering = !widget.adapter.discovering; } } DankToggle { id: toggle anchors.verticalCenter: parent.verticalCenter hideText: true checked: widget.adapterEnabled enabled: widget.adapter !== null onToggled: isChecked => { if (widget.adapter) widget.adapter.enabled = isChecked; } } } DankListView { id: deviceList anchors.top: header.bottom anchors.topMargin: Theme.spacingXS anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom clip: true spacing: Theme.spacingXS visible: widget.adapterEnabled && widget.devices.length > 0 model: widget.devices delegate: Rectangle { id: deviceItem required property var modelData readonly property bool isConnected: modelData.connected readonly property bool isBusy: BluetoothService.isDeviceBusy(modelData) width: deviceList.width height: 46 radius: Theme.cornerRadius color: isConnected ? Theme.primarySelected : (deviceArea.containsMouse ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceLight, Theme.popupTransparency)) border.color: isConnected ? Theme.primary : Theme.outlineLight border.width: isConnected ? 1 : Theme.layerOutlineWidth DankIcon { id: deviceIcon anchors.left: parent.left anchors.leftMargin: Theme.spacingM anchors.verticalCenter: parent.verticalCenter name: BluetoothService.getDeviceIcon(deviceItem.modelData) size: Theme.iconSize - 6 color: deviceItem.isConnected ? Theme.primary : Theme.surfaceText } Column { anchors.left: deviceIcon.right anchors.leftMargin: Theme.spacingS anchors.right: busyIndicator.left anchors.rightMargin: Theme.spacingS anchors.verticalCenter: parent.verticalCenter spacing: 0 StyledText { width: parent.width text: deviceItem.modelData.name || deviceItem.modelData.address font.pixelSize: Theme.fontSizeMedium font.weight: deviceItem.isConnected ? Font.Medium : Font.Normal color: Theme.surfaceText elide: Text.ElideRight } StyledText { width: parent.width text: { const battery = widget.batteryText(deviceItem.modelData); const state = deviceItem.isBusy ? "Working…" : (deviceItem.isConnected ? "Connected" : "Disconnected"); return battery !== "" ? state + " · " + battery : state; } font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText elide: Text.ElideRight } } DankSpinner { id: busyIndicator anchors.right: parent.right anchors.rightMargin: Theme.spacingM anchors.verticalCenter: parent.verticalCenter size: 18 running: deviceItem.isBusy visible: deviceItem.isBusy } MouseArea { id: deviceArea anchors.fill: parent hoverEnabled: true enabled: !deviceItem.isBusy cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor onClicked: { if (deviceItem.isConnected) deviceItem.modelData.disconnect(); else BluetoothService.connectDeviceWithTrust(deviceItem.modelData); } } } } Column { anchors.centerIn: parent spacing: Theme.spacingS visible: !deviceList.visible DankIcon { anchors.horizontalCenter: parent.horizontalCenter name: "bluetooth_disabled" size: Theme.iconSizeLarge color: Theme.surfaceVariantText } StyledText { anchors.horizontalCenter: parent.horizontalCenter text: widget.adapterEnabled ? "No paired devices" : "Bluetooth is off" font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText } } }