424 lines
No EOL
12 KiB
QML
424 lines
No EOL
12 KiB
QML
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: ""
|
|
|
|
// 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()
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
console.log("[Bluetooth] Service started")
|
|
|
|
if (isEnabled && Bluetooth.defaultAdapter) {
|
|
// Ensure adapter is enabled
|
|
if (!Bluetooth.defaultAdapter.enabled) {
|
|
Bluetooth.defaultAdapter.enabled = true
|
|
}
|
|
|
|
// Start discovery to find devices
|
|
if (!Bluetooth.defaultAdapter.discovering) {
|
|
Bluetooth.defaultAdapter.discovering = true
|
|
}
|
|
|
|
// Refresh devices after a short delay to allow discovery to start
|
|
Qt.callLater(function() {
|
|
refreshDevices()
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
console.warn("[Bluetooth] No Bluetooth adapter found!")
|
|
}
|
|
} else {
|
|
// Disconnect from current devices before disabling
|
|
for (const device of connectedDevices) {
|
|
if (device.connected) {
|
|
device.disconnect()
|
|
}
|
|
}
|
|
|
|
// Disable Bluetooth
|
|
if (Bluetooth.defaultAdapter) {
|
|
console.log("[Bluetooth] Disabling Bluetooth 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
|
|
}
|
|
|
|
// 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 (let 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 (let 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) {
|
|
console.warn("[Bluetooth] Error accessing device model:", e)
|
|
}
|
|
|
|
if (!deviceFound) {
|
|
console.log("[Bluetooth] No devices 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') {
|
|
console.warn("[Bluetooth] Device object is stale, refreshing devices")
|
|
refreshDevices()
|
|
return
|
|
}
|
|
|
|
connectStatus = "connecting"
|
|
connectStatusDevice = device.name || device.deviceName
|
|
connectError = ""
|
|
|
|
try {
|
|
device.connect()
|
|
} catch (error) {
|
|
console.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') {
|
|
console.warn("[Bluetooth] Device object is stale, refreshing devices")
|
|
refreshDevices()
|
|
return
|
|
}
|
|
|
|
try {
|
|
device.disconnect()
|
|
// Clear connection status
|
|
connectStatus = ""
|
|
connectStatusDevice = ""
|
|
connectError = ""
|
|
} catch (error) {
|
|
console.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') {
|
|
console.warn("[Bluetooth] Device object is stale, refreshing devices")
|
|
refreshDevices()
|
|
return
|
|
}
|
|
|
|
try {
|
|
device.pair()
|
|
} catch (error) {
|
|
console.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') {
|
|
console.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) {
|
|
console.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
|
|
}
|
|
|
|
// 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")) {
|
|
return "mouse"
|
|
} else if (name.includes("phone") || name.includes("mobile")) {
|
|
return "smartphone"
|
|
} else if (name.includes("laptop") || name.includes("computer")) {
|
|
return "laptop"
|
|
}
|
|
|
|
return "bluetooth"
|
|
}
|
|
|
|
// Function to get device status text
|
|
function getDeviceStatus(device) {
|
|
if (!device) return ""
|
|
|
|
if (device.connected) {
|
|
return "Connected"
|
|
} else if (device.pairing) {
|
|
return "Pairing..."
|
|
} else if (device.paired) {
|
|
return "Paired"
|
|
} else {
|
|
return "Available"
|
|
}
|
|
}
|
|
|
|
// Function to get battery level text
|
|
function getBatteryText(device) {
|
|
if (!device || !device.batteryAvailable) return ""
|
|
|
|
const percentage = Math.round(device.battery * 100)
|
|
return `${percentage}%`
|
|
}
|
|
|
|
// Watch for Bluetooth adapter changes
|
|
Connections {
|
|
target: Bluetooth.defaultAdapter
|
|
ignoreUnknownSignals: true
|
|
|
|
function onEnabledChanged() {
|
|
root.isEnabled = Bluetooth.defaultAdapter.enabled
|
|
Settings.data.network.bluetoothEnabled = root.isEnabled
|
|
if (root.isEnabled) {
|
|
Qt.callLater(refreshDevices)
|
|
} else {
|
|
connectedDevices = []
|
|
pairedDevices = []
|
|
availableDevices = []
|
|
}
|
|
}
|
|
|
|
function onDiscoveringChanged() {
|
|
root.isDiscovering = Bluetooth.defaultAdapter.discovering
|
|
if (Bluetooth.defaultAdapter.discovering) {
|
|
Qt.callLater(refreshDevices)
|
|
}
|
|
}
|
|
|
|
function onStateChanged() {
|
|
if (Bluetooth.defaultAdapter.state >= 4) {
|
|
Qt.callLater(refreshDevices)
|
|
}
|
|
}
|
|
|
|
function onDevicesChanged() {
|
|
Qt.callLater(refreshDevices)
|
|
}
|
|
}
|
|
|
|
// Watch for global device changes
|
|
Connections {
|
|
target: Bluetooth
|
|
ignoreUnknownSignals: true
|
|
|
|
function onDevicesChanged() {
|
|
Qt.callLater(refreshDevices)
|
|
}
|
|
}
|
|
} |