From ffe9bb851af2be20f925a94aecc2a4a9052f42f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lyas?= <67807483+Mestane@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:37:45 +0300 Subject: [PATCH 1/7] fix: notif image and vpn serialisation (#1370) * fix: serialize VPN interface name when configured via UI * fix: override last icon notification history * screw qmlls --------- Co-authored-by: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> --- config/Config.qml | 2 +- services/NotifData.qml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config/Config.qml b/config/Config.qml index cdfd7123..d0b24935 100644 --- a/config/Config.qml +++ b/config/Config.qml @@ -326,7 +326,7 @@ Singleton { const provider = { displayName: p.displayName, enabled: p.enabled, - iface: p.iface, + interface: p.iface, name: p.name }; if (p.connectCmd && p.connectCmd.length > 0) { diff --git a/services/NotifData.qml b/services/NotifData.qml index 3fde0145..d84183e5 100644 --- a/services/NotifData.qml +++ b/services/NotifData.qml @@ -77,7 +77,8 @@ QtObject { h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0); - const cache = Paths.notifimagecache + "/${hash}.png"; + Paths; // Screw you qmlls + const cache = `${Paths.notifimagecache}/${hash}.png`; CUtils.saveItem(this, Qt.resolvedUrl(cache), () => { notif.image = cache; notif.dummyImageLoader.active = false; From 9fa889536c73b65725e73eacf6d334141c0718de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lyas?= <67807483+Mestane@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:49:22 +0300 Subject: [PATCH 2/7] fix: reset lyrics when player is killed (#1359) Co-authored-by: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> --- services/LyricsService.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/LyricsService.qml b/services/LyricsService.qml index d41b93b4..a6b94f27 100644 --- a/services/LyricsService.qml +++ b/services/LyricsService.qml @@ -78,8 +78,11 @@ Singleton { function _doLoadLyrics() { const meta = getMetadata(); - if (!meta) + if (!meta) { + lyricsModel.clear(); + root.currentIndex = -1; return; + } loading = true; lyricsModel.clear(); From dfc5bcf8b9511fde1fee7de15cdfa79c5f5aeeb6 Mon Sep 17 00:00:00 2001 From: Robin Seger Date: Thu, 2 Apr 2026 17:43:22 +0200 Subject: [PATCH 3/7] 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 4/7] 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"; + } } From 673b09626167612dd3afa1d1568640a9a72f25f5 Mon Sep 17 00:00:00 2001 From: Robin Seger Date: Mon, 6 Apr 2026 11:18:30 +0200 Subject: [PATCH 5/7] fix: iface -> interface in VPN service (#1376) --- services/VPN.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/VPN.qml b/services/VPN.qml index 31b68319..bc0addce 100644 --- a/services/VPN.qml +++ b/services/VPN.qml @@ -25,7 +25,7 @@ Singleton { } readonly property bool isCustomProvider: typeof providerInput === "object" readonly property string providerName: isCustomProvider ? (providerInput.name || "custom") : String(providerInput) - readonly property string interfaceName: isCustomProvider ? (providerInput.iface || "") : "" + readonly property string interfaceName: isCustomProvider ? (providerInput.interface || "") : "" readonly property var currentConfig: { const name = providerName; const iface = interfaceName; @@ -36,7 +36,7 @@ Singleton { return { connectCmd: custom.connectCmd || defaults.connectCmd, disconnectCmd: custom.disconnectCmd || defaults.disconnectCmd, - interface: custom.iface || defaults.interface, + interface: custom.interface || defaults.interface, displayName: custom.displayName || defaults.displayName }; } From ffa7d88caee13abea21d9243bc06588d4b2d693c Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:12:14 +1000 Subject: [PATCH 6/7] ci: use clang format dry-run -Werror --- .github/workflows/check-format.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index b26cb949..ea5fe9ef 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -34,6 +34,5 @@ jobs: - name: Check C++ format shell: fish {0} run: | - for file in (string match -v 'build/*' **.cpp **.hpp) - clang-format $file | diff -u $file - || exit 1 - end + find plugin extras -name '*.cpp' -o -name '*.hpp' \ + | xargs clang-format --dry-run --Werror From 612f828b9f84ca0dda76ce009c3ff64f75c5733e Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:04:48 +1000 Subject: [PATCH 7/7] fix: don't loader variants Change model instead This may or may not help with the background not loading --- modules/background/Background.qml | 267 +++++++++++++++--------------- 1 file changed, 131 insertions(+), 136 deletions(-) diff --git a/modules/background/Background.qml b/modules/background/Background.qml index 95109e12..0f44b1a0 100644 --- a/modules/background/Background.qml +++ b/modules/background/Background.qml @@ -7,159 +7,154 @@ import qs.components.containers import qs.services import qs.config -Loader { - asynchronous: true - active: Config.background.enabled +Variants { + model: Config.background.enabled ? Screens.screens : [] - sourceComponent: Variants { - model: Screens.screens + StyledWindow { + id: win - StyledWindow { - id: win + required property ShellScreen modelData - required property ShellScreen modelData + screen: modelData + name: "background" + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.layer: Config.background.wallpaperEnabled ? WlrLayer.Background : WlrLayer.Bottom + color: Config.background.wallpaperEnabled ? "black" : "transparent" + surfaceFormat.opaque: false - screen: modelData - name: "background" - WlrLayershell.exclusionMode: ExclusionMode.Ignore - WlrLayershell.layer: Config.background.wallpaperEnabled ? WlrLayer.Background : WlrLayer.Bottom - color: Config.background.wallpaperEnabled ? "black" : "transparent" - surfaceFormat.opaque: false + anchors.top: true + anchors.bottom: true + anchors.left: true + anchors.right: true - anchors.top: true - anchors.bottom: true - anchors.left: true - anchors.right: true + Item { + id: behindClock - Item { - id: behindClock + anchors.fill: parent + + Loader { + id: wallpaper + + asynchronous: true anchors.fill: parent + active: Config.background.wallpaperEnabled - Loader { - id: wallpaper + sourceComponent: Wallpaper {} + } - asynchronous: true + Visualiser { + anchors.fill: parent + screen: win.modelData + wallpaper: wallpaper + } + } - anchors.fill: parent - active: Config.background.wallpaperEnabled + Loader { + id: clockLoader - sourceComponent: Wallpaper {} + asynchronous: true + active: Config.background.desktopClock.enabled + + anchors.margins: Appearance.padding.large * 2 + anchors.leftMargin: Appearance.padding.large * 2 + Config.bar.sizes.innerWidth + Math.max(Appearance.padding.smaller, Config.border.thickness) + + state: Config.background.desktopClock.position + states: [ + State { + name: "top-left" + + AnchorChanges { + target: clockLoader + anchors.top: parent.top + anchors.left: parent.left + } + }, + State { + name: "top-center" + + AnchorChanges { + target: clockLoader + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + } + }, + State { + name: "top-right" + + AnchorChanges { + target: clockLoader + anchors.top: parent.top + anchors.right: parent.right + } + }, + State { + name: "middle-left" + + AnchorChanges { + target: clockLoader + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + } + }, + State { + name: "middle-center" + + AnchorChanges { + target: clockLoader + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + }, + State { + name: "middle-right" + + AnchorChanges { + target: clockLoader + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + } + }, + State { + name: "bottom-left" + + AnchorChanges { + target: clockLoader + anchors.bottom: parent.bottom + anchors.left: parent.left + } + }, + State { + name: "bottom-center" + + AnchorChanges { + target: clockLoader + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + } + }, + State { + name: "bottom-right" + + AnchorChanges { + target: clockLoader + anchors.bottom: parent.bottom + anchors.right: parent.right + } } + ] - Visualiser { - anchors.fill: parent - screen: win.modelData - wallpaper: wallpaper + transitions: Transition { + AnchorAnimation { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial } } - Loader { - id: clockLoader - - asynchronous: true - active: Config.background.desktopClock.enabled - - anchors.margins: Appearance.padding.large * 2 - anchors.leftMargin: Appearance.padding.large * 2 + Config.bar.sizes.innerWidth + Math.max(Appearance.padding.smaller, Config.border.thickness) - - state: Config.background.desktopClock.position - states: [ - State { - name: "top-left" - - AnchorChanges { - target: clockLoader - anchors.top: parent.top - anchors.left: parent.left - } - }, - State { - name: "top-center" - - AnchorChanges { - target: clockLoader - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - } - }, - State { - name: "top-right" - - AnchorChanges { - target: clockLoader - anchors.top: parent.top - anchors.right: parent.right - } - }, - State { - name: "middle-left" - - AnchorChanges { - target: clockLoader - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - } - }, - State { - name: "middle-center" - - AnchorChanges { - target: clockLoader - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - } - }, - State { - name: "middle-right" - - AnchorChanges { - target: clockLoader - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - } - }, - State { - name: "bottom-left" - - AnchorChanges { - target: clockLoader - anchors.bottom: parent.bottom - anchors.left: parent.left - } - }, - State { - name: "bottom-center" - - AnchorChanges { - target: clockLoader - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - } - }, - State { - name: "bottom-right" - - AnchorChanges { - target: clockLoader - anchors.bottom: parent.bottom - anchors.right: parent.right - } - } - ] - - transitions: Transition { - AnchorAnimation { - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial - } - } - - sourceComponent: DesktopClock { - wallpaper: behindClock - absX: clockLoader.x - absY: clockLoader.y - } + sourceComponent: DesktopClock { + wallpaper: behindClock + absX: clockLoader.x + absY: clockLoader.y } } }