From dfc5bcf8b9511fde1fee7de15cdfa79c5f5aeeb6 Mon Sep 17 00:00:00 2001 From: Robin Seger Date: Thu, 2 Apr 2026 17:43:22 +0200 Subject: [PATCH 1/2] fix: preserve VPN interface and command fields (#1373) --- config/Config.qml | 2 +- modules/controlcenter/network/VpnDetails.qml | 35 +++++++++---- modules/controlcenter/network/VpnList.qml | 27 +++++++++- modules/controlcenter/network/VpnSettings.qml | 50 +++++++++++++++---- 4 files changed, 94 insertions(+), 20 deletions(-) diff --git a/config/Config.qml b/config/Config.qml index d0b24935..997eaf1e 100644 --- a/config/Config.qml +++ b/config/Config.qml @@ -326,7 +326,7 @@ Singleton { const provider = { displayName: p.displayName, enabled: p.enabled, - interface: p.iface, + interface: p.interface, name: p.name }; if (p.connectCmd && p.connectCmd.length > 0) { diff --git a/modules/controlcenter/network/VpnDetails.qml b/modules/controlcenter/network/VpnDetails.qml index 4d749d8b..b02f4c49 100644 --- a/modules/controlcenter/network/VpnDetails.qml +++ b/modules/controlcenter/network/VpnDetails.qml @@ -72,6 +72,13 @@ DeviceDetails { newProvider.enabled = (i === index) ? false : (p.enabled !== false); } + if (p.connectCmd && p.connectCmd.length > 0) { + newProvider.connectCmd = p.connectCmd; + } + if (p.disconnectCmd && p.disconnectCmd.length > 0) { + newProvider.disconnectCmd = p.disconnectCmd; + } + providers.push(newProvider); } else { providers.push(p); @@ -504,21 +511,31 @@ DeviceDetails { const newProvider = { displayName: editVpnDialog.displayName || editVpnDialog.interfaceName, enabled: wasEnabled, - iface: editVpnDialog.interfaceName, - name: editVpnDialog.providerName, - connectCmd: hasCommands ? editVpnDialog.connectCmd.split(" ").filter(s => s.length > 0) : undefined, - disconnectCmd: hasCommands ? editVpnDialog.disconnectCmd.split(" ").filter(s => s.length > 0) : undefined + interface: editVpnDialog.interfaceName, + name: editVpnDialog.providerName }; - // Remove undefined properties - if (!hasCommands) { - delete newProvider.connectCmd; - delete newProvider.disconnectCmd; + if (hasCommands) { + newProvider.connectCmd = editVpnDialog.connectCmd.split(" ").filter(s => s.length > 0); + newProvider.disconnectCmd = editVpnDialog.disconnectCmd.split(" ").filter(s => s.length > 0); } providers.push(newProvider); } else { - providers.push(Config.utilities.vpn.provider[i]); + const p = Config.utilities.vpn.provider[i]; + const reconstructed = { + displayName: p.displayName, + enabled: p.enabled, + interface: p.interface, + name: p.name + }; + if (p.connectCmd && p.connectCmd.length > 0) { + reconstructed.connectCmd = p.connectCmd; + } + if (p.disconnectCmd && p.disconnectCmd.length > 0) { + reconstructed.disconnectCmd = p.disconnectCmd; + } + providers.push(reconstructed); } } diff --git a/modules/controlcenter/network/VpnList.qml b/modules/controlcenter/network/VpnList.qml index 6dabc2cf..8522f972 100644 --- a/modules/controlcenter/network/VpnList.qml +++ b/modules/controlcenter/network/VpnList.qml @@ -36,6 +36,12 @@ ColumnLayout { interface: p.interface, enabled: (i === targetIndex) }; + if (p.connectCmd && p.connectCmd.length > 0) { + newProvider.connectCmd = p.connectCmd; + } + if (p.disconnectCmd && p.disconnectCmd.length > 0) { + newProvider.disconnectCmd = p.disconnectCmd; + } providers.push(newProvider); } else { providers.push(p); @@ -226,6 +232,12 @@ ColumnLayout { interface: p.interface, enabled: (i === clickedIndex) }; + if (p.connectCmd && p.connectCmd.length > 0) { + newProvider.connectCmd = p.connectCmd; + } + if (p.disconnectCmd && p.disconnectCmd.length > 0) { + newProvider.disconnectCmd = p.disconnectCmd; + } providers.push(newProvider); } else { providers.push(p); @@ -265,7 +277,20 @@ ColumnLayout { const providers = []; for (let i = 0; i < Config.utilities.vpn.provider.length; i++) { if (i !== modelData.index) { - providers.push(Config.utilities.vpn.provider[i]); + const p = Config.utilities.vpn.provider[i]; + const reconstructed = { + name: p.name, + displayName: p.displayName, + interface: p.interface, + enabled: p.enabled + }; + if (p.connectCmd && p.connectCmd.length > 0) { + reconstructed.connectCmd = p.connectCmd; + } + if (p.disconnectCmd && p.disconnectCmd.length > 0) { + reconstructed.disconnectCmd = p.disconnectCmd; + } + providers.push(reconstructed); } } Config.utilities.vpn.provider = providers; diff --git a/modules/controlcenter/network/VpnSettings.qml b/modules/controlcenter/network/VpnSettings.qml index ae689050..80732fde 100644 --- a/modules/controlcenter/network/VpnSettings.qml +++ b/modules/controlcenter/network/VpnSettings.qml @@ -119,30 +119,62 @@ ColumnLayout { icon: modelData.isActive ? "arrow_downward" : "arrow_upward" visible: !modelData.isActive || Config.utilities.vpn.provider.length > 1 onClicked: { - if (modelData.isActive && index < Config.utilities.vpn.provider.length - 1) { + const providers = []; + for (let i = 0; i < Config.utilities.vpn.provider.length; i++) { + const p = Config.utilities.vpn.provider[i]; + const reconstructed = { + name: p.name, + displayName: p.displayName, + interface: p.interface, + enabled: p.enabled + }; + if (p.connectCmd && p.connectCmd.length > 0) { + reconstructed.connectCmd = p.connectCmd; + } + if (p.disconnectCmd && p.disconnectCmd.length > 0) { + reconstructed.disconnectCmd = p.disconnectCmd; + } + providers.push(reconstructed); + } + + if (modelData.isActive && index < providers.length - 1) { // Move down - const providers = [...Config.utilities.vpn.provider]; const temp = providers[index]; providers[index] = providers[index + 1]; providers[index + 1] = temp; - Config.utilities.vpn.provider = providers; - Config.save(); } else if (!modelData.isActive) { // Make active (move to top) - const providers = [...Config.utilities.vpn.provider]; const provider = providers.splice(index, 1)[0]; providers.unshift(provider); - Config.utilities.vpn.provider = providers; - Config.save(); } + + Config.utilities.vpn.provider = providers; + Config.save(); } } IconButton { icon: "delete" onClicked: { - const providers = [...Config.utilities.vpn.provider]; - providers.splice(index, 1); + const providers = []; + for (let i = 0; i < Config.utilities.vpn.provider.length; i++) { + if (i !== index) { + const p = Config.utilities.vpn.provider[i]; + const reconstructed = { + name: p.name, + displayName: p.displayName, + interface: p.interface, + enabled: p.enabled + }; + if (p.connectCmd && p.connectCmd.length > 0) { + reconstructed.connectCmd = p.connectCmd; + } + if (p.disconnectCmd && p.disconnectCmd.length > 0) { + reconstructed.disconnectCmd = p.disconnectCmd; + } + providers.push(reconstructed); + } + } Config.utilities.vpn.provider = providers; Config.save(); } From f924dbfbbb8f1a0fe8c36ccca254063f1bbe0e1b Mon Sep 17 00:00:00 2001 From: Evertiro Date: Thu, 2 Apr 2026 23:32:00 -0500 Subject: [PATCH 2/2] feat: show battery status in Bluetooth popout (#1130) * Display bluetooth battery in popout when possible Signed-off-by: Dan Griffiths * Actually return the alert icon if battery status can't be determined Signed-off-by: Dan Griffiths * Fix lint error Signed-off-by: Dan Griffiths * move icon to right + static popout width --------- Signed-off-by: Dan Griffiths Co-authored-by: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> --- modules/bar/popouts/Bluetooth.qml | 9 +++++++++ utils/Icons.qml | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/modules/bar/popouts/Bluetooth.qml b/modules/bar/popouts/Bluetooth.qml index 2e87270a..027b3d41 100644 --- a/modules/bar/popouts/Bluetooth.qml +++ b/modules/bar/popouts/Bluetooth.qml @@ -15,6 +15,7 @@ ColumnLayout { required property PopoutState popouts + width: 300 spacing: Appearance.spacing.small StyledText { @@ -99,6 +100,13 @@ ColumnLayout { Layout.rightMargin: Appearance.spacing.small / 2 Layout.fillWidth: true text: device.modelData.name + elide: Text.ElideRight + } + + MaterialIcon { + visible: device.modelData.state === BluetoothDeviceState.Connected // qmllint disable unresolved-type + text: Icons.getBatteryIcon(device.modelData.batteryAvailable ? device.modelData.battery * 100 : -1) + color: device.modelData.battery < 0.2 ? Colours.palette.m3error : Colours.palette.m3onSurfaceVariant } StyledRect { @@ -141,6 +149,7 @@ ColumnLayout { } Loader { + visible: status === Loader.Ready asynchronous: true active: device.modelData.bonded sourceComponent: Item { diff --git a/utils/Icons.qml b/utils/Icons.qml index 8a62f62d..aded0f4b 100644 --- a/utils/Icons.qml +++ b/utils/Icons.qml @@ -240,4 +240,24 @@ Singleton { } return icon; } + + function getBatteryIcon(charge: int): string { + if (charge > 0 && charge < 5) + return "battery_0_bar"; + if (charge >= 5 && charge < 20) + return "battery_1_bar"; + if (charge >= 20 && charge < 35) + return "battery_2_bar"; + if (charge >= 35 && charge < 50) + return "battery_3_bar"; + if (charge >= 50 && charge < 65) + return "battery_4_bar"; + if (charge >= 65 && charge < 80) + return "battery_5_bar"; + if (charge >= 80 && charge < 95) + return "battery_6_bar"; + if (charge >= 95) + return "battery_full"; + return "battery_alert"; + } }