Fix airplane mode logic & some cleanup

This commit is contained in:
notiant
2026-02-12 02:55:40 +01:00
parent d05b46bd90
commit 4b513b4d8f
13 changed files with 161 additions and 144 deletions
+3 -1
View File
@@ -335,11 +335,13 @@
},
"network": {
"wifiEnabled": true,
"airplaneModeEnabled": false,
"bluetoothRssiPollingEnabled": false,
"bluetoothRssiPollIntervalMs": 60000,
"wifiDetailsViewMode": "grid",
"bluetoothDetailsViewMode": "grid",
"bluetoothHideUnnamedDevices": false
"bluetoothHideUnnamedDevices": false,
"disableDiscoverability": false
},
"sessionMenu": {
"enableCountdown": true,
+1 -1
View File
@@ -118,7 +118,7 @@ Singleton {
"settings-launcher": "rocket",
"settings-audio": "device-speaker",
"settings-display": "device-desktop",
"settings-network": "affiliate",
"settings-network": "circles-relation",
"settings-brightness": "brightness-up",
"settings-location": "world-pin",
"settings-color-scheme": "palette",
+2
View File
@@ -540,11 +540,13 @@ Singleton {
// network
property JsonObject network: JsonObject {
property bool wifiEnabled: true
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 wifiDetailsViewMode: "grid" // "grid" or "list"
property string bluetoothDetailsViewMode: "grid" // "grid" or "list"
property bool bluetoothHideUnnamedDevices: false
property bool disableDiscoverability: false
}
// session menu
+2 -2
View File
@@ -51,7 +51,7 @@ Item {
"icon": Settings.data.network.wifiEnabled ? "wifi-off" : "wifi"
},
{
"label": I18n.tr("tooltips.manage-wifi") + " " + I18n.tr("tooltips.open-settings"),
"label": I18n.tr("common.wifi") + " " + I18n.tr("tooltips.open-settings"),
"action": "wifi-settings",
"icon": "settings"
},
@@ -161,7 +161,7 @@ Item {
} catch (e) {
// noop
}
return I18n.tr("tooltips.manage-wifi");
return I18n.tr("common.wifi");
}
}
}
@@ -46,7 +46,7 @@ NIconButtonHot {
} catch (e) {
// noop
}
return I18n.tr("tooltips.manage-wifi");
return I18n.tr("common.wifi");
}
onClicked: {
var panel = PanelService.getPanel("networkPanel", screen);
+3 -3
View File
@@ -140,13 +140,13 @@ SmartPanel {
panelViewMode = "wifi";
}
}
onEntered: TooltipService.show(parent, panelViewMode === "wifi" ? I18n.tr("control-center.wifi.label-ethernet") : I18n.tr("wifi.panel.title"))
onEntered: TooltipService.show(parent, panelViewMode === "wifi" ? I18n.tr("control-center.wifi.label-ethernet") : I18n.tr("common.wifi"))
onExited: TooltipService.hide()
}
}
NText {
text: panelViewMode === "wifi" ? I18n.tr("wifi.panel.title") : I18n.tr("control-center.wifi.label-ethernet")
text: panelViewMode === "wifi" ? I18n.tr("common.wifi") : I18n.tr("control-center.wifi.label-ethernet")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
@@ -198,7 +198,7 @@ SmartPanel {
}
NTabButton {
text: I18n.tr("tooltips.manage-wifi")
text: I18n.tr("common.wifi")
tabIndex: 0
checked: modeTabBar.currentIndex === 0
}
+2 -2
View File
@@ -402,7 +402,7 @@ NBox {
const value = NetworkService.activeWifiIf || "";
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.bluetooth.address-copied"), "wifi");
ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("toast.bluetooth.address-copied"), "wifi");
}
}
}
@@ -545,7 +545,7 @@ NBox {
const value = NetworkService.activeWifiDetails.ipv4 || "";
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.bluetooth.address-copied"), "wifi");
ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("toast.bluetooth.address-copied"), "wifi");
}
}
}
@@ -104,15 +104,15 @@ Item {
function _updateScanningState() {
if (effectivelyVisible && BluetoothService.enabled && !showOnlyLists) {
Logger.d("Bluetooth Prefs", "Panel/Tab Active");
Logger.d("BluetoothPrefs", "Panel/tab active");
if (!isScanningActive) {
BluetoothService.setScanActive(true);
}
if (!isDiscoverable) {
if (!Settings.data.network.disableDiscoverability && !isDiscoverable) {
BluetoothService.setDiscoverable(true);
}
} else {
Logger.d("Bluetooth Prefs", "Panel/Tab Inactive");
Logger.d("BluetoothPrefs", "Panel/tab inactive");
if (isScanningActive) {
BluetoothService.setScanActive(false);
}
@@ -131,7 +131,7 @@ Item {
if (isDiscoverable) {
BluetoothService.setDiscoverable(false);
}
Logger.d("Bluetooth Prefs", "Panel Closed");
Logger.d("BluetoothPrefs", "Panel closed");
}
ColumnLayout {
@@ -161,11 +161,8 @@ Item {
color: BluetoothService.enabled ? Color.mPrimary : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("common.bluetooth")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
NLabel {
label: I18n.tr("common.bluetooth")
}
Item {
@@ -186,9 +183,7 @@ Item {
}
NText {
text: I18n.tr("panels.connections.bluetooth-discoverable", {
hostName: HostService.hostName
})
text: I18n.tr("panels.connections.bluetooth-discoverable", {hostName: HostService.hostName})
visible: (BluetoothService.enabled && isDiscoverable)
richTextEnabled: true
wrapMode: Text.WordWrap
@@ -320,19 +315,31 @@ Item {
}
NToggle {
label: I18n.tr("tooltips.hide-unnamed-devices")
description: "Hide devices that appear only as Bluetooth addresses."
checked: Settings.data && Settings.data.network && Settings.data.network.bluetoothHideUnnamedDevices
label: I18n.tr("panels.connections.hide-unnamed-devices-label") // former i18n key: tooltips.hide-unnamed-devices
description: I18n.tr("panels.connections.hide-unnamed-devices-description") // "Hide devices that appear only as Bluetooth addresses."
checked: Settings.data.network.bluetoothHideUnnamedDevices
onToggled: checked => Settings.data.network.bluetoothHideUnnamedDevices = checked
Layout.alignment: Qt.AlignVCenter
visible: !btprefs.showOnlyLists && BluetoothService.enabled
}
NToggle {
label: I18n.tr("panels.connections.disable-discoverability-label") // "Disable device visibility"
description: I18n.tr("panels.connections.disable-discoverability-description") // "Hide your device from nearby Bluetooth devices."
checked: Settings.data.network.disableDiscoverability
onToggled: checked => {
Settings.data.network.disableDiscoverability = checked;
BluetoothService.setDiscoverable(!checked);
}
Layout.alignment: Qt.AlignVCenter
visible: !btprefs.showOnlyLists && BluetoothService.enabled
}
// RSSI Polling
NToggle {
label: I18n.tr("panels.connections.bluetooth-rssi-polling-label")
description: I18n.tr("panels.connections.bluetooth-rssi-polling-description")
checked: Settings.data && Settings.data.network && Settings.data.network.bluetoothRssiPollingEnabled
checked: Settings.data.network.bluetoothRssiPollingEnabled
onToggled: checked => Settings.data.network.bluetoothRssiPollingEnabled = checked
Layout.alignment: Qt.AlignVCenter
visible: !btprefs.showOnlyLists && BluetoothService.enabled
@@ -343,12 +350,12 @@ Item {
from: 10000
to: 120000
stepSize: 1000
value: Settings.data && Settings.data.network && Settings.data.network.bluetoothRssiPollIntervalMs
value: Settings.data.network.bluetoothRssiPollIntervalMs
defaultValue: Settings.getDefaultValue("network.bluetoothRssiPollIntervalMs")
onValueChanged: Settings.data.network.bluetoothRssiPollIntervalMs = value
suffix: " ms"
Layout.alignment: Qt.AlignVCenter
visible: (!btprefs.showOnlyLists && BluetoothService.enabled) && Settings.data && Settings.data.network && Settings.data.network.bluetoothRssiPollingEnabled
visible: (!btprefs.showOnlyLists && BluetoothService.enabled) && Settings.data.network.bluetoothRssiPollingEnabled
}
}
@@ -1,25 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services.Networking
import qs.Widgets
Item {
id: root
Layout.fillWidth: true
// TBD: Implement Ethernet settings
implicitHeight: placeholder.implicitHeight
NBox {
id: placeholder
anchors.fill: parent
implicitHeight: 100
NText {
anchors.centerIn: parent
text: "Ethernet Settings - Coming Soon"
color: Color.mOnSurfaceVariant
}
}
}
@@ -25,6 +25,43 @@ Item {
anchors.right: parent.right
spacing: Style.marginL
// Airplane Mode Toggle
NBox {
Layout.fillWidth: true
Layout.preferredHeight: masterControlColAirplane.implicitHeight
ColumnLayout {
id: masterControlColAirplane
anchors.fill: parent
spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
NIcon {
icon: Settings.data.network.airplaneModeEnabled ? "plane" : "plane-off"
pointSize: Style.fontSizeXXL
color: Settings.data.network.airplaneModeEnabled ? Color.mPrimary : Color.mOnSurfaceVariant
}
NLabel {
label: I18n.tr("toast.airplane-mode.title")
}
Item {
Layout.fillWidth: true
}
NToggle {
checked: Settings.data.network.airplaneModeEnabled
onToggled: checked => NetworkService.setAirplaneMode(checked)
Layout.alignment: Qt.AlignVCenter
}
}
}
}
// Wi-Fi Master Control
NBox {
Layout.fillWidth: true
@@ -45,11 +82,11 @@ Item {
color: Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("tooltips.manage-wifi")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
NLabel {
label: I18n.tr("common.wifi")
}
Item {
Layout.fillWidth: true
}
@@ -57,10 +94,10 @@ Item {
checked: Settings.data.network.wifiEnabled
onToggled: checked => NetworkService.setWifiEnabled(checked)
Layout.alignment: Qt.AlignVCenter
enabled: !NetworkService.wifiBlocked
enabled: ProgramCheckerService.nmcliAvailable
}
}
}
}
}
}
}
@@ -18,7 +18,7 @@ ColumnLayout {
currentIndex: tabView.currentIndex
NTabButton {
text: I18n.tr("tooltips.manage-wifi")
text: I18n.tr("common.wifi")
// visible: NetworkService.wifiAvailable
enabled: NetworkService.wifiAvailable // Remove when work finished, only use visibility
tabIndex: 0
@@ -31,13 +31,6 @@ ColumnLayout {
tabIndex: 1
checked: subTabBar.currentIndex === 1
}
NTabButton {
text: I18n.tr("panels.connections.ethernet")
// visible: NetworkService.ethernetAvailable
enabled: NetworkService.ethernetAvailable // Remove when work finished, only use visibility
tabIndex: 2
checked: subTabBar.currentIndex === 2
}
}
Item {
@@ -51,6 +44,5 @@ ColumnLayout {
currentIndex: subTabBar.currentIndex
WifiSubTab {}
BluetoothSubTab {}
EthernetSubTab {}
}
}
+59 -10
View File
@@ -17,16 +17,20 @@ Singleton {
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
// Airplane mode status
property bool airplaneModeToggled: false
property bool wifiBlocked: false
property bool btBlocked: false
// Power/blocked/availability state
readonly property bool bluetoothAvailable: !!adapter
readonly property bool enabled: adapter ? adapter.enabled : root.ctlPowered
readonly property bool blocked: adapter?.state === BluetoothAdapterState.Blocked
readonly property bool enabled: adapter?.enabled ?? root.ctlPowered
property bool ctlPowered: false
property bool ctlDiscovering: false
property bool ctlDiscoverable: false
// Adapter discoverability (advertising) flag (driven by bluetoothctl)
readonly property bool discoverable: root.ctlDiscoverable
// Adapter discoverability (advertising) flag
readonly property bool discoverable: adapter?.discoverable ?? root.ctlDiscoverable
readonly property var devices: adapter ? adapter.devices : null
readonly property var connectedDevices: {
if (!adapter || !adapter.devices) {
@@ -94,7 +98,7 @@ Singleton {
}
// Exposed scanning flag for UI button state; reflects adapter discovery when available
readonly property bool scanningActive: (adapter && adapter.discovering) || root.ctlDiscovering
readonly property bool scanningActive: adapter?.discovering ?? root.ctlDiscovering
function init() {
Logger.i("Bluetooth", "Service started");
@@ -107,13 +111,58 @@ Singleton {
target: adapter
function onStateChanged() {
if (!adapter || adapter.state === BluetoothAdapter.Enabling || adapter.state === BluetoothAdapter.Disabling) {
return;
return;
}
checkAirplaneMode.running = true;
}
}
if (adapter.state === BluetoothAdapter.Enabled) {
ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.enabled"), "bluetooth");
} else if (adapter.state === BluetoothAdapter.Disabled && !root.blocked) {
ToastService.showNotice(I18n.tr("common.bluetooth"), I18n.tr("common.disabled"), "bluetooth-off");
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)
// Track if actual state changed
var actualAirplaneModeActive = wifiBlocked && btBlocked;
var previousAirplaneModeActive = root.wifiBlocked && root.btBlocked;
// Check if airplane mode has been toggled
if (actualAirplaneModeActive && !previousAirplaneModeActive) {
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 (!actualAirplaneModeActive && previousAirplaneModeActive) {
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.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");
}
root.airplaneModeToggled = false;
// Update current blocked states (always reflect actual rfkill state)
root.wifiBlocked = wifiBlocked;
root.btBlocked = btBlocked;
}
}
stderr: StdioCollector {
onStreamFinished: {
if (text && text.trim()) {
Logger.w("AirplaneMode", "rfkill stderr:", text.trim());
}
}
}
}
+18 -65
View File
@@ -70,81 +70,22 @@ Singleton {
}
}
property bool bluetoothBlocked: false
property bool wifiBlocked: false
Connections {
target: Settings.data.network
function onWifiEnabledChanged() {
if (Settings.data.network.wifiEnabled) {
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("common.enabled"), "wifi");
ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.enabled"), "wifi");
// Perform a scan to update the UI
delayedScanTimer.interval = 3000;
delayedScanTimer.restart();
} else {
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("common.disabled"), "wifi-off");
ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("common.disabled"), "wifi-off");
// Clear networks so the widget icon changes
root.networks = ({});
}
}
}
// Poll rfkill status periodically to detect hardware switches
Timer {
id: rfkillPollTimer
interval: 2000
repeat: true
running: true
onTriggered: checkWifiBlocked.running = true
}
// Handle Airplane Mode detection via rfkill
Process {
id: checkWifiBlocked
running: false
command: ["rfkill", "list", "wifi"]
stdout: StdioCollector {
onStreamFinished: {
var wifiBlocked = text && text.trim().indexOf("Soft blocked: yes") !== -1;
checkBluetoothBlocked.wifiBlockedState = wifiBlocked;
checkBluetoothBlocked.running = true;
}
}
}
Process {
id: checkBluetoothBlocked
running: false
command: ["rfkill", "list", "bluetooth"]
property bool wifiBlockedState: false // To pass state from checkWifiBlocked
stdout: StdioCollector {
onStreamFinished: {
var wifiBlocked = checkBluetoothBlocked.wifiBlockedState;
var btBlocked = text && text.trim().indexOf("Soft blocked: yes") !== -1;
var currentAirplaneMode = wifiBlocked && btBlocked;
var previousAirplaneMode = root.wifiBlocked && root.bluetoothBlocked;
if (currentAirplaneMode && !previousAirplaneMode) {
ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.enabled"), "plane");
} else if (!currentAirplaneMode && previousAirplaneMode) {
ToastService.showNotice(I18n.tr("toast.airplane-mode.title"), I18n.tr("common.disabled"), "plane-off");
} else {
if (wifiBlocked !== root.wifiBlocked) {
if (wifiBlocked) {
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("common.disabled"), "wifi-off");
} else {
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("common.enabled"), "wifi");
}
}
}
root.wifiBlocked = wifiBlocked;
root.bluetoothBlocked = btBlocked;
}
}
}
// Handle system resume to refresh state and connectivity
Connections {
target: Time
@@ -310,10 +251,23 @@ Singleton {
if (!ProgramCheckerService.nmcliAvailable) {
return;
}
Logger.i("Wi-Fi", "SetWifiEnabled", enabled);
Settings.data.network.wifiEnabled = enabled;
wifiStateEnableProcess.running = true;
}
function setAirplaneMode(enabled) {
if (enabled) {
Quickshell.execDetached(["rfkill", "block", "wifi"]);
Quickshell.execDetached(["rfkill", "block", "bluetooth"]);
Settings.data.network.airplaneModeEnabled = true;
} else {
Quickshell.execDetached(["rfkill", "unblock", "wifi"]);
Quickshell.execDetached(["rfkill", "unblock", "bluetooth"]);
Settings.data.network.airplaneModeEnabled = false;
}
}
function scan() {
if (!ProgramCheckerService.nmcliAvailable || !Settings.data.network.wifiEnabled) {
return;
@@ -974,7 +928,6 @@ Singleton {
stdout: StdioCollector {
onStreamFinished: {
Logger.i("Network", "Wi-Fi state change command executed");
// Re-check the state to ensure it's in sync
syncWifiState();
}
@@ -1316,7 +1269,7 @@ Singleton {
root.connecting = false;
root.connectingTo = "";
Logger.i("Network", "Connected to network: '" + connectProcess.ssid + "'");
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.connected", {
ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("toast.wifi.connected", {
"ssid": connectProcess.ssid
}), "wifi");
@@ -1347,7 +1300,7 @@ Singleton {
Logger.w("Network", "Connect error: " + text);
// Notify user about the failure
ToastService.showWarning(I18n.tr("wifi.panel.title"), root.lastError || I18n.tr("toast.wifi.connection-failed"));
ToastService.showWarning(I18n.tr("common.wifi"), root.lastError || I18n.tr("toast.wifi.connection-failed"));
}
}
}
@@ -1362,7 +1315,7 @@ Singleton {
stdout: StdioCollector {
onStreamFinished: {
Logger.i("Network", "Disconnected from network: '" + disconnectProcess.ssid + "'");
ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.disconnected", {
ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("toast.wifi.disconnected", {
"ssid": disconnectProcess.ssid
}), "wifi-off");