import QtQuick import Quickshell.Bluetooth import "bluetoothNative.js" as BluetoothNative Item { id: root property string error: "" property var lastAction: ({}) property int refreshTicks: 0 signal refreshed readonly property string nativeApi: "Quickshell.Bluetooth.Bluetooth.defaultAdapter BluetoothAdapter BluetoothDevice" readonly property var boundedActions: ["scan", "toggle", "connect", "disconnect", "pair", "forget", "trust", "block"] readonly property bool nativeActionRoute: true function nativeAdapter() { return BluetoothNative.defaultAdapter(Bluetooth); } function deviceId(device) { return String(device.id || device.address || device.deviceName || device.name || "device"); } function deviceName(device) { return String(device.name || device.deviceName || device.address || device.id || "device"); } function cloneDevice(device) { return { "id": deviceId(device), "name": deviceName(device), "type": String(device.type || device.icon || "device"), "connected": !!device.connected, "paired": !!device.paired, "trusted": !!device.trusted, "blocked": !!device.blocked, "battery": Number(device.batteryAvailable === false ? -1 : (device.battery ?? -1)) }; } function nativeDevices() { return BluetoothNative.devices(Bluetooth).map(device => { return ({ "id": deviceId(device), "name": deviceName(device), "type": String(device.icon || "device"), "connected": !!device.connected, "paired": !!device.paired, "trusted": !!device.trusted, "blocked": !!device.blocked, "battery": Number(device.batteryAvailable ? Math.round(Number(device.battery || 0) * 100) : -1), "nativeObject": device }); }); } function sortedDevices(rows) { return rows.slice(0).sort((a, b) => { if (!!a.connected !== !!b.connected) return a.connected ? -1 : 1; if (!!a.paired !== !!b.paired) return a.paired ? -1 : 1; return deviceName(a).localeCompare(deviceName(b)); }); } function devicesList() { return sortedDevices(nativeDevices()); } function devicesById() { const indexed = {}; const list = devicesList(); for (let i = 0; i < list.length; i++) indexed[deviceId(list[i])] = cloneDevice(list[i]); return indexed; } function adapterEnabled() { const adapter = nativeAdapter(); return !!(adapter && adapter.enabled); } function discovering() { const adapter = nativeAdapter(); return !!(adapter && adapter.discovering); } function role() { if (error.length > 0) return "error"; if (!adapterEnabled()) return "ui.minor"; return discovering() ? "warning" : "ui.accent"; } function connectedCount() { let count = 0; const list = devicesList(); for (let i = 0; i < list.length; i++) if (list[i].connected) { count++; } return count; } function stateObject() { return { "backend": "Bluetooth.qml", "usesNativeQuickshellBluetooth": true, "nativeApi": nativeApi, "nativeActionRoute": nativeActionRoute, "nativeAdapterAvailable": !!nativeAdapter(), "nativeAdapterName": nativeAdapter() ? String(nativeAdapter().name) : "", "helperProcesses": [], "serviceOwnedActions": true, "boundedActions": boundedActions, "error": error, "adapter": { "id": !nativeAdapter() ? "" : String(nativeAdapter().adapterId || ""), "state": adapterEnabled() ? "powered" : "disabled", "discovering": discovering(), "discoverable": !nativeAdapter() ? false : !!nativeAdapter().discoverable, "pairable": !nativeAdapter() ? false : !!nativeAdapter().pairable }, "devices": devicesList().map(device => { return cloneDevice(device); }), "devicesById": devicesById(), "compact": { "label": adapterEnabled() ? String(connectedCount()) + " connected" : "bluetooth off", "role": role() }, "lastAction": lastAction }; } function stateJson() { return JSON.stringify(stateObject()); } function scheduleRefresh() { refreshTicks = 8; refreshTimer.restart(); } function scan() { const adapter = nativeAdapter(); const wasEnabled = adapterEnabled(); if (adapter) { adapter.enabled = true; if (wasEnabled) adapter.discovering = true; } error = ""; lastAction = { "kind": "scan", "poweredBeforeScan": wasEnabled, "nativeActionRoute": nativeActionRoute }; scheduleRefresh(); return stateJson(); } function toggle(on) { if (nativeAdapter()) { nativeAdapter().enabled = !!on; if (!on) nativeAdapter().discovering = false; } error = ""; lastAction = { "kind": "toggle", "enabled": adapterEnabled(), "nativeActionRoute": nativeActionRoute }; scheduleRefresh(); return stateJson(); } function deviceAction(kind, id) { const actionName = String(kind); const deviceIdValue = String(id); { const list = devicesList(); for (let i = 0; i < list.length; i++) { if (deviceId(list[i]) !== deviceIdValue || !list[i].nativeObject) continue; const device = list[i].nativeObject; if (actionName === "connect") device.connect(); if (actionName === "disconnect") device.disconnect(); if (actionName === "pair") device.pair(); if (actionName === "forget") device.forget(); if (actionName === "trust") device.trusted = true; if (actionName === "block") device.blocked = true; } } error = ""; lastAction = { "kind": actionName, "id": deviceIdValue, "nativeActionRoute": nativeActionRoute }; scheduleRefresh(); return stateJson(); } Timer { id: refreshTimer interval: 500 repeat: true onTriggered: { root.refreshed(); root.refreshTicks -= 1; if (root.refreshTicks <= 0) stop(); } } }