features/monitor_options #5

Merged
thomas merged 2 commits from features/monitor_options into features/adding_quickshell 2026-08-27 14:46:07 +01:00
Showing only changes of commit e5809efc6a - Show all commits

View File

@@ -79,6 +79,7 @@ Item {
bonded: !!d.bonded, bonded: !!d.bonded,
trusted: !!d.trusted, trusted: !!d.trusted,
pairing: !!d.pairing, pairing: !!d.pairing,
state: d.state,
batteryAvailable: !!d.batteryAvailable, batteryAvailable: !!d.batteryAvailable,
battery: d.battery || 0 battery: d.battery || 0
} }
@@ -105,19 +106,107 @@ Item {
return null return null
} }
// --- Action feedback ---------------------------------------------------
// BlueZ operations here are fire-and-forget from QML's side — connect()/
// disconnect()/pair()/forget() report failure only via a C++-side log
// line (see quickshell's bluetooth/device.cpp), nothing reaches QML. So
// "did it work" has to be inferred from the device's own properties
// instead of a success/failure callback. Shown as a line right under the
// specific row it's about (DeviceRow.statusText below) rather than a
// panel-wide banner — actionStatusAddress is how a row knows a message
// is meant for it; since it's address-keyed rather than tied to a
// particular DeviceRow instance, the message still finds its row even
// when the device jumps sections (Paired → Connected on a successful
// connect, say), because that's a new DeviceRow for the same address.
// - busyStatus is purely reactive — it just reads pairing/state off
// whichever row is currently mid-operation, however that operation
// was triggered. It's not shown directly (each row already derives
// the same "Connecting…"/"Disconnecting…" off its own dev.state);
// it only exists here to tell resolvePendingAction when to fire.
// - pendingAction+the busyStatus watcher below is how a *specific*
// click gets its own outcome message: it snapshots what was asked
// for, waits for busyStatus to clear, then checks whether the
// expected end state (connected/paired) actually landed.
readonly property string busyStatus: {
for (const r of root.deviceRows) {
if (r.pairing) return "pairing:" + r.address
if (r.state === BluetoothDeviceState.Connecting) return "connecting:" + r.address
if (r.state === BluetoothDeviceState.Disconnecting) return "disconnecting:" + r.address
}
return ""
}
property var pendingAction: null
property string actionStatus: ""
property string actionStatusAddress: ""
// Checks whether pendingAction's expected end state actually landed and
// reports it, then clears pendingAction. Called both when busyStatus
// clears normally and from pendingTimeoutTimer below — an operation
// that resolves inside a single binding re-evaluation (a device already
// in range reconnecting near-instantly, say) can flip busyStatus from ""
// straight back to "" without ever visibly changing, which would
// otherwise leave pendingAction stuck forever and misattribute whatever
// busyStatus transition happens to come along next.
function resolvePendingAction() {
if (!root.pendingAction) return
const p = root.pendingAction
root.pendingAction = null
const d = root.deviceForAddress(p.address)
root.actionStatusAddress = p.address
if (p.kind === "disconnect") {
root.actionStatus = (d && !d.connected) ? "Disconnected" : "Couldn't disconnect"
} else if (p.kind === "pair") {
root.actionStatus = (d && d.paired) ? "Paired" : "Couldn't pair"
} else {
root.actionStatus = (d && d.connected) ? "Connected" : "Couldn't connect"
}
actionStatusTimer.restart()
}
onBusyStatusChanged: if (root.busyStatus === "") root.resolvePendingAction()
Timer {
id: actionStatusTimer
interval: 3500
repeat: false
onTriggered: root.actionStatus = ""
}
// Backstop for the "resolved before busyStatus ever visibly changed"
// case above, and a genuine timeout for an operation that's just stuck
// (device out of range, etc.) — either way, restarted whenever
// pendingAction is (re)set, so it always reflects the latest click.
Timer {
id: pendingTimeoutTimer
interval: 8000
repeat: false
onTriggered: root.resolvePendingAction()
}
function connectDevice(row) { function connectDevice(row) {
const d = root.deviceForAddress(row.address) const d = root.deviceForAddress(row.address)
if (!d) return if (!d) return
// Already-paired devices just reconnect; a fresh discovery result // Already-paired devices just reconnect; a fresh discovery result
// has to pair first (BlueZ's own agent handles the SSP/passkey // has to pair first (BlueZ's own agent handles the SSP/passkey
// handshake — there's no PIN-entry surface exposed by this API). // handshake — there's no PIN-entry surface exposed by this API).
if (row.paired || row.bonded || row.trusted) d.connect() if (row.paired || row.bonded || row.trusted) {
else d.pair() root.pendingAction = { address: row.address, kind: "connect" }
pendingTimeoutTimer.restart()
d.connect()
} else {
root.pendingAction = { address: row.address, kind: "pair" }
pendingTimeoutTimer.restart()
d.pair()
}
} }
function disconnectDevice(row) { function disconnectDevice(row) {
const d = root.deviceForAddress(row.address) const d = root.deviceForAddress(row.address)
if (d) d.disconnect() if (!d) return
root.pendingAction = { address: row.address, kind: "disconnect" }
pendingTimeoutTimer.restart()
d.disconnect()
} }
function forgetDevice(row) { function forgetDevice(row) {
@@ -125,11 +214,18 @@ Item {
if (!d) return if (!d) return
if (d.connected) d.disconnect() if (d.connected) d.disconnect()
d.forget() d.forget()
// No feedback line here — forgetting removes this row from every
// list immediately, so there's nothing left to show a message
// under. The row vanishing is the confirmation.
} }
function setTrusted(row, trusted) { function setTrusted(row, trusted) {
const d = root.deviceForAddress(row.address) const d = root.deviceForAddress(row.address)
if (d) d.trusted = trusted if (!d) return
d.trusted = trusted
root.actionStatusAddress = row.address
root.actionStatus = trusted ? "Trusting" : "No longer trusting"
actionStatusTimer.restart()
} }
readonly property bool anyConnected: root.connectedRows.length > 0 readonly property bool anyConnected: root.connectedRows.length > 0
@@ -301,6 +397,17 @@ Item {
Text { Text {
readonly property string statusText: { readonly property string statusText: {
if (devRow.dev.pairing) return "Pairing…" if (devRow.dev.pairing) return "Pairing…"
if (devRow.dev.state === BluetoothDeviceState.Connecting) return "Connecting…"
if (devRow.dev.state === BluetoothDeviceState.Disconnecting) return "Disconnecting…"
// The outcome of the last action taken on this
// specific device (connect/disconnect/pair/trust) —
// see the "Action feedback" comment above
// root.busyStatus. Matched by address rather than by
// this being "the same row" that was clicked, since
// a successful connect/disconnect moves the device
// to a different section (a new DeviceRow entirely).
if (root.actionStatusAddress === devRow.dev.address && root.actionStatus !== "")
return root.actionStatus
if (devRow.dev.connected && devRow.dev.batteryAvailable) if (devRow.dev.connected && devRow.dev.batteryAvailable)
return Math.round(devRow.dev.battery * 100) + "%" return Math.round(devRow.dev.battery * 100) + "%"
if (devRow.dev.connected) return "Connected" if (devRow.dev.connected) return "Connected"