Files
omarchycn/default/quickshell/omarchy-shell/plugins/bar/widgets/bluetoothPanel.qml
T
Ryan Hughes 53f9899d5a Show discovered devices and shrink bluetooth header buttons
- Scan and Manage are now small icon-only buttons in the header beside
  the On/Off pill, with hover tooltips via the new PillButton.tooltipText
  prop (backed by QtQuick.Controls ToolTip).
- New discoveredDevices list pulls everything in Bluetooth.devices that
  isn't paired/connected/bonded/trusted. While scanning, those appear
  under a 'Discovered' header. Click pairs and connects.
- Empty-state text only shows when there are no known and no
  discovered devices, otherwise it would push down real entries.
2026-05-14 02:21:48 -04:00

214 lines
6.8 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Bluetooth
import "../common" as Common
Item {
id: root
property QtObject bar: null
property string moduleName: "bluetoothPanel"
property var settings: ({})
property bool popupOpen: false
function closePopout() { popupOpen = false }
readonly property var adapter: Bluetooth.defaultAdapter
readonly property var devices: Bluetooth.devices ? Bluetooth.devices.values : []
readonly property var connectedDevices: {
var list = []
for (var i = 0; i < devices.length; i++)
if (devices[i] && devices[i].connected) list.push(devices[i])
return list
}
readonly property var knownDevices: {
var list = []
for (var i = 0; i < devices.length; i++) {
var d = devices[i]
if (d && (d.paired || d.connected || d.bonded || d.trusted)) list.push(d)
}
list.sort(function(a, b) {
if (a.connected !== b.connected) return a.connected ? -1 : 1
return (a.name || a.deviceName || "").localeCompare(b.name || b.deviceName || "")
})
return list
}
readonly property string icon: {
if (!adapter) return ""
if (!adapter.enabled) return "󰂲"
if (connectedDevices.length > 0) return "󰂱"
return "󰂯"
}
visible: adapter !== null
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
Common.WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
text: root.icon
horizontalMargin: 8.5
tooltipText: root.adapter ? (root.adapter.enabled ? "Bluetooth: " + root.connectedDevices.length + " connected" : "Bluetooth off") : ""
onPressed: function(b) {
if (b === Qt.RightButton && root.adapter) root.adapter.enabled = !root.adapter.enabled
else if (b === Qt.MiddleButton) root.bar.run("omarchy-launch-bluetooth")
else root.popupOpen = !root.popupOpen
}
}
Common.PopupCard {
anchorItem: button
owner: root
bar: root.bar
open: root.popupOpen
contentWidth: 320
contentHeight: column.implicitHeight + 28
Column {
id: column
anchors.fill: parent
spacing: 10
// Header: title left, on/off toggle right.
Item {
width: parent.width
height: titleText.implicitHeight
Text {
id: titleText
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: "Bluetooth"
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: 13
font.bold: true
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 4
Common.PillButton {
iconText: "󰂳"
tooltipText: !root.adapter ? "" : !root.adapter.enabled ? "Bluetooth is off"
: root.adapter.discovering ? "Stop scanning" : "Scan for devices"
foreground: root.bar.foreground
horizontalPadding: 6
verticalPadding: 4
iconSize: 14
enabled: root.adapter !== null && root.adapter.enabled
opacity: enabled ? 1 : 0.4
active: root.adapter && root.adapter.discovering
onClicked: if (root.adapter) root.adapter.discovering = !root.adapter.discovering
}
Common.PillButton {
iconText: "󱁤"
tooltipText: "Open Impala (TUI)"
foreground: root.bar.foreground
horizontalPadding: 6
verticalPadding: 4
iconSize: 14
onClicked: { root.bar.run("omarchy-launch-bluetooth"); root.popupOpen = false }
}
Common.PillButton {
iconText: root.adapter && root.adapter.enabled ? "󰂯" : "󰂲"
text: root.adapter && root.adapter.enabled ? "On" : "Off"
tooltipText: root.adapter && root.adapter.enabled ? "Turn Bluetooth off" : "Turn Bluetooth on"
foreground: root.bar.foreground
horizontalPadding: 10
verticalPadding: 4
active: root.adapter && root.adapter.enabled
onClicked: if (root.adapter) root.adapter.enabled = !root.adapter.enabled
}
}
}
// Paired / known devices.
Repeater {
model: root.knownDevices
Common.PillButton {
required property var modelData
width: parent.width
iconText: modelData && modelData.connected ? "󰂱" : "󰂯"
text: {
var label = modelData ? (modelData.deviceName || modelData.name || modelData.address || "Device") : ""
if (modelData && modelData.batteryAvailable) label += " " + Math.round(modelData.battery * 100) + "%"
return label
}
tooltipText: modelData && modelData.connected ? "Click to disconnect · right-click to forget"
: "Click to connect · right-click to forget"
foreground: root.bar.foreground
horizontalPadding: 10
verticalPadding: 6
active: modelData && modelData.connected
onClicked: {
if (!modelData) return
if (modelData.connected) modelData.disconnect()
else modelData.connect()
}
onRightClicked: if (modelData) modelData.forget()
}
}
// Discovered (unpaired) devices, only shown while scanning.
Text {
visible: root.adapter && root.adapter.discovering && root.discoveredDevices.length > 0
text: "Discovered"
color: Qt.darker(root.bar.foreground, 1.4)
font.family: root.bar.fontFamily
font.pixelSize: 10
font.bold: true
}
Repeater {
model: root.adapter && root.adapter.discovering ? root.discoveredDevices : []
Common.PillButton {
required property var modelData
width: parent.width
iconText: "󰂯"
text: modelData ? (modelData.deviceName || modelData.name || modelData.address || "Unknown") : ""
tooltipText: "Click to pair and connect"
foreground: Qt.darker(root.bar.foreground, 1.2)
horizontalPadding: 10
verticalPadding: 6
onClicked: {
if (!modelData) return
modelData.pair()
}
}
}
Text {
visible: root.knownDevices.length === 0
&& (!root.adapter || !root.adapter.discovering || root.discoveredDevices.length === 0)
text: !root.adapter ? "No Bluetooth adapter"
: !root.adapter.enabled ? "Turn Bluetooth on to scan"
: root.adapter.discovering ? "Scanning for devices…"
: "No paired devices. Tap the scan icon to find new ones."
color: Qt.darker(root.bar.foreground, 1.5)
font.family: root.bar.fontFamily
font.pixelSize: 11
wrapMode: Text.WordWrap
width: parent.width
}
}
}
}