diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 4234ff797..26fb27f14 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -919,6 +919,8 @@ }, "connections": { "authentication-required": "Authentication required", + "bluetooth-auto-connect-description": "Automatically connect to trusted paired devices when Bluetooth is enabled.", + "bluetooth-auto-connect-label": "Auto-connect paired devices", "bluetooth-devices-unnamed": "Unnamed devices are not shown.", "bluetooth-discoverable": "This device is discoverable as {hostName} while this settings tab is open.", "bluetooth-rssi-polling-description": "Periodically sample RSSI for connected devices via bluetoothctl. May not be available for all devices; uses minimal resources when enabled.", @@ -1805,6 +1807,9 @@ }, "bluetooth": { "address-copied": "Address copied to clipboard", + "auto-connect-disabled": "Auto-connect disabled", + "auto-connect-enabled": "Auto-connect enabled", + "auto-connecting": "Connecting to {count} device(s)...", "confirm-code": "Confirm code {value} on the other device.", "connect-failed": "Failed to connect to device", "disconnect-failed": "Failed to disconnect from device", @@ -1877,6 +1882,8 @@ }, "tooltips": { "add-widget": "Add widget", + "bluetooth-auto-connect-off": "Auto-connect is off — click to enable", + "bluetooth-auto-connect-on": "Auto-connect is on — click to disable", "bluetooth-devices": "Bluetooth devices", "brightness-at": "Brightness: {brightness}%", "click-to-start-recording": "Screen recorder (start recording)", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index bc1554b36..702fd4d87 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -566,6 +566,7 @@ Singleton { property string bluetoothDetailsViewMode: "grid" // "grid" or "list" property bool bluetoothHideUnnamedDevices: false property bool disableDiscoverability: false + property bool bluetoothAutoConnect: true } // session menu diff --git a/Modules/Panels/Bluetooth/BluetoothPanel.qml b/Modules/Panels/Bluetooth/BluetoothPanel.qml index c416829e6..92464d7b7 100644 --- a/Modules/Panels/Bluetooth/BluetoothPanel.qml +++ b/Modules/Panels/Bluetooth/BluetoothPanel.qml @@ -59,6 +59,14 @@ SmartPanel { baseSize: Style.baseWidgetSize * 0.65 } + NIconButton { + icon: Settings.data.network.bluetoothAutoConnect ? "bluetooth-connected" : "bluetooth" + tooltipText: Settings.data.network.bluetoothAutoConnect ? I18n.tr("tooltips.bluetooth-auto-connect-on") : I18n.tr("tooltips.bluetooth-auto-connect-off") + colorFg: Settings.data.network.bluetoothAutoConnect ? Color.mPrimary : Color.mOnSurfaceVariant + baseSize: Style.baseWidgetSize * 0.8 + onClicked: Settings.data.network.bluetoothAutoConnect = !Settings.data.network.bluetoothAutoConnect + } + NIconButton { icon: "settings" tooltipText: I18n.tr("tooltips.open-settings") diff --git a/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml b/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml index 4cf2cf75c..b2785eab7 100644 --- a/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml @@ -313,6 +313,13 @@ Item { anchors.margins: Style.marginXL spacing: Style.marginM + NToggle { + label: I18n.tr("panels.connections.bluetooth-auto-connect-label") + description: I18n.tr("panels.connections.bluetooth-auto-connect-description") + checked: Settings.data.network.bluetoothAutoConnect + onToggled: checked => Settings.data.network.bluetoothAutoConnect = checked + } + NToggle { label: I18n.tr("panels.connections.hide-unnamed-devices-label") description: I18n.tr("panels.connections.hide-unnamed-devices-description") diff --git a/Services/Control/IPCService.qml b/Services/Control/IPCService.qml index 784d0b855..22ff899a2 100644 --- a/Services/Control/IPCService.qml +++ b/Services/Control/IPCService.qml @@ -653,6 +653,15 @@ Singleton { bluetoothPanel?.toggle(null, "Bluetooth"); }); } + function toggleAutoConnect() { + Settings.data.network.bluetoothAutoConnect = !Settings.data.network.bluetoothAutoConnect; + } + function enableAutoConnect() { + Settings.data.network.bluetoothAutoConnect = true; + } + function disableAutoConnect() { + Settings.data.network.bluetoothAutoConnect = false; + } } IpcHandler { diff --git a/Services/Networking/BluetoothService.qml b/Services/Networking/BluetoothService.qml index 87c64263a..f893ea357 100644 --- a/Services/Networking/BluetoothService.qml +++ b/Services/Networking/BluetoothService.qml @@ -68,6 +68,17 @@ Singleton { property bool _discoveryWasRunning: false property bool _ctlInit: false + Connections { + target: Settings.data.network + function onBluetoothAutoConnectChanged() { + if ((Settings?.data?.network?.bluetoothAutoConnect ?? true) && adapter && adapter.enabled) { + autoConnectTimer.restart(); + } else { + autoConnectTimer.stop(); + } + } + } + Timer { id: initDelayTimer interval: 3000 @@ -75,6 +86,13 @@ Singleton { repeat: false } + Timer { + id: autoConnectTimer + interval: 2000 + repeat: false + onTriggered: root.attemptAutoConnect() + } + function init() { Logger.i("Bluetooth", "Service started"); } @@ -86,6 +104,10 @@ Singleton { Quickshell.execDetached(["rfkill", "block", "wifi"]); Quickshell.execDetached(["rfkill", "block", "bluetooth"]); } + // Auto-connect on startup if BT is already enabled + if ((Settings?.data?.network?.bluetoothAutoConnect ?? true) && adapter && adapter.enabled) { + autoConnectTimer.restart(); + } } // Handle system wakeup to force-poll and ensure state is up-to-date @@ -106,6 +128,11 @@ Singleton { } checkAirplaneMode.running = true; } + function onEnabledChanged() { + if (adapter && adapter.enabled && (Settings?.data?.network?.bluetoothAutoConnect ?? true)) { + autoConnectTimer.restart(); + } + } } onAdapterChanged: { @@ -597,6 +624,27 @@ Singleton { forgetDevice(device); } + function attemptAutoConnect() { + if (airplaneModeEnabled) return; + if (!adapter || !adapter.enabled) return; + if (!(Settings?.data?.network?.bluetoothAutoConnect ?? true)) return; + + var devList = adapter.devices.values.filter(function(dev) { + return dev && dev.paired && !dev.connected && !dev.blocked; + }); + + for (var i = 0; i < devList.length; i++) { + Logger.i("Bluetooth", "Auto-connecting to:", devList[i].name || devList[i].deviceName); + connectDeviceWithTrust(devList[i]); + } + + if (devList.length > 0) { + ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("toast.bluetooth.auto-connecting", { + count: devList.length + }), "bluetooth"); + } + } + function connectDeviceWithTrust(device) { if (!device) { return;