Build the Panama Hyprland desktop
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
// Bluetooth device picker. Paired devices first (that's what you actually
|
||||
// reconnect to), then whatever discovery has turned up.
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Bluetooth
|
||||
import qs.config
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// True while the section is open. Discovery is expensive, so it only runs
|
||||
// while the list is on screen — same as the GNOME panel.
|
||||
property bool active: false
|
||||
property alias maxHeight: list.maxHeight
|
||||
|
||||
readonly property var adapter: Bluetooth.defaultAdapter
|
||||
property bool discoveryOwned: false
|
||||
|
||||
implicitHeight: list.implicitHeight
|
||||
|
||||
readonly property var devices: {
|
||||
const list = Bluetooth.devices.values.slice();
|
||||
list.sort((a, b) => {
|
||||
if (a.connected !== b.connected)
|
||||
return a.connected ? -1 : 1;
|
||||
if (a.paired !== b.paired)
|
||||
return a.paired ? -1 : 1;
|
||||
return (a.name || "").localeCompare(b.name || "");
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
function syncDiscovery(): void {
|
||||
if (!root.adapter)
|
||||
return;
|
||||
const shouldDiscover = root.active && root.adapter.enabled;
|
||||
if (shouldDiscover && !root.adapter.discovering) {
|
||||
root.adapter.discovering = true;
|
||||
root.discoveryOwned = true;
|
||||
} else if (!shouldDiscover && root.discoveryOwned && root.adapter.discovering) {
|
||||
root.adapter.discovering = false;
|
||||
root.discoveryOwned = false;
|
||||
}
|
||||
}
|
||||
|
||||
onActiveChanged: root.syncDiscovery()
|
||||
onAdapterChanged: {
|
||||
if (root.active)
|
||||
root.syncDiscovery();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (root.adapter && root.discoveryOwned && root.adapter.discovering)
|
||||
root.adapter.discovering = false;
|
||||
}
|
||||
|
||||
function stateText(device): string {
|
||||
if (device.pairing)
|
||||
return "Pairing…";
|
||||
switch (device.state) {
|
||||
case BluetoothDeviceState.Connecting:
|
||||
return "Connecting…";
|
||||
case BluetoothDeviceState.Disconnecting:
|
||||
return "Disconnecting…";
|
||||
case BluetoothDeviceState.Connected:
|
||||
return device.batteryAvailable ? "Connected · " + Math.round(device.battery * 100) + "%" : "Connected";
|
||||
default:
|
||||
return device.paired ? "Paired" : "";
|
||||
}
|
||||
}
|
||||
|
||||
// Click cycles the obvious next step: pair, connect, disconnect.
|
||||
function activate(device): void {
|
||||
if (device.connected)
|
||||
device.disconnect();
|
||||
else if (device.paired)
|
||||
device.connect();
|
||||
else
|
||||
device.pair();
|
||||
}
|
||||
|
||||
ScrollColumn {
|
||||
id: list
|
||||
anchors.fill: parent
|
||||
maxHeight: 300
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: root.devices.length === 0
|
||||
topPadding: 12
|
||||
bottomPadding: 12
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: {
|
||||
if (!root.adapter)
|
||||
return "No Bluetooth adapter";
|
||||
if (!root.adapter.enabled)
|
||||
return "Bluetooth is off";
|
||||
return "Searching…";
|
||||
}
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.devices
|
||||
|
||||
RowButton {
|
||||
id: deviceRow
|
||||
|
||||
required property var modelData
|
||||
|
||||
width: parent.width
|
||||
// BlueZ reports a plain freedesktop name ("audio-headphones");
|
||||
// the symbolic variant is the one that can be recoloured.
|
||||
icon: deviceRow.modelData.icon !== "" ? deviceRow.modelData.icon + "-symbolic" : "bluetooth-symbolic"
|
||||
iconFallback: "bluetooth-symbolic"
|
||||
label: deviceRow.modelData.name || deviceRow.modelData.address
|
||||
sublabel: root.stateText(deviceRow.modelData)
|
||||
selected: deviceRow.modelData.connected
|
||||
onClicked: root.activate(deviceRow.modelData)
|
||||
|
||||
// Forgetting is destructive enough that it gets its own
|
||||
// control rather than sharing the row click.
|
||||
IconButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: deviceRow.modelData.paired
|
||||
size: 26
|
||||
iconSize: 13
|
||||
icon: "user-trash-symbolic"
|
||||
iconFallback: "window-close-symbolic"
|
||||
onClicked: deviceRow.modelData.forget()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user