diff --git a/Modules/Bar/Bluetooth.qml b/Modules/Bar/Bluetooth.qml index c8792d9..6536a82 100644 --- a/Modules/Bar/Bluetooth.qml +++ b/Modules/Bar/Bluetooth.qml @@ -15,18 +15,11 @@ NIconButton { showBorder: false visible: bluetoothEnabled - Component.onCompleted: { - Logger.log("Bluetooth", "Component loaded, bluetoothEnabled:", bluetoothEnabled) - Logger.log("Bluetooth", "BluetoothService available:", typeof BluetoothService !== 'undefined') - if (typeof BluetoothService !== 'undefined') { - Logger.log("Bluetooth", "Connected devices:", BluetoothService.connectedDevices.length) - } - } icon: { // Show different icons based on connection status - if (BluetoothService.connectedDevices.length > 0) { + if (BluetoothService.pairedDevices.length > 0) { return "bluetooth_connected" - } else if (BluetoothService.isDiscovering) { + } else if (BluetoothService.discovering) { return "bluetooth_searching" } else { return "bluetooth" diff --git a/Modules/Bar/BluetoothMenu.qml b/Modules/Bar/BluetoothMenu.qml index 9153de8..6da9e29 100644 --- a/Modules/Bar/BluetoothMenu.qml +++ b/Modules/Bar/BluetoothMenu.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell +import Quickshell.Bluetooth import Quickshell.Wayland import qs.Commons import qs.Services @@ -38,7 +39,7 @@ NLoader { onVisibleChanged: { if (visible && Settings.data.network.bluetoothEnabled) { // Always refresh devices when menu opens to get fresh device objects - BluetoothService.refreshDevices() + BluetoothService.adapter.discovering = true } else if (bluetoothMenuRect.opacityValue > 0) { // Start hide animation bluetoothMenuRect.scaleValue = 0.8 @@ -63,11 +64,14 @@ NLoader { Rectangle { id: bluetoothMenuRect + + property var deviceData: null + color: Color.mSurface radius: Style.radiusLarge * scaling border.color: Color.mOutlineVariant border.width: Math.max(1, Style.borderThin * scaling) - width: 340 * scaling + width: 400 * scaling height: 500 * scaling anchors.top: parent.top anchors.right: parent.right @@ -81,6 +85,11 @@ NLoader { scale: scaleValue opacity: opacityValue + // Prevent closing the window if clicking inside it + MouseArea { + anchors.fill: parent + } + // Animate in when component is completed Component.onCompleted: { scaleValue = 1.0 @@ -107,6 +116,7 @@ NLoader { anchors.margins: Style.marginLarge * scaling spacing: Style.marginMedium * scaling + // HEADER RowLayout { Layout.fillWidth: true spacing: Style.marginMedium * scaling @@ -121,18 +131,19 @@ NLoader { NText { text: "Bluetooth" font.pointSize: Style.fontSizeLarge * scaling - font.bold: true + font.weight: Style.fontWeightBold color: Color.mOnSurface Layout.fillWidth: true } NIconButton { - icon: "refresh" + icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop_circle" : "refresh" tooltipText: "Refresh Devices" sizeMultiplier: 0.8 - enabled: Settings.data.network.bluetoothEnabled && !BluetoothService.isDiscovering onClicked: { - BluetoothService.refreshDevices() + if (BluetoothService.adapter) { + BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering + } } } @@ -148,247 +159,311 @@ NLoader { NDivider {} - Item { - Layout.fillWidth: true - Layout.fillHeight: true + // Available devices + Column { + id: column - // Loading indicator - ColumnLayout { - anchors.centerIn: parent - visible: Settings.data.network.bluetoothEnabled && BluetoothService.isDiscovering - spacing: Style.marginMedium * scaling + width: parent.width + spacing: Style.marginMedium * scaling + visible: BluetoothService.adapter && BluetoothService.adapter.enabled + - NBusyIndicator { - running: BluetoothService.isDiscovering - color: Color.mPrimary - size: Style.baseWidgetSize * scaling - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: "Scanning for devices..." - font.pointSize: Style.fontSizeNormal * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter - } - } - - // Bluetooth disabled message - ColumnLayout { - anchors.centerIn: parent - visible: !Settings.data.network.bluetoothEnabled + RowLayout { + width: parent.width spacing: Style.marginMedium * scaling NText { - text: "bluetooth_disabled" - font.family: "Material Symbols Outlined" - font.pointSize: Style.fontSizeXXL * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: "Bluetooth is disabled" + text: "Available Devices" font.pointSize: Style.fontSizeLarge * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: "Enable Bluetooth to see available devices" - font.pointSize: Style.fontSizeNormal * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter + color: Color.mOnSurface + font.weight: Style.fontWeightMedium + anchors.verticalCenter: parent.verticalCenter } } - // Device list - ListView { - id: deviceList - anchors.fill: parent - visible: Settings.data.network.bluetoothEnabled && !BluetoothService.isDiscovering - model: [] - spacing: Style.marginMedium * scaling - clip: true + Repeater { + model: { + if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) + return [] - // Combine all devices into a single list for the ListView - property var allDevices: { - const devices = [] - - // Add connected devices first - for (const device of BluetoothService.connectedDevices) { - devices.push({ - "device": device, - "type": 'connected', - "section": 'Connected Devices' - }) - } - - // Add paired devices - for (const device of BluetoothService.pairedDevices) { - devices.push({ - "device": device, - "type": 'paired', - "section": 'Paired Devices' - }) - } - - // Add available devices - for (const device of BluetoothService.availableDevices) { - devices.push({ - "device": device, - "type": 'available', - "section": 'Available Devices' - }) - } - - return devices + var filtered = Bluetooth.devices.values.filter(dev => { + return dev && !dev.paired && !dev.pairing + && !dev.blocked && (dev.signalStrength === undefined + || dev.signalStrength > 0) + }) + return BluetoothService.sortDevices(filtered) } - // Update model when devices change - onAllDevicesChanged: { - deviceList.model = allDevices - } + Rectangle { + property bool canConnect: BluetoothService.canConnect(modelData) + property bool isBusy: BluetoothService.isDeviceBusy(modelData) - // Also watch for changes in the service arrays - Connections { - target: BluetoothService - function onConnectedDevicesChanged() { - deviceList.model = deviceList.allDevices + width: parent.width + height: 70 + radius: Style.radiusMedium * scaling + color: { + if (availableDeviceArea.containsMouse && !isBusy) + return Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.08) + + if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting) + return Qt.rgba(Color.mError.r, Color.mError.g, Color.mError.b, 0.12) + + if (modelData.blocked) + return Color.mError + + return Color.mSurfaceVariant } - function onPairedDevicesChanged() { - deviceList.model = deviceList.allDevices + border.color: { + if (modelData.pairing) + return Color.mError + + if (modelData.blocked) + return Color.mError + + return Color.mOutline } - function onAvailableDevicesChanged() { - deviceList.model = deviceList.allDevices - } - } + border.width: 1 - delegate: Item { - width: parent ? parent.width : 0 - height: Style.baseWidgetSize * 1.5 * scaling + Row { + anchors.left: parent.left + anchors.leftMargin: Style.marginMedium * scaling + anchors.verticalCenter: parent.verticalCenter + spacing: Style.marginSmall * scaling - Rectangle { - anchors.fill: parent - radius: Style.radiusMedium * scaling - color: modelData.device.connected ? Color.mPrimary : (deviceMouseArea.containsMouse ? Color.mTertiary : Color.transparent) + NText { + text: BluetoothService.getDeviceIcon(modelData) + font.family: "Material Symbols Outlined" + font.pointSize: Style.fontSizeXL * scaling + color: { + if (modelData.pairing) + return Color.mError - RowLayout { - anchors.fill: parent - anchors.margins: Style.marginSmall * scaling - spacing: Style.marginSmall * scaling + if (modelData.blocked) + return Color.mError + + return Color.mOnSurface + } + anchors.verticalCenter: parent.verticalCenter + } + + Column { + spacing: 2 + anchors.verticalCenter: parent.verticalCenter NText { - text: BluetoothService.getDeviceIcon(modelData.device) - font.family: "Material Symbols Outlined" - font.pointSize: Style.fontSizeXL * scaling - color: modelData.device.connected ? Color.mSurface : (deviceMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface) + text: modelData.name || modelData.deviceName + font.pointSize: Style.fonttSizeMedium * scaling + color: { + if (modelData.pairing) + return Color.mError + + if (modelData.blocked) + return Color.mError + + return Color.mOnSurface + } + font.weight: modelData.pairing ? Style.fontWeightMedium : Font.Normal } - ColumnLayout { - Layout.fillWidth: true + Row { spacing: Style.marginTiny * scaling - NText { - text: modelData.device.name || modelData.device.deviceName || "Unknown Device" - font.pointSize: Style.fontSizeNormal * scaling - elide: Text.ElideRight - Layout.fillWidth: true - color: modelData.device.connected ? Color.mSurface : (deviceMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface) - } + Row { + spacing: Style.marginSmall * spacing - NText { - text: { - if (modelData.device.connected) { - return "Connected" - } else if (modelData.device.paired) { - return "Paired" - } else { - return "Available" + NText { + text: { + if (modelData.pairing) + return "Pairing..." + + if (modelData.blocked) + return "Blocked" + + return BluetoothService.getSignalStrength(modelData) + } + font.pointSize: Style.fontSizeSmall * scaling + color: { + if (modelData.pairing) + return Color.mError + + if (modelData.blocked) + return Theme.error + + return Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.7) } } - font.pointSize: Style.fontSizeSmall * scaling - color: modelData.device.connected ? Color.mSurface : (deviceMouseArea.containsMouse ? Color.mSurface : Color.mOnSurfaceVariant) - } - NText { - text: BluetoothService.getBatteryText(modelData.device) - font.pointSize: Style.fontSizeSmall * scaling - color: modelData.device.connected ? Color.mSurface : (deviceMouseArea.containsMouse ? Color.mSurface : Color.mOnSurfaceVariant) - visible: modelData.device.batteryAvailable + NText { + text: BluetoothService.getSignalIcon(modelData) + font.family: "Material Symbols Outlined" + font.pointSize: Style.fontSizeSmall * scaling + color: Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.7) + visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 + && !modelData.pairing && !modelData.blocked + } + + NText { + text: (modelData.signalStrength !== undefined + && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : "" + font.pointSize: Style.fontSizeSmall * scaling + color: Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.5) + visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 + && !modelData.pairing && !modelData.blocked + } } } + } + } - Item { - Layout.preferredWidth: Style.baseWidgetSize * 0.7 * scaling - Layout.preferredHeight: Style.baseWidgetSize * 0.7 * scaling - visible: modelData.device.pairing || modelData.device.state === 2 // Connecting state + Rectangle { + width: 80 + height: 28 + radius: Style.radiusMedium * scaling + anchors.right: parent.right + anchors.rightMargin: Style.marginMedium * scaling + anchors.verticalCenter: parent.verticalCenter + visible: modelData.state !== BluetoothDeviceState.Connecting + color: { + if (!canConnect && !isBusy) + return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) - NBusyIndicator { - visible: modelData.device.pairing || modelData.device.state === 2 - running: modelData.device.pairing || modelData.device.state === 2 - color: Color.mPrimary - anchors.centerIn: parent - size: Style.baseWidgetSize * 0.7 * scaling - } - } - - NText { - visible: modelData.device.connected - text: "connected" - font.pointSize: Style.fontSizeSmall * scaling - color: modelData.device.connected ? Color.mSurface : (deviceMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface) + if (actionButtonArea.containsMouse && !isBusy) + return Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.12) + + return "transparent" + } + border.color: canConnect || isBusy ? Color.mPrimary : Qt.rgba(Theme.outline.r, Theme.outline.g, + Theme.outline.b, 0.2) + border.width: 1 + opacity: canConnect || isBusy ? 1 : 0.5 + + NText { + anchors.centerIn: parent + text: { + if (modelData.pairing) + return "Pairing..." + + if (modelData.blocked) + return "Blocked" + + return "Connect" } + font.pointSize: Style.fontSizeSmall * scaling + color: canConnect || isBusy ? Color.mPrimary : Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, + Color.mOnSurface.b, 0.5) + font.weight: Style.fontWeightMedium } MouseArea { - id: deviceMouseArea + id: actionButtonArea + anchors.fill: parent hoverEnabled: true + cursorShape: canConnect + && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor) + enabled: canConnect && !isBusy onClicked: { - if (modelData.device.connected) { - BluetoothService.disconnectDevice(modelData.device) - } else if (modelData.device.paired) { - BluetoothService.connectDevice(modelData.device) - } else { - BluetoothService.pairDevice(modelData.device) - } + if (modelData) + BluetoothService.connectDeviceWithTrust(modelData) } } } + + MouseArea { + id: availableDeviceArea + + anchors.fill: parent + anchors.rightMargin: 90 + hoverEnabled: true + cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor) + enabled: canConnect && !isBusy + onClicked: { + if (modelData) + BluetoothService.connectDeviceWithTrust(modelData) + } + } } } - // Empty state when no devices found - ColumnLayout { - anchors.centerIn: parent - visible: Settings.data.network.bluetoothEnabled && !BluetoothService.isDiscovering - && deviceList.count === 0 + Column { + width: parent.width spacing: Style.marginMedium * scaling + visible: { + if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) + return false - NText { - text: "bluetooth_disabled" - font.family: "Material Symbols Outlined" - font.pointSize: Style.fontSizeXXL * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter + var availableCount = Bluetooth.devices.values.filter(dev => { + return dev && !dev.paired && !dev.pairing + && !dev.blocked + && (dev.signalStrength === undefined + || dev.signalStrength > 0) + }).length + return availableCount === 0 + } + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: Style.marginMedium * scaling + + NText { + text: "sync" + font.family: "Material Symbols Outlined" + font.pointSize: 32 * scaling + color: Color.mPrimary + anchors.verticalCenter: parent.verticalCenter + + RotationAnimation on rotation { + running: true + loops: Animation.Infinite + from: 0 + to: 360 + duration: 2000 + } + } + + NText { + text: "Scanning for devices..." + font.pointSize: Style.fontSizeLarge * scaling + color: Color.mOnSurface + font.weight: Style.fontWeightMedium + anchors.verticalCenter: parent.verticalCenter + } } NText { - text: "No Bluetooth devices" - font.pointSize: Style.fontSizeLarge * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: "Click the refresh button to discover devices" - font.pointSize: Style.fontSizeNormal * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter + text: "Make sure your device is in pairing mode" + font.pointSize: Style.fontSizeMedium * scaling + color: Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.7) + anchors.horizontalCenter: parent.horizontalCenter } } + + NText { + text: "No devices found. Put your device in pairing mode and click Start Scanning." + font.pointSize: Style.fontSizeMedium * scaling + color: Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.7) + visible: { + if (!BluetoothService.adapter || !Bluetooth.devices) + return true + + var availableCount = Bluetooth.devices.values.filter(dev => { + return dev && !dev.paired && !dev.pairing + && !dev.blocked + && (dev.signalStrength === undefined + || dev.signalStrength > 0) + }).length + return availableCount === 0 && !BluetoothService.adapter.discovering + } + wrapMode: Text.WordWrap + width: parent.width + horizontalAlignment: Text.AlignHCenter + } } + + // This item takes up all the remaining vertical space. + Item { + Layout.fillHeight: true + } } } } diff --git a/Services/BluetoothService.qml b/Services/BluetoothService.qml index 18f2794..03c3a2a 100644 --- a/Services/BluetoothService.qml +++ b/Services/BluetoothService.qml @@ -3,432 +3,142 @@ pragma Singleton import QtQuick import Quickshell import Quickshell.Bluetooth -import qs.Commons Singleton { id: root - // Bluetooth state properties - property bool isEnabled: Settings.data.network.bluetoothEnabled - property bool isDiscovering: false - property var connectedDevices: [] - property var pairedDevices: [] - property var availableDevices: [] - property string lastConnectedDevice: "" - property string connectStatus: "" - property string connectStatusDevice: "" - property string connectError: "" + readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter + readonly property bool available: adapter !== null + readonly property bool enabled: (adapter && adapter.enabled) ?? false + readonly property bool discovering: (adapter && adapter.discovering) ?? false + readonly property var devices: adapter ? adapter.devices : null + readonly property var pairedDevices: { + if (!adapter || !adapter.devices) + return [] - // Timer for refreshing device lists - property Timer refreshTimer: Timer { - interval: 5000 // Refresh every 5 seconds when discovery is active - repeat: true - running: root.isEnabled && Bluetooth.defaultAdapter && Bluetooth.defaultAdapter.discovering - onTriggered: root.refreshDevices() + return adapter.devices.values.filter(dev => { + return dev && (dev.paired || dev.trusted) + }) + } + readonly property var allDevicesWithBattery: { + if (!adapter || !adapter.devices) + return [] + + return adapter.devices.values.filter(dev => { + return dev && dev.batteryAvailable && dev.battery > 0 + }) } - Component.onCompleted: { - Logger.log("Bluetooth", "Service started") + function sortDevices(devices) { + return devices.sort((a, b) => { + var aName = a.name || a.deviceName || "" + var bName = b.name || b.deviceName || "" - if (isEnabled && Bluetooth.defaultAdapter) { - // Ensure adapter is enabled - if (!Bluetooth.defaultAdapter.enabled) { - Bluetooth.defaultAdapter.enabled = true - } + var aHasRealName = aName.includes(" ") && aName.length > 3 + var bHasRealName = bName.includes(" ") && bName.length > 3 - // Start discovery to find devices - if (!Bluetooth.defaultAdapter.discovering) { - Bluetooth.defaultAdapter.discovering = true - } + if (aHasRealName && !bHasRealName) + return -1 + if (!aHasRealName && bHasRealName) + return 1 - // Refresh devices after a short delay to allow discovery to start - Qt.callLater(function () { - refreshDevices() - }) - } + var aSignal = (a.signalStrength !== undefined && a.signalStrength > 0) ? a.signalStrength : 0 + var bSignal = (b.signalStrength !== undefined && b.signalStrength > 0) ? b.signalStrength : 0 + return bSignal - aSignal + }) } - // Function to enable/disable Bluetooth - function setBluetoothEnabled(enabled) { - - if (enabled) { - // Store the currently connected devices before enabling - for (const device of connectedDevices) { - if (device.connected) { - lastConnectedDevice = device.name || device.deviceName - break - } - } - - // Enable Bluetooth - if (Bluetooth.defaultAdapter) { - Bluetooth.defaultAdapter.enabled = true - - // Start discovery to find devices - if (!Bluetooth.defaultAdapter.discovering) { - Bluetooth.defaultAdapter.discovering = true - } - - // Refresh devices after enabling - Qt.callLater(refreshDevices) - } else { - Logger.warn("Bluetooth", "No adapter found") - } - } else { - // Disconnect from current devices before disabling - for (const device of connectedDevices) { - if (device.connected) { - device.disconnect() - } - } - - // Disable Bluetooth - if (Bluetooth.defaultAdapter) { - Logger.log("Bluetooth", "Disabling adapter") - Bluetooth.defaultAdapter.enabled = false - } - } - - Settings.data.network.bluetoothEnabled = enabled - isEnabled = enabled - } - - // Function to refresh device lists - function refreshDevices() { - if (!isEnabled || !Bluetooth.defaultAdapter) { - connectedDevices = [] - pairedDevices = [] - availableDevices = [] - return - } - - Logger.log("Bluetooth", "refreshDevices") - - // Remove duplicate check since we already did it above - const connected = [] - const paired = [] - const available = [] - - let devices = null - - // Try adapter devices first - if (Bluetooth.defaultAdapter.enabled && Bluetooth.defaultAdapter.devices) { - devices = Bluetooth.defaultAdapter.devices - } - - // Fallback to global devices list - if (!devices && Bluetooth.devices) { - devices = Bluetooth.devices - } - - if (!devices) { - connectedDevices = [] - pairedDevices = [] - availableDevices = [] - return - } - - // Use Qt model methods to iterate through the ObjectModel - let deviceFound = false - - try { - // Get the row count using the Qt model method - const rowCount = devices.rowCount() - - if (rowCount > 0) { - // Iterate through each row using the Qt model data() method - for (var i = 0; i < rowCount; i++) { - try { - // Create a model index for this row - const modelIndex = devices.index(i, 0) - if (!modelIndex.valid) - continue - - // Get the device object using the Qt.UserRole (typically 256) - const device = devices.data(modelIndex, 256) // Qt.UserRole - if (!device) { - // Try alternative role values - const deviceAlt = devices.data(modelIndex, 0) // Qt.DisplayRole - if (deviceAlt) { - device = deviceAlt - } else { - continue - } - } - - deviceFound = true - - if (device.connected) { - connected.push(device) - } else if (device.paired) { - paired.push(device) - } else { - available.push(device) - } - } catch (e) { - - // Silent error handling - } - } - } - - // Alternative method: try the values property if available - if (!deviceFound && devices.values) { - try { - const values = devices.values - if (values && typeof values === 'object') { - // Try to iterate through values if it's iterable - if (values.length !== undefined) { - for (var i = 0; i < values.length; i++) { - const device = values[i] - if (device) { - deviceFound = true - if (device.connected) { - connected.push(device) - } else if (device.paired) { - paired.push(device) - } else { - available.push(device) - } - } - } - } - } - } catch (e) { - - // Silent error handling - } - } - } catch (e) { - Logger.warn("Bluetooth", "Error accessing device model:", e) - } - - if (!deviceFound) { - Logger.log("Bluetooth", "No device found") - } - - connectedDevices = connected - pairedDevices = paired - availableDevices = available - } - - // Function to start discovery - function startDiscovery() { - if (!isEnabled || !Bluetooth.defaultAdapter) - return - - isDiscovering = true - Bluetooth.defaultAdapter.discovering = true - } - - // Function to stop discovery - function stopDiscovery() { - if (!Bluetooth.defaultAdapter) - return - - isDiscovering = false - Bluetooth.defaultAdapter.discovering = false - } - - // Function to connect to a device - function connectDevice(device) { - if (!device) - return - - // Check if device is still valid (not stale from previous session) - if (!device.connect || typeof device.connect !== 'function') { - Logger.warn("Bluetooth", "Device object is stale, refreshing devices") - refreshDevices() - return - } - - connectStatus = "connecting" - connectStatusDevice = device.name || device.deviceName - connectError = "" - - try { - device.connect() - } catch (error) { - Logger.error("Bluetooth", "Error connecting to device:", error) - connectStatus = "error" - connectError = error.toString() - Qt.callLater(refreshDevices) - } - } - - // Function to disconnect from a device - function disconnectDevice(device) { - if (!device) - return - - // Check if device is still valid (not stale from previous session) - if (!device.disconnect || typeof device.disconnect !== 'function') { - Logger.warn("Bluetooth", "Device object is stale, refreshing devices") - refreshDevices() - return - } - - try { - device.disconnect() - // Clear connection status - connectStatus = "" - connectStatusDevice = "" - connectError = "" - } catch (error) { - Logger.warn("Bluetooth", "Error disconnecting device:", error) - Qt.callLater(refreshDevices) - } - } - - // Function to pair with a device - function pairDevice(device) { - if (!device) - return - - // Check if device is still valid (not stale from previous session) - if (!device.pair || typeof device.pair !== 'function') { - Logger.warn("Bluetooth", "Device object is stale, refreshing devices") - refreshDevices() - return - } - - try { - device.pair() - } catch (error) { - Logger.warn("Bluetooth", "Error pairing device:", error) - Qt.callLater(refreshDevices) - } - } - - // Function to forget a device - function forgetDevice(device) { - if (!device) - return - - // Check if device is still valid (not stale from previous session) - if (!device.forget || typeof device.forget !== 'function') { - Logger.warn("Bluetooth", "Device object is stale, refreshing devices") - refreshDevices() - return - } - - // Store device info before forgetting (in case device object becomes invalid) - const deviceName = device.name || device.deviceName || "Unknown Device" - - try { - device.forget() - - // Clear any connection status that might be related to this device - if (connectStatusDevice === deviceName) { - connectStatus = "" - connectStatusDevice = "" - connectError = "" - } - - // Refresh devices after a delay to ensure the forget operation is complete - Qt.callLater(refreshDevices, 1000) - } catch (error) { - Logger.warn("Bluetooth", "Error forgetting device:", error) - Qt.callLater(refreshDevices, 500) - } - } - - // Function to get device icon function getDeviceIcon(device) { if (!device) return "bluetooth" - // Use device icon if available, otherwise fall back to device type - if (device.icon) { - return device.icon - } + var name = (device.name || device.deviceName || "").toLowerCase() + var icon = (device.icon || "").toLowerCase() + if (icon.includes("headset") || icon.includes("audio") || name.includes("headphone") || name.includes("airpod") + || name.includes("headset") || name.includes("arctis")) + return "headset" - // Fallback icons based on common device types - const name = (device.name || device.deviceName || "").toLowerCase() - if (name.includes("headphone") || name.includes("earbud") || name.includes("airpods")) { - return "headphones" - } else if (name.includes("speaker")) { - return "speaker" - } else if (name.includes("keyboard")) { - return "keyboard" - } else if (name.includes("mouse")) { + if (icon.includes("mouse") || name.includes("mouse")) return "mouse" - } else if (name.includes("phone") || name.includes("mobile")) { + + if (icon.includes("keyboard") || name.includes("keyboard")) + return "keyboard" + + if (icon.includes("phone") || name.includes("phone") || name.includes("iphone") || name.includes("android") + || name.includes("samsung")) return "smartphone" - } else if (name.includes("laptop") || name.includes("computer")) { - return "laptop" - } + + if (icon.includes("watch") || name.includes("watch")) + return "watch" + + if (icon.includes("speaker") || name.includes("speaker")) + return "speaker" + + if (icon.includes("display") || name.includes("tv")) + return "tv" return "bluetooth" } - // Function to get device status text - function getDeviceStatus(device) { + function canConnect(device) { if (!device) - return "" + return false - if (device.connected) { - return "Connected" - } else if (device.pairing) { - return "Pairing..." - } else if (device.paired) { - return "Paired" - } else { - return "Available" - } + return !device.paired && !device.pairing && !device.blocked } - // Function to get battery level text - function getBatteryText(device) { - if (!device || !device.batteryAvailable) - return "" + function getSignalStrength(device) { + if (!device || device.signalStrength === undefined || device.signalStrength <= 0) + return "Unknown" - const percentage = Math.round(device.battery * 100) - return `${percentage}%` + var signal = device.signalStrength + if (signal >= 80) + return "Excellent" + + if (signal >= 60) + return "Good" + + if (signal >= 40) + return "Fair" + + if (signal >= 20) + return "Poor" + + return "Very Poor" } - // Watch for Bluetooth adapter changes - Connections { - target: Bluetooth.defaultAdapter - ignoreUnknownSignals: true + function getSignalIcon(device) { + if (!device || device.signalStrength === undefined || device.signalStrength <= 0) + return "signal_cellular_null" - function onEnabledChanged() { - root.isEnabled = Bluetooth.defaultAdapter.enabled - Settings.data.network.bluetoothEnabled = root.isEnabled - if (root.isEnabled) { - Qt.callLater(refreshDevices) - } else { - connectedDevices = [] - pairedDevices = [] - availableDevices = [] - } - } + var signal = device.signalStrength + if (signal >= 80) + return "signal_cellular_4_bar" - function onDiscoveringChanged() { - root.isDiscovering = Bluetooth.defaultAdapter.discovering - if (Bluetooth.defaultAdapter.discovering) { - Qt.callLater(refreshDevices) - } - } + if (signal >= 60) + return "signal_cellular_3_bar" - function onStateChanged() { - if (Bluetooth.defaultAdapter.state >= 4) { - Qt.callLater(refreshDevices) - } - } + if (signal >= 40) + return "signal_cellular_2_bar" - function onDevicesChanged() { - Qt.callLater(refreshDevices) - } + if (signal >= 20) + return "signal_cellular_1_bar" + + return "signal_cellular_0_bar" } - // Watch for global device changes - Connections { - target: Bluetooth - ignoreUnknownSignals: true + function isDeviceBusy(device) { + if (!device) + return false + return device.pairing || device.state === BluetoothDeviceState.Disconnecting + || device.state === BluetoothDeviceState.Connecting + } - function onDevicesChanged() { - Qt.callLater(refreshDevices) - } + function connectDeviceWithTrust(device) { + if (!device) + return + + device.trusted = true + device.connect() } }