features/adding_quickshell #6
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user