mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-06 04:29:34 +02:00
Fix bluetooth panel pair flow, scroll, and X-button click
- Discovered devices now populate via a new `discoveredDevices` filter; the Repeater was referencing a property that did not exist. - Pairing chains into trust + connect at root scope, so freshly-paired devices (especially audio headphones) persist in BlueZ and actually finish connecting instead of flashing "Connected" and disappearing. - Wrap the device list in a Flickable capped at 400px with a scroll bar so crowded RF environments do not grow the popup off-screen. - Reorder MouseAreas so the per-row X button receives clicks instead of the full-row click handler swallowing them.
This commit is contained in:
@@ -13,6 +13,12 @@ Item {
|
||||
|
||||
property bool popupOpen: false
|
||||
|
||||
// Address -> true while we are waiting for a click-initiated pair to land
|
||||
// so we can chain trust + connect at root scope. Doing this in the row's
|
||||
// Connections is racy: the discovered Repeater destroys the delegate the
|
||||
// moment `paired` flips, before the row's handler reliably fires.
|
||||
property var pendingPairAddresses: ({})
|
||||
|
||||
function closePopout() { popupOpen = false }
|
||||
|
||||
readonly property var adapter: Bluetooth.defaultAdapter
|
||||
@@ -38,6 +44,20 @@ Item {
|
||||
return list
|
||||
}
|
||||
|
||||
readonly property var discoveredDevices: {
|
||||
var list = []
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
var d = devices[i]
|
||||
if (!d) continue
|
||||
if (d.paired || d.connected || d.bonded || d.trusted) continue
|
||||
list.push(d)
|
||||
}
|
||||
list.sort(function(a, b) {
|
||||
return (a.name || a.deviceName || a.address || "").localeCompare(b.name || b.deviceName || b.address || "")
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
readonly property string icon: {
|
||||
if (!adapter) return ""
|
||||
if (!adapter.enabled) return ""
|
||||
@@ -49,6 +69,31 @@ Item {
|
||||
implicitWidth: button.implicitWidth
|
||||
implicitHeight: button.implicitHeight
|
||||
|
||||
// Non-visual lifecycle watchers, one per device. Survives popup open/close
|
||||
// and the discovered↔known transition that destroys row delegates.
|
||||
Repeater {
|
||||
model: root.devices
|
||||
Item {
|
||||
required property var modelData
|
||||
visible: false
|
||||
Connections {
|
||||
target: modelData || null
|
||||
function onPairedChanged() {
|
||||
var d = modelData
|
||||
if (!d || !d.paired) return
|
||||
if (!root.pendingPairAddresses[d.address]) return
|
||||
delete root.pendingPairAddresses[d.address]
|
||||
// BlueZ pair() does not auto-trust or auto-connect. Without
|
||||
// trusted, the daemon may drop the entry shortly after pairing,
|
||||
// which makes a freshly-paired device flash "Connected" and then
|
||||
// vanish from the model.
|
||||
d.trusted = true
|
||||
if (!d.connected) d.connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.WidgetButton {
|
||||
id: button
|
||||
anchors.fill: parent
|
||||
@@ -141,49 +186,68 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Paired / known devices.
|
||||
Repeater {
|
||||
model: root.knownDevices
|
||||
DeviceRow {
|
||||
required property var modelData
|
||||
width: parent.width
|
||||
dev: modelData
|
||||
isDiscovered: false
|
||||
}
|
||||
}
|
||||
|
||||
// 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 : []
|
||||
DeviceRow {
|
||||
required property var modelData
|
||||
width: parent.width
|
||||
dev: modelData
|
||||
isDiscovered: true
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// Scrollable device list — capped so a noisy neighborhood doesn't
|
||||
// grow the popup past the screen.
|
||||
Flickable {
|
||||
width: parent.width
|
||||
height: Math.min(deviceList.implicitHeight, 400)
|
||||
contentWidth: width
|
||||
contentHeight: deviceList.implicitHeight
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
||||
|
||||
Column {
|
||||
id: deviceList
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
|
||||
// Paired / known devices.
|
||||
Repeater {
|
||||
model: root.knownDevices
|
||||
DeviceRow {
|
||||
required property var modelData
|
||||
width: deviceList.width
|
||||
dev: modelData
|
||||
isDiscovered: false
|
||||
}
|
||||
}
|
||||
|
||||
// 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 : []
|
||||
DeviceRow {
|
||||
required property var modelData
|
||||
width: deviceList.width
|
||||
dev: modelData
|
||||
isDiscovered: true
|
||||
}
|
||||
}
|
||||
|
||||
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: deviceList.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,6 +329,36 @@ Item {
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
|
||||
MouseArea {
|
||||
id: rowMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: row.dev ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (!row.dev) return
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
if (row.dev.forget) row.dev.forget()
|
||||
return
|
||||
}
|
||||
if (row.isDiscovered) {
|
||||
row.pendingAction = 3
|
||||
row.failureReason = ""
|
||||
failureTimer.restart()
|
||||
root.pendingPairAddresses[row.dev.address] = true
|
||||
row.dev.pair()
|
||||
return
|
||||
}
|
||||
if (!row.dev.trusted) row.dev.trusted = true
|
||||
if (row.isConnected) return // use the X button to disconnect
|
||||
row.pendingAction = 1
|
||||
row.failureReason = ""
|
||||
failureTimer.restart()
|
||||
row.dev.connect()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: rowContent
|
||||
anchors.left: parent.left
|
||||
@@ -382,32 +476,5 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rowMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: row.dev ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if (!row.dev) return
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
if (row.dev.forget) row.dev.forget()
|
||||
return
|
||||
}
|
||||
if (row.isDiscovered) {
|
||||
row.pendingAction = 3
|
||||
row.failureReason = ""
|
||||
failureTimer.restart()
|
||||
row.dev.pair()
|
||||
return
|
||||
}
|
||||
if (row.isConnected) return // use the X button to disconnect
|
||||
row.pendingAction = 1
|
||||
row.failureReason = ""
|
||||
failureTimer.restart()
|
||||
row.dev.connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user