Krolyxon
Commit a78a1e5

Branches

Refs →

aurora.configquickshellwal-controlBtPage.qml

143 lines · 5.3 KB · QML

1import QtQuick
2import QtQuick.Layouts
3import Quickshell.Bluetooth
4
5ColumnLayout {
6 id: page
7 property bool active: false
8 spacing: 8
9
10 readonly property var adapter: Bluetooth.defaultAdapter
11
12 onActiveChanged: {
13 if (adapter) adapter.discovering = active && adapter.enabled
14 }
15
16 function paired(d) { return d.bonded === true || d.paired === true }
17
18 function looksLikeAddress(d) {
19 return String(d.name).replace(/-/g, ":").toUpperCase() === String(d.address).toUpperCase()
20 }
21
22 function devIcon(d) {
23 var i = String(d.icon || "")
24 if (i.indexOf("head") >= 0) return Theme.iHeadphones
25 if (i.indexOf("mouse") >= 0) return Theme.iMouse
26 if (i.indexOf("keyboard") >= 0) return Theme.iKeyboard
27 if (i.indexOf("phone") >= 0) return Theme.iPhone
28 if (i.indexOf("gaming") >= 0 || i.indexOf("joystick") >= 0) return Theme.iGamepad
29 if (i.indexOf("speaker") >= 0) return Theme.iSpeaker
30 return d.connected ? Theme.iBtConn : Theme.iBt
31 }
32
33 function list() {
34 var src = Bluetooth.devices.values, out = []
35 var scanning = adapter ? adapter.discovering : false
36 for (var i = 0; i < src.length; i++) {
37 var d = src[i]
38 if (d.connected || paired(d)) out.push(d)
39 else if (scanning && !looksLikeAddress(d)) out.push(d)
40 }
41 out.sort(function (a, b) {
42 if (a.connected !== b.connected) return a.connected ? -1 : 1
43 var pa = paired(a), pb = paired(b)
44 if (pa !== pb) return pa ? -1 : 1
45 return String(a.name).localeCompare(String(b.name))
46 })
47 return out
48 }
49 readonly property var devices: list()
50
51 RowLayout {
52 Layout.fillWidth: true
53 spacing: 6
54 Text {
55 Layout.fillWidth: true
56 elide: Text.ElideRight
57 text: !page.adapter ? "no bluetooth adapter"
58 : (!page.adapter.enabled ? "bluetooth is off"
59 : (page.adapter.discovering ? "scanning…" : (page.adapter.name || "adapter ready")))
60 color: page.adapter && page.adapter.enabled ? Theme.accent : Theme.dim
61 font.family: Theme.font
62 font.pixelSize: Theme.fontSize
63 }
64 IconBtn {
65 visible: page.adapter !== null
66 icon: Theme.iSearch
67 active: page.adapter ? page.adapter.discovering : false
68 onClicked: if (page.adapter && page.adapter.enabled) page.adapter.discovering = !page.adapter.discovering
69 }
70 IconBtn {
71 visible: page.adapter !== null
72 icon: page.adapter && page.adapter.enabled ? Theme.iBt : Theme.iBtOff
73 active: page.adapter ? page.adapter.enabled : false
74 onClicked: {
75 if (!page.adapter) return
76 page.adapter.enabled = !page.adapter.enabled
77 if (page.adapter.enabled && page.active) page.adapter.discovering = true
78 }
79 }
80 }
81
82 SectionLabel { text: "devices" }
83
84 Flickable {
85 id: flick
86 Layout.fillWidth: true
87 Layout.fillHeight: true
88 clip: true
89 contentWidth: width
90 contentHeight: col.implicitHeight
91 boundsBehavior: Flickable.StopAtBounds
92
93 ColumnLayout {
94 id: col
95 width: flick.width
96 spacing: 2
97
98 Repeater {
99 model: page.adapter && page.adapter.enabled ? page.devices : []
100 delegate: ListRow {
101 id: row
102 required property var modelData
103 readonly property var dev: modelData
104 property bool pendingConnect: false
105
106 function afterPair() {
107 if (pendingConnect && page.paired(dev)) { pendingConnect = false; dev.connect() }
108 }
109
110 icon: page.devIcon(dev)
111 title: dev.name || dev.address
112 current: dev.connected
113 subtitle: {
114 if (dev.state === BluetoothDeviceState.Connecting) return "connecting…"
115 if (dev.pairing) return "pairing…"
116 if (dev.connected)
117 return "connected" + (dev.batteryAvailable ? " · " + Math.round(dev.battery * 100) + "%" : "")
118 return page.paired(dev) ? "paired" : "available"
119 }
120 onClicked: {
121 if (dev.connected) dev.disconnect()
122 else if (page.paired(dev)) dev.connect()
123 else { pendingConnect = true; dev.trusted = true; dev.pair() }
124 }
125
126 // property name differs between Quickshell versions
127 Connections {
128 target: row.dev
129 ignoreUnknownSignals: true
130 function onBondedChanged() { row.afterPair() }
131 function onPairedChanged() { row.afterPair() }
132 }
133 IconBtn {
134 visible: page.paired(row.dev)
135 icon: Theme.iTrash
136 height: 26
137 onClicked: row.dev.forget()
138 }
139 }
140 }
141 }
142 }
143}