From dea0575efad277379f1154919a9f6a9c12f339bf Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:22:12 +0100 Subject: [PATCH 1/7] rework Airplane Mode handling & remove bluetoothctl fallback --- Modules/Bar/Widgets/Bluetooth.qml | 2 +- Modules/Bar/Widgets/Network.qml | 2 +- Modules/Panels/Bluetooth/BluetoothPanel.qml | 2 +- .../ControlCenter/Widgets/AirplaneMode.qml | 6 +- .../ControlCenter/Widgets/Bluetooth.qml | 2 +- .../Panels/ControlCenter/Widgets/Network.qml | 2 +- Modules/Panels/Network/NetworkPanel.qml | 2 +- .../Tabs/Connections/BluetoothSubTab.qml | 2 +- .../Settings/Tabs/Connections/WifiSubTab.qml | 8 +- Services/Control/IPCService.qml | 6 +- Services/Networking/BluetoothService.qml | 521 ++++++------------ Services/Networking/NetworkService.qml | 85 +-- shell.qml | 1 - 13 files changed, 244 insertions(+), 397 deletions(-) diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index bb59c595b..88790264a 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -49,7 +49,7 @@ Item { "label": BluetoothService.enabled ? I18n.tr("actions.disable-bluetooth") : I18n.tr("actions.enable-bluetooth"), "action": "toggle-bluetooth", "icon": BluetoothService.enabled ? "bluetooth-off" : "bluetooth", - "enabled": !Settings.data.network.airplaneModeEnabled && BluetoothService.bluetoothAvailable + "enabled": !NetworkService.airplaneModeEnabled && BluetoothService.bluetoothAvailable }, { "label": I18n.tr("common.bluetooth") + " " + I18n.tr("tooltips.open-settings"), diff --git a/Modules/Bar/Widgets/Network.qml b/Modules/Bar/Widgets/Network.qml index 3435b323c..458950b06 100644 --- a/Modules/Bar/Widgets/Network.qml +++ b/Modules/Bar/Widgets/Network.qml @@ -49,7 +49,7 @@ Item { "label": NetworkService.wifiEnabled ? I18n.tr("actions.disable-wifi") : I18n.tr("actions.enable-wifi"), "action": "toggle-wifi", "icon": NetworkService.wifiEnabled ? "wifi-off" : "wifi", - "enabled": !Settings.data.network.airplaneModeEnabled && NetworkService.wifiAvailable + "enabled": !NetworkService.airplaneModeEnabled && NetworkService.wifiAvailable }, { "label": I18n.tr("common.wifi") + " " + I18n.tr("tooltips.open-settings"), diff --git a/Modules/Panels/Bluetooth/BluetoothPanel.qml b/Modules/Panels/Bluetooth/BluetoothPanel.qml index 92464d7b7..90a59ee95 100644 --- a/Modules/Panels/Bluetooth/BluetoothPanel.qml +++ b/Modules/Panels/Bluetooth/BluetoothPanel.qml @@ -54,7 +54,7 @@ SmartPanel { NToggle { id: bluetoothSwitch checked: BluetoothService.enabled - enabled: !Settings.data.network.airplaneModeEnabled && BluetoothService.bluetoothAvailable + enabled: !NetworkService.airplaneModeEnabled && BluetoothService.bluetoothAvailable onToggled: checked => BluetoothService.setBluetoothEnabled(checked) baseSize: Style.baseWidgetSize * 0.65 } diff --git a/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml b/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml index 6f4679bbd..c0d00c026 100644 --- a/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml +++ b/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml @@ -8,10 +8,10 @@ import qs.Widgets NIconButtonHot { property ShellScreen screen - icon: !Settings.data.network.airplaneModeEnabled ? "plane-off" : "plane" - hot: Settings.data.network.airplaneModeEnabled + icon: !NetworkService.airplaneModeEnabled ? "plane-off" : "plane" + hot: NetworkService.airplaneModeEnabled tooltipText: I18n.tr("toast.airplane-mode.title") onClicked: { - BluetoothService.setAirplaneMode(!Settings.data.network.airplaneModeEnabled); + NetworkService.setAirplaneMode(!NetworkService.airplaneModeEnabled); } } diff --git a/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml b/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml index 071013601..b6ede8415 100644 --- a/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml +++ b/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml @@ -16,7 +16,7 @@ NIconButtonHot { p.toggle(this); } onRightClicked: { - if (!Settings.data.network.airplaneModeEnabled) { + if (!NetworkService.airplaneModeEnabled) { BluetoothService.setBluetoothEnabled(!BluetoothService.enabled); } } diff --git a/Modules/Panels/ControlCenter/Widgets/Network.qml b/Modules/Panels/ControlCenter/Widgets/Network.qml index 82bb55e03..9fe91eed2 100644 --- a/Modules/Panels/ControlCenter/Widgets/Network.qml +++ b/Modules/Panels/ControlCenter/Widgets/Network.qml @@ -14,7 +14,7 @@ NIconButtonHot { panel?.toggle(this); } onRightClicked: { - if (!Settings.data.network.airplaneModeEnabled) { + if (!NetworkService.airplaneModeEnabled) { NetworkService.setWifiEnabled(!NetworkService.wifiEnabled); } } diff --git a/Modules/Panels/Network/NetworkPanel.qml b/Modules/Panels/Network/NetworkPanel.qml index c0718d380..2918c6182 100644 --- a/Modules/Panels/Network/NetworkPanel.qml +++ b/Modules/Panels/Network/NetworkPanel.qml @@ -144,7 +144,7 @@ SmartPanel { id: wifiSwitch visible: panelViewMode === "wifi" checked: NetworkService.wifiEnabled - enabled: !Settings.data.network.airplaneModeEnabled && NetworkService.wifiAvailable + enabled: !NetworkService.airplaneModeEnabled && NetworkService.wifiAvailable onToggled: checked => NetworkService.setWifiEnabled(checked) baseSize: Style.baseWidgetSize * 0.7 // Slightly smaller } diff --git a/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml b/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml index 3183b6ad2..c424a52eb 100644 --- a/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml @@ -161,7 +161,7 @@ Item { label: I18n.tr("common.bluetooth") icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off" checked: BluetoothService.enabled - enabled: !Settings.data.network.airplaneModeEnabled && BluetoothService.bluetoothAvailable + enabled: !NetworkService.airplaneModeEnabled && BluetoothService.bluetoothAvailable onToggled: checked => BluetoothService.setBluetoothEnabled(checked) Layout.alignment: Qt.AlignVCenter } diff --git a/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml b/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml index 918d5dd5d..c44b88eaa 100644 --- a/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml @@ -149,7 +149,7 @@ Item { icon: NetworkService.wifiEnabled ? "wifi" : "wifi-off" checked: NetworkService.wifiEnabled onToggled: checked => NetworkService.setWifiEnabled(checked) - enabled: ProgramCheckerService.nmcliAvailable && !Settings.data.network.airplaneModeEnabled && NetworkService.wifiAvailable + enabled: ProgramCheckerService.nmcliAvailable && !NetworkService.airplaneModeEnabled && NetworkService.wifiAvailable Layout.alignment: Qt.AlignVCenter } } @@ -338,9 +338,9 @@ Item { NToggle { label: I18n.tr("toast.airplane-mode.title") description: I18n.tr("toast.airplane-mode.description") - icon: Settings.data.network.airplaneModeEnabled ? "plane" : "plane-off" - checked: Settings.data.network.airplaneModeEnabled - onToggled: checked => BluetoothService.setAirplaneMode(checked) + icon: NetworkService.airplaneModeEnabled ? "plane" : "plane-off" + checked: NetworkService.airplaneModeEnabled + onToggled: checked => NetworkService.setAirplaneMode(checked) } } } diff --git a/Services/Control/IPCService.qml b/Services/Control/IPCService.qml index e535b6c70..23df03c2e 100644 --- a/Services/Control/IPCService.qml +++ b/Services/Control/IPCService.qml @@ -674,13 +674,13 @@ Singleton { IpcHandler { target: "airplaneMode" function toggle() { - BluetoothService.setAirplaneMode(!Settings.data.network.airplaneModeEnabled); + NetworkService.setAirplaneMode(!NetworkService.airplaneModeEnabled); } function enable() { - BluetoothService.setAirplaneMode(true); + NetworkService.setAirplaneMode(true); } function disable() { - BluetoothService.setAirplaneMode(false); + NetworkService.setAirplaneMode(false); } } diff --git a/Services/Networking/BluetoothService.qml b/Services/Networking/BluetoothService.qml index 7ad9f02b1..d81ac48aa 100644 --- a/Services/Networking/BluetoothService.qml +++ b/Services/Networking/BluetoothService.qml @@ -12,30 +12,18 @@ import qs.Services.UI Singleton { id: root - // Constants (centralized tunables) - readonly property int ctlPollMs: 10000 - readonly property int ctlPollSoonMs: 250 - readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter - // Airplane mode status - readonly property bool airplaneModeEnabled: Settings.data.network.airplaneModeEnabled - property bool airplaneModeToggled: false - - // Power/blocked/availability state - property bool ctlAvailable: false - readonly property bool bluetoothAvailable: !!adapter || root.ctlAvailable - readonly property bool enabled: adapter?.enabled ?? root.ctlPowered - property bool ctlPowered: false - property bool ctlPowerBlocked: false - property bool ctlDiscovering: false - property bool ctlDiscoverable: false + // Power/availability state + readonly property bool bluetoothAvailable: !!adapter + readonly property bool enabled: adapter?.enabled ?? false + readonly property bool blocked: adapter?.state === BluetoothAdapter.Blocked // Exposed scanning flag for UI button state; reflects adapter discovery when available - readonly property bool scanningActive: adapter?.discovering ?? root.ctlDiscovering + readonly property bool scanningActive: adapter?.discovering ?? false // Adapter discoverability (advertising) flag - readonly property bool discoverable: adapter?.discoverable ?? root.ctlDiscoverable + readonly property bool discoverable: adapter?.discoverable ?? false readonly property var devices: adapter ? adapter.devices : null readonly property var connectedDevices: { if (!adapter || !adapter.devices) { @@ -44,9 +32,6 @@ Singleton { return adapter.devices.values.filter(dev => dev && dev.connected); } - // Current backend in use for scanning (e.g., "native", "bluetoothctl") - property string backendUsed: "" - // Experimental: best‑effort RSSI polling for connected devices (without root) // Enabled in debug mode or via user setting in Settings > Network property bool rssiPollingEnabled: Settings?.data?.network?.bluetoothRssiPollingEnabled || Settings?.isDebug || false @@ -64,6 +49,9 @@ Singleton { property int connectAttempts: 5 property int connectRetryIntervalMs: 2000 + // Interaction state + property bool pinRequired: false + // Internal variables property bool _discoveryWasRunning: false property bool _ctlInit: false @@ -83,31 +71,39 @@ Singleton { } } - function getDeviceAutoConnect(device) { - if (!device || !device.address || !cacheAdapter.autoConnectSettings) { - return false; + // Handle system wakeup to force-poll and ensure state is up-to-date + Connections { + target: Time + function onResumed() { + Logger.i("Bluetooth", "System resumed - forcing state poll"); + ctlPollTimer.restart(); } - const mac = device.address; - const settings = cacheAdapter.autoConnectSettings[mac]; - return settings ? !!settings.autoConnect : false; } - function setDeviceAutoConnect(device, enabled) { - if (!device || !device.address) { - return; + // Re-run polling once bluetoothctl availability is known + Connections { + target: ProgramCheckerService + function onBluetoothctlAvailableChanged() { + if (ProgramCheckerService.bluetoothctlAvailable) { + ctlPollTimer.restart(); + } } - const mac = device.address; - let settings = cacheAdapter.autoConnectSettings || ({}); - if (enabled) { - settings[mac] = { - autoConnect: true, - deviceName: device.name || device.deviceName || "" - }; - } else { - delete settings[mac]; + } + + // Track adapter state changes + Connections { + target: adapter + function onStateChanged() { + if (!adapter || adapter.state === BluetoothAdapter.Enabling || adapter.state === BluetoothAdapter.Disabling) { + return; + } + checkAirplaneMode(); + } + function onEnabledChanged() { + if (adapter && adapter.enabled && Settings.data.network.bluetoothAutoConnect) { + autoConnectTimer.restart(); + } } - cacheAdapter.autoConnectSettings = settings; - cacheFileView.writeAdapter(); } Connections { @@ -121,11 +117,16 @@ Singleton { } } + Component.onCompleted: { + Logger.i("Bluetooth", "Service started"); + autoConnectTimer.restart(); + } + Timer { id: autoConnectTimer interval: 1500 repeat: false - onTriggered: root.attemptAutoConnect() + onTriggered: attemptAutoConnect() } Timer { @@ -144,303 +145,78 @@ Singleton { } } - function init() { - Logger.i("Bluetooth", "Service started"); - } - - Component.onCompleted: { - pollCtlState(); - // Ensure Airplane Mode persists upon reboot - if (root.airplaneModeEnabled) { - Quickshell.execDetached(["rfkill", "block", "wifi"]); - Quickshell.execDetached(["rfkill", "block", "bluetooth"]); - } - // Auto-connect on startup - autoConnectTimer.restart(); - } - - // Handle system wakeup to force-poll and ensure state is up-to-date - Connections { - target: Time - function onResumed() { - Logger.i("Bluetooth", "System resumed - forcing state poll"); - requestCtlPoll(); - } - } - - // Track adapter state changes - Connections { - target: adapter - function onStateChanged() { - if (!adapter || adapter.state === BluetoothAdapter.Enabling || adapter.state === BluetoothAdapter.Disabling) { - return; - } - checkAirplaneMode.running = true; - } - function onEnabledChanged() { - if (adapter && adapter.enabled && Settings.data.network.bluetoothAutoConnect) { - autoConnectTimer.restart(); - } - } - } - - onAdapterChanged: { - pollCtlState(); - if (!adapter) { - ctlPollTimer.interval = 2000; - } - } - - // Re-run polling once bluetoothctl availability is known - Connections { - target: ProgramCheckerService - function onBluetoothctlAvailableChanged() { - if (!adapter && ProgramCheckerService.bluetoothctlAvailable) { - requestCtlPoll(0); - } - } - } - - function setAirplaneMode(state) { - if (state) { - Quickshell.execDetached(["rfkill", "block", "wifi"]); - Quickshell.execDetached(["rfkill", "block", "bluetooth"]); - } else { - Quickshell.execDetached(["rfkill", "unblock", "wifi"]); - Quickshell.execDetached(["rfkill", "unblock", "bluetooth"]); - } - if (!adapter) { - root.ctlPowered = !state; - root.ctlPowerBlocked = state; - root.airplaneModeToggled = true; - NetworkService.setWifiEnabled(!state); - Settings.data.network.airplaneModeEnabled = state; - ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), state ? I18n.tr("common.enabled") : I18n.tr("common.disabled"), state ? "plane" : "plane-off"); - Logger.i("AirplaneMode", state ? "Wi-Fi & Bluetooth adapter blocked" : "Wi-Fi & Bluetooth adapter unblocked"); - root.airplaneModeToggled = false; - } - } - - Process { - id: checkAirplaneMode - running: false - command: ["rfkill", "list"] - stdout: StdioCollector { - onStreamFinished: { - var output = this.text || ""; - var wifiBlocked = /^\d+:.*Wireless LAN[^\n]*\n\s*Soft blocked:\s*yes/im.test(output); - var btBlocked = /^\d+:.*Bluetooth[^\n]*\n\s*Soft blocked:\s*yes/im.test(output); - var isAirplaneModeActive = wifiBlocked && btBlocked; - - // Check if airplane mode has been toggled - if (isAirplaneModeActive && !root.airplaneModeEnabled) { - root.airplaneModeToggled = true; - NetworkService.setWifiEnabled(false); - Settings.data.network.airplaneModeEnabled = true; - ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.enabled"), "plane"); - Logger.i("AirplaneMode", "Wi-Fi & Bluetooth adapter blocked"); - } else if (!isAirplaneModeActive && root.airplaneModeEnabled) { - root.airplaneModeToggled = true; - NetworkService.setWifiEnabled(true); - Settings.data.network.airplaneModeEnabled = false; - ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.disabled"), "plane-off"); - Logger.i("AirplaneMode", "Wi-Fi & Bluetooth adapter unblocked"); - } else if (adapter ? adapter.enabled : root.ctlPowered) { - ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.enabled"), "bluetooth"); - Logger.d("Bluetooth", "Adapter enabled"); - } else { - ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.disabled"), "bluetooth-off"); - Logger.d("Bluetooth", "Adapter disabled"); - } - root.airplaneModeToggled = false; - } - } - stderr: StdioCollector { - onStreamFinished: { - if (text && text.trim()) { - Logger.w("AirplaneMode", "rfkill stderr:", text.trim()); - } - } - } - } - - // Periodic state polling - readonly property bool _lockScreenActive: PanelService.lockScreen?.active ?? false - Timer { id: ctlPollTimer - interval: adapter ? ctlPollMs : 2000 - repeat: true - running: (adapter || ProgramCheckerService.bluetoothctlAvailable) && !_lockScreenActive - onTriggered: { - pollCtlState(); - var targetInterval = adapter ? ctlPollMs : 2000; - if (interval !== targetInterval) { - interval = targetInterval; - } - } - } - - function requestCtlPoll(delayMs) { - if (!adapter && !ProgramCheckerService.bluetoothctlAvailable) { - return; - } - ctlPollTimer.interval = Math.max(50, delayMs || ctlPollSoonMs); - ctlPollTimer.restart(); - } - - function pollCtlState() { - if (!adapter || !ProgramCheckerService.bluetoothctlAvailable) { - return; - } - if (ctlShowProcess.running) { - return; - } - try { - ctlShowProcess.running = true; - } catch (_) {} - } - - // bluetoothctl state polling - Process { - id: ctlShowProcess - command: ["bluetoothctl", "show"] + interval: 2000 running: false - stdout: StdioCollector { - id: ctlStdout - } - onExited: function (exitCode, exitStatus) { - try { - var text = ctlStdout.text || ""; - var lines = text.split('\n'); - var foundController = false; - var powered = false; - var powerBlocked = false; - var discoverable = false; - var discovering = false; - - for (var i = 0; i < lines.length; i++) { - var line = lines[i].trim(); - if (line.indexOf("Controller") === 0) { - foundController = true; - } - - var mp = line.match(/\bPowered:\s*(yes|no)\b/i); - if (mp) { - powered = (mp[1].toLowerCase() === "yes"); - } - - var mps = line.match(/\bPowerState:\s*([A-Za-z-]+)\b/i); - if (mps) { - powerBlocked = (mps[1].toLowerCase() === "off-blocked"); - } - - var md = line.match(/\bDiscoverable:\s*(yes|no)\b/i); - if (md) { - discoverable = (md[1].toLowerCase() === "yes"); - } - - var ms = line.match(/\bDiscovering:\s*(yes|no)\b/i); - if (ms) { - discovering = (ms[1].toLowerCase() === "yes"); - } - } - - if (!adapter && (root.ctlPowered !== powered || root.ctlPowerBlocked !== powerBlocked)) { - root.ctlPowered = powered; - root.ctlPowerBlocked = powerBlocked; - if (root._ctlInit) { - checkAirplaneMode.running = true; - } - root._ctlInit = true; - } - - root.ctlAvailable = foundController; - root.ctlPowered = powered; - root.ctlPowerBlocked = powerBlocked; - root.ctlDiscoverable = discoverable; - root.ctlDiscovering = discovering; - } catch (e) { - Logger.d("Bluetooth", "Failed to parse bluetoothctl show output", e); + onTriggered: { + if (!adapter || !ProgramCheckerService.bluetoothctlAvailable) { + return; } + ctlPollProcess.running = true; } } - // Persistent process for bluetoothctl scanning when native discovery is unavailable - Process { - id: bluetoothctlScanProcess - command: ["bluetoothctl", "scan", "on"] - onExited: Logger.d("Bluetooth", "bluetoothctl scan process exited.") - } - - // Unify discovery controls - function setScanActive(active) { - if (!adapter && !ProgramCheckerService.bluetoothctlAvailable) { - Logger.d("Bluetooth", "Scan request ignored: bluetoothctl unavailable"); - return; - } - var nativeSuccess = false; - try { - if (adapter && adapter.discovering !== undefined) { - if (active || adapter.discovering) { // Only attempt to set if activating, or if deactivating and currently currently discovering - adapter.discovering = active; - } - nativeSuccess = true; // Mark as success if adapter was handled without error - } - } catch (e) { - Logger.e("Bluetooth", "setScanActive native failed", e); - } - - if (!nativeSuccess) { - if (active) { - bluetoothctlScanProcess.running = true; - } else { - bluetoothctlScanProcess.running = false; - btExec(["bluetoothctl", "scan", "off"]); - } - } else if (bluetoothctlScanProcess.running) { - bluetoothctlScanProcess.running = false; - } - - requestCtlPoll(ctlPollSoonMs); - } - // Adapter power (enable/disable) via bluetoothctl function setBluetoothEnabled(state) { - Logger.i("Bluetooth", "SetBluetoothEnabled", state); - if (!adapter && !ProgramCheckerService.bluetoothctlAvailable) { - Logger.i("Bluetooth", "Enable/Disable skipped: no adapter or bluetoothctl"); + if (!adapter) { + Logger.d("Bluetooth", "Enable/Disable skipped: no adapter"); return; } try { - if (adapter) { - adapter.enabled = state; - } else { - root.ctlPowered = state; - btExec(["bluetoothctl", "power", state ? "on" : "off"]); - ToastService.showNotice(I18n.tr("common.bluetooth"), state ? I18n.tr("common.enabled") : I18n.tr("common.disabled"), state ? "bluetooth" : "bluetooth-off"); - Logger.d("Bluetooth", state ? "Adapter enabled" : "Adapter disabled"); - } + adapter.enabled = state; + Logger.i("Bluetooth", "SetBluetoothEnabled", state); } catch (e) { Logger.w("Bluetooth", "Enable/Disable failed", e); ToastService.showWarning(I18n.tr("common.bluetooth"), I18n.tr("toast.bluetooth.state-change-failed")); } } - // Toggle adapter discoverability (advertising visibility) via bluetoothctl - function setDiscoverable(state) { - if (!adapter && !ProgramCheckerService.bluetoothctlAvailable) { - Logger.d("Bluetooth", "Discoverable change skipped: no adapter or bluetoothctl"); + function checkAirplaneMode() { + var isAirplaneModeActive = !NetworkService.wifiEnabled && adapter.state === BluetoothAdapter.Blocked + // Check if airplane mode has been toggled + if (isAirplaneModeActive && !NetworkService.airplaneModeEnabled) { + NetworkService.airplaneModeToggled = true; + NetworkService.airplaneModeEnabled = true; + ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.enabled"), "plane"); + Logger.i("AirplaneMode", "Wi-Fi & Bluetooth adapter blocked"); + } else if (!isAirplaneModeActive && NetworkService.airplaneModeEnabled) { + NetworkService.airplaneModeToggled = true; + NetworkService.airplaneModeEnabled = false; + ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.disabled"), "plane-off"); + Logger.i("AirplaneMode", "Wi-Fi & Bluetooth adapter unblocked"); + } else if (adapter.enabled) { + ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.enabled"), "bluetooth"); + Logger.d("Bluetooth", "Adapter enabled"); + } else { + ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.disabled"), "bluetooth-off"); + Logger.d("Bluetooth", "Adapter disabled"); + } + } + + // Unify discovery controls + function setScanActive(active) { + if (!adapter) { + Logger.d("Bluetooth", "Scan request ignored: adapter unavailable"); return; } try { - if (adapter) { - adapter.discoverable = state; - } else { - btExec(["bluetoothctl", "discoverable", state ? "on" : "off"]); - root.ctlDiscoverable = state; // optimistic - requestCtlPoll(ctlPollSoonMs); + if (active || adapter.discovering) { // Only attempt to set if activating, or if deactivating and currently currently discovering + adapter.discovering = active; } + } catch (e) { + Logger.e("Bluetooth", "setScanActive failed", e); + } + } + + // Toggle adapter discoverability (advertising visibility) via bluetoothctl + function setDiscoverable(state) { + if (!adapter) { + Logger.d("Bluetooth", "Discoverable change skipped: no adapter"); + return; + } + try { + adapter.discoverable = state; Logger.i("Bluetooth", "Discoverable state set to:", state); } catch (e) { Logger.w("Bluetooth", "Failed to change discoverable state", e); @@ -566,9 +342,6 @@ Singleton { } } - // Interaction state - property bool pinRequired: false - function submitPin(pin) { if (pairingProcess.running) { pairingProcess.write(pin + "\n"); @@ -583,33 +356,6 @@ Singleton { root.pinRequired = false; } - // Interactive pairing process - Process { - id: pairingProcess - stdout: SplitParser { - onRead: data => { - Logger.d("Bluetooth", data); - if (data.indexOf("PIN_REQUIRED") !== -1) { - root.pinRequired = true; - Logger.i("Bluetooth", "PIN required for pairing"); - } - } - } - onExited: { - root.pinRequired = false; - Logger.i("Bluetooth", "Pairing process exited."); - // Restore discovery if we paused it - if (root._discoveryWasRunning) { - root.setScanActive(true); - } - root._discoveryWasRunning = false; - root.requestCtlPoll(); - } - environment: ({ - "LC_ALL": "C" - }) - } - // Pair using bluetoothctl which registers its own BlueZ agent internally. function pairWithBluetoothctl(device) { if (!device) { @@ -675,8 +421,35 @@ Singleton { forgetDevice(device); } + function getDeviceAutoConnect(device) { + if (!device || !device.address || !cacheAdapter.autoConnectSettings) { + return false; + } + const mac = device.address; + const settings = cacheAdapter.autoConnectSettings[mac]; + return settings ? !!settings.autoConnect : false; + } + + function setDeviceAutoConnect(device, enabled) { + if (!device || !device.address) { + return; + } + const mac = device.address; + let settings = cacheAdapter.autoConnectSettings || ({}); + if (enabled) { + settings[mac] = { + autoConnect: true, + deviceName: device.name || device.deviceName || "" + }; + } else { + delete settings[mac]; + } + cacheAdapter.autoConnectSettings = settings; + cacheFileView.writeAdapter(); + } + function attemptAutoConnect() { - if (airplaneModeEnabled || !adapter || !adapter.enabled || !Settings.data.network.bluetoothAutoConnect) { + if (NetworkService.airplaneModeEnabled || !adapter || !adapter.enabled || !Settings.data.network.bluetoothAutoConnect) { return; } @@ -724,4 +497,56 @@ Singleton { ToastService.showWarning(I18n.tr("common.bluetooth"), I18n.tr("toast.bluetooth.forget-failed")); } } + + // Poll Bluetooth power state with bluetoothctl to handle a Quickshell bug on resume after suspend + Process { + id: ctlPollProcess + command: ["bluetoothctl", "show"] + running: false + stdout: StdioCollector { + onStreamFinished: { + var powered = false; + var mp = text.match(/\bPowered:\s*(yes|no)\b/i); + if (mp) { + powered = mp[1].toLowerCase() === 'yes'; + } + if (adapter.enabled !== powered) { + adapter.enabled = powered; + } + } + } + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + Logger.d("Bluetooth", "Failed to parse bluetoothctl show output" + text); + } + } + } + } + + // Interactive pairing process + Process { + id: pairingProcess + stdout: SplitParser { + onRead: data => { + Logger.d("Bluetooth", data); + if (data.indexOf("PIN_REQUIRED") !== -1) { + root.pinRequired = true; + Logger.i("Bluetooth", "PIN required for pairing"); + } + } + } + onExited: { + root.pinRequired = false; + Logger.i("Bluetooth", "Pairing process exited."); + // Restore discovery if we paused it + if (root._discoveryWasRunning) { + root.setScanActive(true); + } + root._discoveryWasRunning = false; + } + environment: ({ + "LC_ALL": "C" + }) + } } diff --git a/Services/Networking/NetworkService.qml b/Services/Networking/NetworkService.qml index 43ec1216d..f99bbf407 100644 --- a/Services/Networking/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -59,6 +59,7 @@ Singleton { property bool _internetConnectivity: false property string lastError: "" property int activeDetailsTtlMs: 10000 + // Ethernet properties property var ethernetInterfaces: ([]) property var activeEthernetDetails: ({}) @@ -66,6 +67,7 @@ Singleton { property string activeEthernetIf: "" property bool ethernetDetailsLoading: false property double activeEthernetDetailsTimestamp: 0 + // Wi-Fi properties readonly property bool wifiEnabled: Networking.wifiEnabled property var networks: ({}) @@ -75,6 +77,7 @@ Singleton { property bool wifiDetailsLoading: false property double activeWifiDetailsTimestamp: 0 property bool wifiInit: false + // Wi-Fi adapter/connection properties property bool connecting: false property string connectingTo: "" @@ -84,10 +87,14 @@ Singleton { property bool scanningActive: false property var existingProfiles: ({}) + // Airplane mode status + property bool airplaneModeEnabled: false + property bool airplaneModeToggled: false + Connections { target: root function onWifiEnabledChanged() { - if (!root.wifiInit || BluetoothService.airplaneModeToggled) { + if (!root.wifiInit) { return; } wifiDebounce.restart(); @@ -129,11 +136,14 @@ Singleton { // Prevent an initial "Wi-Fi enabled" toast and trigger initial scan Timer { id: wifiInitTimer - interval: 100 + interval: 500 onTriggered: { root.wifiInit = true; if (root.wifiEnabled) { - root.scan(); + scan(); + } + if (!root.wifiEnabled && BluetoothService.blocked) { + root.airplaneModeEnabled = true; } } } @@ -143,14 +153,17 @@ Singleton { id: wifiDebounce interval: 300 onTriggered: { + if (root.airplaneModeToggled) { + root.airplaneModeToggled = false; + return; + } if (root.wifiEnabled) { ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.enabled"), "wifi"); connectivityCheckProcess.running = true; deviceStatusProcess.running = true; - root.scan(); + scan(); } else { ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.disabled"), "wifi-off"); - root.scanningActive = false; root.networks = ({}); } } @@ -172,30 +185,6 @@ Singleton { onTriggered: scan() } - // Refresh details for the currently active Wi‑Fi link - function refreshActiveWifiDetails() { - const now = Date.now(); - if (wifiDetailsLoading || (activeWifiIf && wifiConnected && activeWifiDetails && (now - activeWifiDetailsTimestamp) < activeDetailsTtlMs)) { - return; - } - if (wifiConnected && activeWifiIf) { - wifiDetailsLoading = true; - deviceStatusProcess.running = true; - } - } - - // Refresh details for the currently active Ethernet link - function refreshActiveEthernetDetails() { - const now = Date.now(); - if (ethernetDetailsLoading || activeEthernetIf && activeEthernetDetails && (now - activeEthernetDetailsTimestamp) < activeDetailsTtlMs) { - return; - } - if (ethernetConnected && activeEthernetIf) { - ethernetDetailsLoading = true; - deviceStatusProcess.running = true; - } - } - // Core functions function setWifiEnabled(enabled) { if (!ProgramCheckerService.nmcliAvailable) { @@ -205,6 +194,16 @@ Singleton { Networking.wifiEnabled = enabled; } + function setAirplaneMode(state) { + if (state) { + Quickshell.execDetached(["rfkill", "block", "wifi"]); + Quickshell.execDetached(["rfkill", "block", "bluetooth"]); + } else { + Quickshell.execDetached(["rfkill", "unblock", "wifi"]); + Quickshell.execDetached(["rfkill", "unblock", "bluetooth"]); + } + } + function scan() { if (!ProgramCheckerService.nmcliAvailable || !root.wifiEnabled) { return; @@ -276,6 +275,30 @@ Singleton { forgetProcess.running = true; } + // Refresh details for the currently active Wi‑Fi link + function refreshActiveWifiDetails() { + const now = Date.now(); + if (wifiDetailsLoading || (activeWifiIf && wifiConnected && activeWifiDetails && (now - activeWifiDetailsTimestamp) < activeDetailsTtlMs)) { + return; + } + if (wifiConnected && activeWifiIf) { + wifiDetailsLoading = true; + deviceStatusProcess.running = true; + } + } + + // Refresh details for the currently active Ethernet link + function refreshActiveEthernetDetails() { + const now = Date.now(); + if (ethernetDetailsLoading || activeEthernetIf && activeEthernetDetails && (now - activeEthernetDetailsTimestamp) < activeDetailsTtlMs) { + return; + } + if (ethernetConnected && activeEthernetIf) { + ethernetDetailsLoading = true; + deviceStatusProcess.running = true; + } + } + // Helper function to immediately update network status function updateNetworkStatus(ssid, connected) { let nets = networks; @@ -411,7 +434,7 @@ Singleton { return root.connectingTo ? I18n.tr("common.connecting") + " " + root.connectingTo : I18n.tr("common.connecting"); } - if (Settings.data.network.airplaneModeEnabled) { + if (NetworkService.airplaneModeEnabled) { return I18n.tr("toast.airplane-mode.title"); } if (!root.wifiEnabled) { @@ -438,7 +461,7 @@ Singleton { } function getIcon(forceEthernet = false) { - if (Settings.data.network.airplaneModeEnabled && !forceEthernet) { + if (NetworkService.airplaneModeEnabled && !forceEthernet) { return "plane"; } diff --git a/shell.qml b/shell.qml index 6fc7bd80b..9a9b88695 100644 --- a/shell.qml +++ b/shell.qml @@ -107,7 +107,6 @@ ShellRoot { Qt.callLater(function () { LocationService.init(); NightLightService.apply(); - BluetoothService.init(); IdleInhibitorService.init(); IdleService.init(); PowerProfileService.init(); From b2c685decb80048c7332d1f32dd8520dd3f5acb2 Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Wed, 25 Mar 2026 00:29:20 +0100 Subject: [PATCH 2/7] remove airplaneModeEnabled from settings --- Assets/settings-default.json | 1 - Commons/Settings.qml | 1 - 2 files changed, 2 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 96e7f1a29..bc8af9233 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -379,7 +379,6 @@ "indicatorOpacity": 0.6 }, "network": { - "airplaneModeEnabled": false, "bluetoothRssiPollingEnabled": false, "bluetoothRssiPollIntervalMs": 60000, "networkPanelView": "wifi", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index d438e0a44..53d63d2b7 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -576,7 +576,6 @@ Singleton { // network property JsonObject network: JsonObject { - property bool airplaneModeEnabled: false property bool bluetoothRssiPollingEnabled: false // Opt-in Bluetooth RSSI polling (uses bluetoothctl) property int bluetoothRssiPollIntervalMs: 60000 // Polling interval in milliseconds for RSSI queries property string networkPanelView: "wifi" From 78c0b2fe253c57c00971db8fc5e368d4a240f92f Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:58:05 +0100 Subject: [PATCH 3/7] improve airplane mode handling --- Services/Networking/BluetoothService.qml | 8 +++--- Services/Networking/NetworkService.qml | 32 +++++++++++++++++++++--- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Services/Networking/BluetoothService.qml b/Services/Networking/BluetoothService.qml index d81ac48aa..e29dce9d6 100644 --- a/Services/Networking/BluetoothService.qml +++ b/Services/Networking/BluetoothService.qml @@ -147,7 +147,7 @@ Singleton { Timer { id: ctlPollTimer - interval: 2000 + interval: 250 running: false onTriggered: { if (!adapter || !ProgramCheckerService.bluetoothctlAvailable) { @@ -172,19 +172,19 @@ Singleton { } } + // Check if airplane mode has been toggled function checkAirplaneMode() { var isAirplaneModeActive = !NetworkService.wifiEnabled && adapter.state === BluetoothAdapter.Blocked - // Check if airplane mode has been toggled if (isAirplaneModeActive && !NetworkService.airplaneModeEnabled) { NetworkService.airplaneModeToggled = true; NetworkService.airplaneModeEnabled = true; ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.enabled"), "plane"); - Logger.i("AirplaneMode", "Wi-Fi & Bluetooth adapter blocked"); + Logger.i("AirplaneMode", "Enabled"); } else if (!isAirplaneModeActive && NetworkService.airplaneModeEnabled) { NetworkService.airplaneModeToggled = true; NetworkService.airplaneModeEnabled = false; ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.disabled"), "plane-off"); - Logger.i("AirplaneMode", "Wi-Fi & Bluetooth adapter unblocked"); + Logger.i("AirplaneMode", "Disabled"); } else if (adapter.enabled) { ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.enabled"), "bluetooth"); Logger.d("Bluetooth", "Adapter enabled"); diff --git a/Services/Networking/NetworkService.qml b/Services/Networking/NetworkService.qml index f99bbf407..ec8071911 100644 --- a/Services/Networking/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -155,6 +155,31 @@ Singleton { onTriggered: { if (root.airplaneModeToggled) { root.airplaneModeToggled = false; + if (root.wifiEnabled) { + connectivityCheckProcess.running = true; + deviceStatusProcess.running = true; + scan(); + } else { + root.networks = ({}); + } + return; + } + var isAirplaneModeActive = !root.wifiEnabled && BluetoothService.blocked + // Extra check for Airplane Mode if Bluetooth has been blocked before Wi-Fi + if (isAirplaneModeActive && !root.airplaneModeEnabled) { + root.airplaneModeEnabled = true; + ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.enabled"), "plane"); + Logger.i("AirplaneMode", "Enabled"); + root.networks = ({}); + return; + } + // Extra check for Airplane Mode if Wi-Fi has been unblocked before Bluetooth + if (!isAirplaneModeActive && root.airplaneModeEnabled) { + root.airplaneModeEnabled = false; + ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.disabled"), "plane-off"); + Logger.i("AirplaneMode", "Disabled"); + deviceStatusProcess.restart(); + scan(); return; } if (root.wifiEnabled) { @@ -196,11 +221,10 @@ Singleton { function setAirplaneMode(state) { if (state) { - Quickshell.execDetached(["rfkill", "block", "wifi"]); - Quickshell.execDetached(["rfkill", "block", "bluetooth"]); + Quickshell.execDetached(["rfkill", "block", "all"]); } else { - Quickshell.execDetached(["rfkill", "unblock", "wifi"]); - Quickshell.execDetached(["rfkill", "unblock", "bluetooth"]); + Quickshell.execDetached(["rfkill", "unblock", "all"]); + } } From dffdfcffc4bfba7613465f6c5b92f2fd24d8458a Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Thu, 26 Mar 2026 19:30:41 +0100 Subject: [PATCH 4/7] improve some process handling --- Services/Networking/BluetoothService.qml | 10 ---------- Services/Networking/NetworkService.qml | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/Services/Networking/BluetoothService.qml b/Services/Networking/BluetoothService.qml index e29dce9d6..972ffd599 100644 --- a/Services/Networking/BluetoothService.qml +++ b/Services/Networking/BluetoothService.qml @@ -80,16 +80,6 @@ Singleton { } } - // Re-run polling once bluetoothctl availability is known - Connections { - target: ProgramCheckerService - function onBluetoothctlAvailableChanged() { - if (ProgramCheckerService.bluetoothctlAvailable) { - ctlPollTimer.restart(); - } - } - } - // Track adapter state changes Connections { target: adapter diff --git a/Services/Networking/NetworkService.qml b/Services/Networking/NetworkService.qml index ec8071911..16a41e874 100644 --- a/Services/Networking/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -106,8 +106,8 @@ Singleton { target: Time function onResumed() { Logger.i("Network", "System resumed - forcing state poll"); - deviceStatusProcess.running = true; - connectivityCheckProcess.running = true; + deviceStatusProcess.restart(); + connectivityCheckProcess.restart(); } } @@ -156,8 +156,8 @@ Singleton { if (root.airplaneModeToggled) { root.airplaneModeToggled = false; if (root.wifiEnabled) { - connectivityCheckProcess.running = true; - deviceStatusProcess.running = true; + connectivityCheckProcess.restart(); + deviceStatusProcess.restart(); scan(); } else { root.networks = ({}); @@ -178,14 +178,15 @@ Singleton { root.airplaneModeEnabled = false; ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.disabled"), "plane-off"); Logger.i("AirplaneMode", "Disabled"); + connectivityCheckProcess.restart(); deviceStatusProcess.restart(); scan(); return; } if (root.wifiEnabled) { ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.enabled"), "wifi"); - connectivityCheckProcess.running = true; - deviceStatusProcess.running = true; + connectivityCheckProcess.restart(); + deviceStatusProcess.restart(); scan(); } else { ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.disabled"), "wifi-off"); @@ -224,7 +225,6 @@ Singleton { Quickshell.execDetached(["rfkill", "block", "all"]); } else { Quickshell.execDetached(["rfkill", "unblock", "all"]); - } } @@ -307,7 +307,7 @@ Singleton { } if (wifiConnected && activeWifiIf) { wifiDetailsLoading = true; - deviceStatusProcess.running = true; + deviceStatusProcess.restart(); } } @@ -319,7 +319,7 @@ Singleton { } if (ethernetConnected && activeEthernetIf) { ethernetDetailsLoading = true; - deviceStatusProcess.running = true; + deviceStatusProcess.restart(); } } @@ -1165,7 +1165,7 @@ Singleton { onRead: data => { if (data.endsWith(": connected") || data.endsWith(": disconnected")) { Logger.d("Network", "State changed: " + data); - deviceStatusProcess.running = true; + deviceStatusProcess.restart(); } } } From 477810b954ea86c6dcd169d33acaef180b15924e Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Thu, 26 Mar 2026 22:18:16 +0100 Subject: [PATCH 5/7] change Airplane Mode visibility conditions --- Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml | 1 + .../Panels/Settings/Tabs/Connections/WifiSubTab.qml | 5 +++-- Services/Networking/NetworkService.qml | 11 ++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml b/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml index c0d00c026..d22963b8c 100644 --- a/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml +++ b/Modules/Panels/ControlCenter/Widgets/AirplaneMode.qml @@ -14,4 +14,5 @@ NIconButtonHot { onClicked: { NetworkService.setAirplaneMode(!NetworkService.airplaneModeEnabled); } + enabled: NetworkService.wifiAvailable && BluetoothService.bluetoothAvailable } diff --git a/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml b/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml index c44b88eaa..092ac7720 100644 --- a/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Connections/WifiSubTab.qml @@ -149,7 +149,7 @@ Item { icon: NetworkService.wifiEnabled ? "wifi" : "wifi-off" checked: NetworkService.wifiEnabled onToggled: checked => NetworkService.setWifiEnabled(checked) - enabled: ProgramCheckerService.nmcliAvailable && !NetworkService.airplaneModeEnabled && NetworkService.wifiAvailable + enabled: !NetworkService.airplaneModeEnabled && NetworkService.wifiAvailable Layout.alignment: Qt.AlignVCenter } } @@ -324,7 +324,7 @@ Item { // Airplane Mode NBox { id: miscSettingsBox - visible: !root.showOnlyLists + visible: !root.showOnlyLists && miscSettingsCol.visibleChildren.length > 0 Layout.fillWidth: true Layout.preferredHeight: miscSettingsCol.implicitHeight + Style.margin2XL color: Color.mSurface @@ -336,6 +336,7 @@ Item { spacing: Style.marginM NToggle { + visible: NetworkService.wifiAvailable && BluetoothService.bluetoothAvailable label: I18n.tr("toast.airplane-mode.title") description: I18n.tr("toast.airplane-mode.description") icon: NetworkService.airplaneModeEnabled ? "plane" : "plane-off" diff --git a/Services/Networking/NetworkService.qml b/Services/Networking/NetworkService.qml index 16a41e874..dbd51fe8d 100644 --- a/Services/Networking/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -105,9 +105,11 @@ Singleton { Connections { target: Time function onResumed() { - Logger.i("Network", "System resumed - forcing state poll"); - deviceStatusProcess.restart(); - connectivityCheckProcess.restart(); + if (ProgramCheckerService.nmcliAvailable) { + Logger.i("Network", "System resumed - forcing state poll"); + deviceStatusProcess.restart(); + connectivityCheckProcess.restart(); + } } } @@ -153,6 +155,9 @@ Singleton { id: wifiDebounce interval: 300 onTriggered: { + if (!ProgramCheckerService.nmcliAvailable) { + return; + } if (root.airplaneModeToggled) { root.airplaneModeToggled = false; if (root.wifiEnabled) { From 9462caf6c3de4e6dff0643e41180e42244905309 Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:20:22 +0100 Subject: [PATCH 6/7] remove onResumed() since networkMonitorProcess already covers it --- Services/Networking/NetworkService.qml | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/Services/Networking/NetworkService.qml b/Services/Networking/NetworkService.qml index dbd51fe8d..231ab2132 100644 --- a/Services/Networking/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -101,18 +101,6 @@ Singleton { } } - // Handle system resume to refresh state and connectivity - Connections { - target: Time - function onResumed() { - if (ProgramCheckerService.nmcliAvailable) { - Logger.i("Network", "System resumed - forcing state poll"); - deviceStatusProcess.restart(); - connectivityCheckProcess.restart(); - } - } - } - // Start initial checks when nmcli becomes available Connections { target: ProgramCheckerService @@ -161,8 +149,6 @@ Singleton { if (root.airplaneModeToggled) { root.airplaneModeToggled = false; if (root.wifiEnabled) { - connectivityCheckProcess.restart(); - deviceStatusProcess.restart(); scan(); } else { root.networks = ({}); @@ -183,15 +169,11 @@ Singleton { root.airplaneModeEnabled = false; ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.disabled"), "plane-off"); Logger.i("AirplaneMode", "Disabled"); - connectivityCheckProcess.restart(); - deviceStatusProcess.restart(); scan(); return; } if (root.wifiEnabled) { ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.enabled"), "wifi"); - connectivityCheckProcess.restart(); - deviceStatusProcess.restart(); scan(); } else { ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.disabled"), "wifi-off"); @@ -312,7 +294,7 @@ Singleton { } if (wifiConnected && activeWifiIf) { wifiDetailsLoading = true; - deviceStatusProcess.restart(); + deviceStatusProcess.running = true; } } @@ -324,7 +306,7 @@ Singleton { } if (ethernetConnected && activeEthernetIf) { ethernetDetailsLoading = true; - deviceStatusProcess.restart(); + deviceStatusProcess.running = true; } } @@ -1170,7 +1152,8 @@ Singleton { onRead: data => { if (data.endsWith(": connected") || data.endsWith(": disconnected")) { Logger.d("Network", "State changed: " + data); - deviceStatusProcess.restart(); + deviceStatusProcess.running = true; + connectivityCheckProcess.running = true; } } } From b3a9aca83748d0d94ccf97ca0da8a25b3e0bd14e Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:12:36 +0200 Subject: [PATCH 7/7] Add check for non-zero width in enhancedBand --- Services/Networking/NetworkService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Networking/NetworkService.qml b/Services/Networking/NetworkService.qml index 231ab2132..0a71f3b9b 100644 --- a/Services/Networking/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -678,7 +678,7 @@ Singleton { } let enhancedBand = band; - if (channel && width) { + if (channel && width && width !== "0 MHz") { enhancedBand = `${band} / ${channel} (${width})`; } else if (channel) { enhancedBand = `${band} / ${channel}`;