From 14b0ef320ae4654e1d1f45a067489577b612e346 Mon Sep 17 00:00:00 2001 From: wxlyyy <1556588440@qq.com> Date: Thu, 13 Nov 2025 17:36:28 +0800 Subject: [PATCH 01/25] SessionMenu: add hibernate option --- Assets/Translations/de.json | 3 +- Assets/Translations/en.json | 1 + Assets/Translations/es.json | 1 + Assets/Translations/fr.json | 1 + Assets/Translations/nl.json | 1 + Assets/Translations/pt.json | 1 + Assets/Translations/ru.json | 1 + Assets/Translations/tr.json | 1 + Assets/Translations/uk-UA.json | 1 + Assets/Translations/zh-CN.json | 1 + Assets/settings-default.json | 6 ++- Commons/IconsTabler.qml | 1 + Commons/Settings.qml | 3 ++ Modules/LockScreen/LockScreen.qml | 48 +++++++++++++++++++ Modules/Panels/SessionMenu/SessionMenu.qml | 8 ++++ .../Panels/Settings/Tabs/SessionMenuTab.qml | 5 ++ Services/Compositor/CompositorService.qml | 5 ++ 17 files changed, 86 insertions(+), 2 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index d7aea21c..5be307dd 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -652,7 +652,8 @@ "logout": "Abmelden", "reboot": "Neu starten", "shutdown": "Herunterfahren", - "suspend": "Ruhezustand", + "suspend": "Energiesparmodus", + "hibernate": "Ruhezustand", "title": "Sitzungsmenü" }, "settings": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index bb26852e..3155b7bb 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -653,6 +653,7 @@ "reboot": "Reboot", "shutdown": "Shutdown", "suspend": "Suspend", + "hibernate": "Hibernate", "title": "Session Menu" }, "settings": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index b556ddeb..9499bab1 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -653,6 +653,7 @@ "reboot": "Reiniciar", "shutdown": "Apagar", "suspend": "Suspender", + "hibernate": "Hibernación", "title": "Menú de sesión" }, "settings": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index ebeba4ca..c8b5a6c1 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -653,6 +653,7 @@ "reboot": "Redémarrer", "shutdown": "Éteindre", "suspend": "Mettre en veille", + "hibernate": "Mise en veille prolongée", "title": "Menu de session" }, "settings": { diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index 2c87c2f6..42198409 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -653,6 +653,7 @@ "reboot": "Opnieuw opstarten", "shutdown": "Uitschakelen", "suspend": "Onderbreken", + "hibernate": "Slaperstand", "title": "Sessie-menu" }, "settings": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index b24bc15e..7b3218d2 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -653,6 +653,7 @@ "reboot": "Reiniciar", "shutdown": "Desligar", "suspend": "Suspender", + "hibernate": "Hibernação", "title": "Menu da Sessão" }, "settings": { diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index 3d0b9830..db9da813 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -653,6 +653,7 @@ "reboot": "Перезагрузить", "shutdown": "Выключить", "suspend": "Приостановить", + "hibernate": "Гибернация", "title": "Меню сеанса" }, "settings": { diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 560d859a..61ba6c78 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -653,6 +653,7 @@ "reboot": "Yeniden başlat", "shutdown": "Bilgisayarı kapat", "suspend": "Askıya al", + "hibernate": "Hazırda Bekletme", "title": "Oturum Menüsü" }, "settings": { diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index 9223e582..fd8d3c62 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -653,6 +653,7 @@ "reboot": "Перезавантажити", "shutdown": "Вимкнути", "suspend": "Призупинити", + "hibernate": "Гібернація", "title": "Меню сеансу" }, "settings": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 197d2533..473359be 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -653,6 +653,7 @@ "reboot": "重启", "shutdown": "关机", "suspend": "挂起", + "hibernate": "休眠", "title": "会话菜单" }, "settings": { diff --git a/Assets/settings-default.json b/Assets/settings-default.json index f648641b..b98def90 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -219,6 +219,10 @@ "action": "suspend", "enabled": true }, + { + "action": "hibernate", + "enabled": true + }, { "action": "reboot", "enabled": true @@ -317,4 +321,4 @@ "battery": { "chargingMode": 0 } -} \ No newline at end of file +} diff --git a/Commons/IconsTabler.qml b/Commons/IconsTabler.qml index f700360b..0b85a2bb 100644 --- a/Commons/IconsTabler.qml +++ b/Commons/IconsTabler.qml @@ -49,6 +49,7 @@ Singleton { "logout": "logout", "reboot": "refresh", "suspend": "player-pause", + "hibernate": "zzz", "nightlight-on": "moon", "nightlight-off": "moon-off", "nightlight-forced": "moon-stars", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index d1a8ae17..1e31fe25 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -352,6 +352,9 @@ Singleton { }, { "action": "suspend", "enabled": true + }, { + "action": "hibernate", + "enabled": true }, { "action": "reboot", "enabled": true diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 4c87d1ca..4bf8b4d5 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -1188,6 +1188,54 @@ Loader { } } + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48 + radius: Settings.data.general.compactLockScreen ? 18 : 24 + color: hibernateButtonArea.containsMouse ? Color.mHover : "transparent" + border.color: Color.mOutline + border.width: 1 + + RowLayout { + anchors.centerIn: parent + spacing: 6 + + NIcon { + icon: "hibernate" + pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL + color: hibernateButtonArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant + } + + NText { + text: I18n.tr("session-menu.hibernate") + pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM + color: hibernateButtonArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant + font.weight: Font.Medium + } + } + + MouseArea { + id: hibernateButtonArea + anchors.fill: parent + hoverEnabled: true + onClicked: CompositorService.hibernate() + } + + Behavior on color { + ColorAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } + + Behavior on border.color { + ColorAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } + } + Rectangle { Layout.fillWidth: true Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48 diff --git a/Modules/Panels/SessionMenu/SessionMenu.qml b/Modules/Panels/SessionMenu/SessionMenu.qml index 07b071ed..4c99a47d 100644 --- a/Modules/Panels/SessionMenu/SessionMenu.qml +++ b/Modules/Panels/SessionMenu/SessionMenu.qml @@ -65,6 +65,11 @@ SmartPanel { "title": I18n.tr("session-menu.suspend"), "isShutdown": false }, + "hibernate": { + "icon": "hibernate", + "title": I18n.tr("session-menu.hibernate"), + "isShutdown": false + }, "reboot": { "icon": "reboot", "title": I18n.tr("session-menu.reboot"), @@ -159,6 +164,9 @@ SmartPanel { CompositorService.suspend() } break + case "hibernate": + CompositorService.hibernate() + break case "reboot": CompositorService.reboot() break diff --git a/Modules/Panels/Settings/Tabs/SessionMenuTab.qml b/Modules/Panels/Settings/Tabs/SessionMenuTab.qml index 184f2cba..0ce810e6 100644 --- a/Modules/Panels/Settings/Tabs/SessionMenuTab.qml +++ b/Modules/Panels/Settings/Tabs/SessionMenuTab.qml @@ -22,6 +22,11 @@ ColumnLayout { "text": I18n.tr("session-menu.suspend"), "enabled": true, "required": false + }, { + "id": "hibernate", + "text": I18n.tr("session-menu.hibernate"), + "enabled": true, + "required": false }, { "id": "reboot", "text": I18n.tr("session-menu.reboot"), diff --git a/Services/Compositor/CompositorService.qml b/Services/Compositor/CompositorService.qml index 0ce10ffa..069e468d 100644 --- a/Services/Compositor/CompositorService.qml +++ b/Services/Compositor/CompositorService.qml @@ -342,6 +342,11 @@ Singleton { Quickshell.execDetached(["sh", "-c", "systemctl suspend || loginctl suspend"]) } + function hibernate() { + Logger.i("Compositor", "Hibernate requested") + Quickshell.execDetached(["sh", "-c", "systemctl hibernate || loginctl hibernate"]) + } + function lockAndSuspend() { Logger.i("Compositor", "Lock and suspend requested") try { From 454733d80eaa0b426ae510a0a95ece003e75f588 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 08:16:17 -0500 Subject: [PATCH 02/25] Typography: Semi-Bold NHeader and NLabel --- Widgets/NHeader.qml | 2 +- Widgets/NLabel.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Widgets/NHeader.qml b/Widgets/NHeader.qml index 08d58e01..aa40765d 100644 --- a/Widgets/NHeader.qml +++ b/Widgets/NHeader.qml @@ -15,7 +15,7 @@ ColumnLayout { NText { text: root.label pointSize: Style.fontSizeXL - font.weight: Style.fontWeightBold + font.weight: Style.fontWeightSemiBold color: Color.mSecondary visible: root.label !== "" } diff --git a/Widgets/NLabel.qml b/Widgets/NLabel.qml index f1a42440..4ae24762 100644 --- a/Widgets/NLabel.qml +++ b/Widgets/NLabel.qml @@ -16,7 +16,7 @@ ColumnLayout { NText { text: label pointSize: Style.fontSizeL - font.weight: Style.fontWeightBold + font.weight: Style.fontWeightSemiBold color: labelColor visible: label !== "" Layout.fillWidth: true From 70216537647c109b1124ecb1e237022f6cdbd4df Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 08:24:45 -0500 Subject: [PATCH 03/25] OSD: Autoformatting - removing all those semicolons --- Modules/OSD/OSD.qml | 224 ++++++++++++------------ Modules/Panels/Settings/Tabs/OsdTab.qml | 61 +++---- 2 files changed, 138 insertions(+), 147 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 1409d545..d444bdca 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -49,83 +49,83 @@ Variants { function getIcon() { if (currentOSDType === "volume") { if (AudioService.muted) { - return "volume-mute"; + return "volume-mute" } - return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high"; + return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high" } else if (currentOSDType === "inputVolume") { if (AudioService.inputMuted) { - return "microphone-off"; + return "microphone-off" } - return "microphone"; + return "microphone" } else if (currentOSDType === "brightness") { - return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high"; + return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high" } - return ""; + return "" } // Get current value (0-1 range) function getCurrentValue() { if (currentOSDType === "volume") { - return isMuted ? 0 : currentVolume; + return isMuted ? 0 : currentVolume } else if (currentOSDType === "inputVolume") { - return isInputMuted ? 0 : currentInputVolume; + return isInputMuted ? 0 : currentInputVolume } else if (currentOSDType === "brightness") { - return currentBrightness; + return currentBrightness } - return 0; + return 0 } // Get maximum value for current OSD type function getMaxValue() { if (currentOSDType === "volume" || currentOSDType === "inputVolume") { - return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 } else if (currentOSDType === "brightness") { - return 1.0; + return 1.0 } - return 1.0; + return 1.0 } // Get display percentage function getDisplayPercentage() { if (currentOSDType === "volume") { if (isMuted) - return "0%"; - const max = getMaxValue(); - const pct = Math.round(Math.min(max, currentVolume) * 100); - return pct + "%"; + return "0%" + const max = getMaxValue() + const pct = Math.round(Math.min(max, currentVolume) * 100) + return pct + "%" } else if (currentOSDType === "inputVolume") { if (isInputMuted) - return "0%"; - const max = getMaxValue(); - const pct = Math.round(Math.min(max, currentInputVolume) * 100); - return pct + "%"; + return "0%" + const max = getMaxValue() + const pct = Math.round(Math.min(max, currentInputVolume) * 100) + return pct + "%" } else if (currentOSDType === "brightness") { - const pct = Math.round(Math.min(1.0, currentBrightness) * 100); - return pct + "%"; + const pct = Math.round(Math.min(1.0, currentBrightness) * 100) + return pct + "%" } - return ""; + return "" } // Get progress bar color function getProgressColor() { if (currentOSDType === "volume") { if (isMuted) - return Color.mError; - return Color.mPrimary; + return Color.mError + return Color.mPrimary } else if (currentOSDType === "inputVolume") { if (isInputMuted) - return Color.mError; - return Color.mPrimary; + return Color.mError + return Color.mPrimary } - return Color.mPrimary; + return Color.mPrimary } // Get icon color function getIconColor() { if ((currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted)) { - return Color.mError; + return Color.mError } - return Color.mOnSurface; + return Color.mOnSurface } sourceComponent: PanelWindow { @@ -146,8 +146,8 @@ Variants { // Ensure an even width to keep the vertical bar perfectly centered readonly property int barThickness: { - const base = Math.max(8, Math.round(8 * Style.uiScaleRatio)); - return (base % 2 === 0) ? base : base + 1; + const base = Math.max(8, Math.round(8 * Style.uiScaleRatio)) + return (base % 2 === 0) ? base : base + 1 } // Anchor selection based on location (window edges) @@ -159,46 +159,46 @@ Variants { // Margins depending on bar position and chosen location margins.top: { if (!(anchors.top)) - return 0; - var base = Style.marginM; + return 0 + var base = Style.marginM if (Settings.data.bar.position === "top") { - var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0; - return Style.barHeight + base + floatExtraV; + var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0 + return Style.barHeight + base + floatExtraV } - return base; + return base } margins.bottom: { if (!(anchors.bottom)) - return 0; - var base = Style.marginM; + return 0 + var base = Style.marginM if (Settings.data.bar.position === "bottom") { - var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0; - return Style.barHeight + base + floatExtraV; + var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0 + return Style.barHeight + base + floatExtraV } - return base; + return base } margins.left: { if (!(anchors.left)) - return 0; - var base = Style.marginM; + return 0 + var base = Style.marginM if (Settings.data.bar.position === "left") { - var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0; - return Style.barHeight + base + floatExtraH; + var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 + return Style.barHeight + base + floatExtraH } - return base; + return base } margins.right: { if (!(anchors.right)) - return 0; - var base = Style.marginM; + return 0 + var base = Style.marginM if (Settings.data.bar.position === "right") { - var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0; - return Style.barHeight + base + floatExtraH; + var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 + return Style.barHeight + base + floatExtraH } - return base; + return base } implicitWidth: verticalMode ? vWidth : hWidth @@ -249,10 +249,10 @@ Variants { id: visibilityTimer interval: Style.animationNormal + 50 // Add small buffer onTriggered: { - osdItem.visible = false; - root.currentOSDType = ""; + osdItem.visible = false + root.currentOSDType = "" // Deactivate the loader when done - root.active = false; + root.active = false } } @@ -265,8 +265,8 @@ Variants { color: Qt.alpha(Color.mSurface, Settings.data.osd.backgroundOpacity || 1.0) border.color: Qt.alpha(Color.mOutline, Settings.data.osd.backgroundOpacity || 1.0) border.width: { - const bw = Math.max(2, Style.borderM); - return (bw % 2 === 0) ? bw : bw + 1; + const bw = Math.max(2, Style.borderM) + return (bw % 2 === 0) ? bw : bw + 1 } } @@ -365,9 +365,9 @@ Variants { ColumnLayout { // Ensure inner padding respects the rounded corners; avoid clipping the icon/text property int vMargin: { - const styleMargin = Style.marginL; - const cornerGuard = Math.round(osdItem.radius); - return Math.max(styleMargin, cornerGuard); + const styleMargin = Style.marginL + const cornerGuard = Math.round(osdItem.radius) + return Math.max(styleMargin, cornerGuard) } property int vMarginTop: Math.round(Math.max(osdItem.radius, Style.marginS)) property int balanceDelta: Style.marginS @@ -449,46 +449,46 @@ Variants { function show() { // Cancel any pending hide operations - hideTimer.stop(); - visibilityTimer.stop(); + hideTimer.stop() + visibilityTimer.stop() // Make visible and animate in - osdItem.visible = true; + osdItem.visible = true // Use Qt.callLater to ensure the visible change is processed before animation Qt.callLater(() => { - osdItem.opacity = 1; - osdItem.scale = 1.0; - }); + osdItem.opacity = 1 + osdItem.scale = 1.0 + }) // Start the auto-hide timer - hideTimer.start(); + hideTimer.start() } function hide() { - hideTimer.stop(); - visibilityTimer.stop(); + hideTimer.stop() + visibilityTimer.stop() // Start fade out animation - osdItem.opacity = 0; - osdItem.scale = 0.85; // Less dramatic scale change for smoother effect + osdItem.opacity = 0 + osdItem.scale = 0.85 // Less dramatic scale change for smoother effect // Delay hiding the element until after animation completes - visibilityTimer.start(); + visibilityTimer.start() } function hideImmediately() { - hideTimer.stop(); - visibilityTimer.stop(); - osdItem.opacity = 0; - osdItem.scale = 0.85; - osdItem.visible = false; - root.currentOSDType = ""; - root.active = false; + hideTimer.stop() + visibilityTimer.stop() + osdItem.opacity = 0 + osdItem.scale = 0.85 + osdItem.visible = false + root.currentOSDType = "" + root.active = false } } function showOSD() { - osdItem.show(); + osdItem.show() } } @@ -498,34 +498,34 @@ Variants { function onVolumeChanged() { if (volumeInitialized) { - showOSD("volume"); + showOSD("volume") } } function onMutedChanged() { if (muteInitialized) { - showOSD("volume"); + showOSD("volume") } } function onInputVolumeChanged() { if (!inputAudioInitialized) { - return; + return } if (!AudioService.hasInput) { - return; + return } - showOSD("inputVolume"); + showOSD("inputVolume") } function onInputMutedChanged() { if (!inputAudioInitialized) { - return; + return } if (!AudioService.hasInput) { - return; + return } - showOSD("inputVolume"); + showOSD("inputVolume") } } @@ -535,11 +535,11 @@ Variants { interval: 500 running: true onTriggered: { - volumeInitialized = true; - muteInitialized = true; - inputAudioInitialized = true; + volumeInitialized = true + muteInitialized = true + inputAudioInitialized = true // Brightness initializes on first change to avoid showing OSD on startup - connectBrightnessMonitors(); + connectBrightnessMonitors() } } @@ -548,65 +548,65 @@ Variants { target: BrightnessService function onMonitorsChanged() { - connectBrightnessMonitors(); + connectBrightnessMonitors() } } function disconnectBrightnessMonitors() { for (var i = 0; i < BrightnessService.monitors.length; i++) { - let monitor = BrightnessService.monitors[i]; - monitor.brightnessUpdated.disconnect(onBrightnessChanged); + let monitor = BrightnessService.monitors[i] + monitor.brightnessUpdated.disconnect(onBrightnessChanged) } } function connectBrightnessMonitors() { for (var i = 0; i < BrightnessService.monitors.length; i++) { - let monitor = BrightnessService.monitors[i]; + let monitor = BrightnessService.monitors[i] // Disconnect first to avoid duplicate connections - monitor.brightnessUpdated.disconnect(onBrightnessChanged); - monitor.brightnessUpdated.connect(onBrightnessChanged); + monitor.brightnessUpdated.disconnect(onBrightnessChanged) + monitor.brightnessUpdated.connect(onBrightnessChanged) } } function onBrightnessChanged(newBrightness) { - root.lastUpdatedBrightness = newBrightness; + root.lastUpdatedBrightness = newBrightness if (!brightnessInitialized) { - brightnessInitialized = true; - return; + brightnessInitialized = true + return } - showOSD("brightness"); + showOSD("brightness") } function showOSD(type) { // Update the current OSD type - currentOSDType = type; + currentOSDType = type // Activate the loader if not already active if (!root.active) { - root.active = true; + root.active = true } // Show the OSD (may need to wait for loader to create the item) if (root.item) { - root.item.showOSD(); + root.item.showOSD() } else { // If item not ready yet, wait for it Qt.callLater(() => { - if (root.item) { - root.item.showOSD(); - } - }); + if (root.item) { + root.item.showOSD() + } + }) } } function hideOSD() { if (root.item && root.item.osdItem) { - root.item.osdItem.hideImmediately(); + root.item.osdItem.hideImmediately() } else if (root.active) { // If loader is active but item isn't ready, just deactivate - root.active = false; + root.active = false } } } diff --git a/Modules/Panels/Settings/Tabs/OsdTab.qml b/Modules/Panels/Settings/Tabs/OsdTab.qml index 10f54551..d3695411 100644 --- a/Modules/Panels/Settings/Tabs/OsdTab.qml +++ b/Modules/Panels/Settings/Tabs/OsdTab.qml @@ -11,15 +11,15 @@ ColumnLayout { // Helper functions to update arrays immutably function addMonitor(list, name) { - const arr = (list || []).slice(); + const arr = (list || []).slice() if (!arr.includes(name)) - arr.push(name); - return arr; + arr.push(name) + return arr } function removeMonitor(list, name) { return (list || []).filter(function (n) { - return n !== name; - }); + return n !== name + }) } // Display @@ -30,40 +30,31 @@ ColumnLayout { NComboBox { label: I18n.tr("settings.osd.location.label") description: I18n.tr("settings.osd.location.description") - model: [ - { + model: [{ "key": "top", "name": I18n.tr("options.osd.position.top_center") - }, - { + }, { "key": "top_left", "name": I18n.tr("options.osd.position.top_left") - }, - { + }, { "key": "top_right", "name": I18n.tr("options.osd.position.top_right") - }, - { + }, { "key": "bottom", "name": I18n.tr("options.osd.position.bottom_center") - }, - { + }, { "key": "bottom_left", "name": I18n.tr("options.osd.position.bottom_left") - }, - { + }, { "key": "bottom_right", "name": I18n.tr("options.osd.position.bottom_right") - }, - { + }, { "key": "left", "name": I18n.tr("options.osd.position.center_left") - }, - { + }, { "key": "right", "name": I18n.tr("options.osd.position.center_right") - } - ] + }] currentKey: Settings.data.osd.location || "top_right" onSelected: key => Settings.data.osd.location = key } @@ -152,22 +143,22 @@ ColumnLayout { Layout.fillWidth: true label: modelData.name || I18n.tr("system.unknown") description: { - const compositorScale = CompositorService.getDisplayScale(modelData.name); + const compositorScale = CompositorService.getDisplayScale(modelData.name) I18n.tr("system.monitor-description", { - "model": modelData.model, - "width": modelData.width * compositorScale, - "height": modelData.height * compositorScale, - "scale": compositorScale - }); + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }) } checked: (Settings.data.osd.monitors || []).indexOf(modelData.name) !== -1 onToggled: checked => { - if (checked) { - Settings.data.osd.monitors = addMonitor(Settings.data.osd.monitors, modelData.name); - } else { - Settings.data.osd.monitors = removeMonitor(Settings.data.osd.monitors, modelData.name); - } - } + if (checked) { + Settings.data.osd.monitors = addMonitor(Settings.data.osd.monitors, modelData.name) + } else { + Settings.data.osd.monitors = removeMonitor(Settings.data.osd.monitors, modelData.name) + } + } } } } From 04439699ae13342510265557c7ba554f0da6ecfa Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 08:28:28 -0500 Subject: [PATCH 04/25] Keyboard Shortcuts: replaced and fixed shiftTab to backTab. --- Modules/MainScreen/SmartPanel.qml | 2 +- Modules/MainScreen/SmartPanelWindow.qml | 11 +++++------ Modules/Panels/Launcher/Launcher.qml | 2 +- Modules/Panels/SessionMenu/SessionMenu.qml | 2 +- Modules/Panels/Settings/SettingsPanel.qml | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Modules/MainScreen/SmartPanel.qml b/Modules/MainScreen/SmartPanel.qml index 6ea5950b..22bf48be 100644 --- a/Modules/MainScreen/SmartPanel.qml +++ b/Modules/MainScreen/SmartPanel.qml @@ -63,7 +63,7 @@ Item { // Note: SmartPanelWindow directly calls these functions via panelWrapper reference function onEscapePressed() {} function onTabPressed() {} - function onShiftTabPressed() {} + function onBackTabPressed() {} function onUpPressed() {} function onDownPressed() {} function onLeftPressed() {} diff --git a/Modules/MainScreen/SmartPanelWindow.qml b/Modules/MainScreen/SmartPanelWindow.qml index ba490a3e..40a52a90 100644 --- a/Modules/MainScreen/SmartPanelWindow.qml +++ b/Modules/MainScreen/SmartPanelWindow.qml @@ -191,12 +191,11 @@ PanelWindow { } else if (event.key === Qt.Key_Right && panelWrapper.onRightPressed) { panelWrapper.onRightPressed() event.accepted = true - } else if (event.key === Qt.Key_Tab) { - if (event.modifiers & Qt.ShiftModifier && panelWrapper.onShiftTabPressed) { - panelWrapper.onShiftTabPressed() - } else if (panelWrapper.onTabPressed) { - panelWrapper.onTabPressed() - } + } else if (event.key === Qt.Key_Tab && panelWrapper.onTabPressed) { + panelWrapper.onTabPressed() + event.accepted = true + } else if (event.key === Qt.Key_Backtab && panelWrapper.onBackTabPressed) { + panelWrapper.onBackTabPressed() event.accepted = true } else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && panelWrapper.onReturnPressed) { panelWrapper.onReturnPressed() diff --git a/Modules/Panels/Launcher/Launcher.qml b/Modules/Panels/Launcher/Launcher.qml index 3faf2bb3..3ee4bc57 100644 --- a/Modules/Panels/Launcher/Launcher.qml +++ b/Modules/Panels/Launcher/Launcher.qml @@ -55,7 +55,7 @@ SmartPanel { selectNextWrapped() } - function onShiftTabPressed() { + function onBackTabPressed() { selectPreviousWrapped() } diff --git a/Modules/Panels/SessionMenu/SessionMenu.qml b/Modules/Panels/SessionMenu/SessionMenu.qml index 07b071ed..3fe9441e 100644 --- a/Modules/Panels/SessionMenu/SessionMenu.qml +++ b/Modules/Panels/SessionMenu/SessionMenu.qml @@ -220,7 +220,7 @@ SmartPanel { selectNextWrapped() } - function onShiftTabPressed() { + function onBackTabPressed() { selectPreviousWrapped() } diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index 58c39833..5fcdd3a7 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -340,7 +340,7 @@ SmartPanel { selectNextTab() } - function onShiftTabPressed() { + function onBackTabPressed() { selectPreviousTab() } From 205e73e8e8f9e13c259c8719dcf76024ac7656d0 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 08:43:02 -0500 Subject: [PATCH 05/25] Cava: fix activation logic due to new panel structure. --- Modules/MainScreen/SmartPanel.qml | 2 -- Services/Media/CavaService.qml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Modules/MainScreen/SmartPanel.qml b/Modules/MainScreen/SmartPanel.qml index 22bf48be..7574beeb 100644 --- a/Modules/MainScreen/SmartPanel.qml +++ b/Modules/MainScreen/SmartPanel.qml @@ -15,8 +15,6 @@ import qs.Services.UI Item { id: root - // PUBLIC API - unchanged for backward compatibility - // Screen property provided by MainScreen property ShellScreen screen: null diff --git a/Services/Media/CavaService.qml b/Services/Media/CavaService.qml index b37b582e..ec4810e2 100644 --- a/Services/Media/CavaService.qml +++ b/Services/Media/CavaService.qml @@ -16,7 +16,7 @@ Singleton { * - LockScreen is opened * - A control center is open */ - property bool shouldRun: BarService.hasAudioVisualizer || PanelService.lockScreen?.active || (PanelService.openedPanel && PanelService.openedPanel.objectName.startsWith("controlCenterPanel")) + property bool shouldRun: BarService.hasAudioVisualizer || PanelService.lockScreen?.active || (PanelService.openedPanel && PanelService.openedPanel.panelWrapper.objectName.startsWith("controlCenterPanel")) property var values: Array(barsCount).fill(0) property int barsCount: 32 From ac9e316f56ba583fa116c04db8b46a57526b7f75 Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:58:31 +0100 Subject: [PATCH 06/25] NetworkService: re-add improved internet connectivity check Added internet connectivity check process and timer. --- Services/Networking/NetworkService.qml | 87 ++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/Services/Networking/NetworkService.qml b/Services/Networking/NetworkService.qml index 17413844..ebd94595 100644 --- a/Services/Networking/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -18,6 +18,8 @@ Singleton { property bool ethernetConnected: false property string disconnectingFrom: "" property string forgettingNetwork: "" + property string networkConnectivity: "unknown" + property bool internetConnectivity: true property bool ignoreScanResults: false property bool scanPending: false @@ -98,6 +100,16 @@ Singleton { onTriggered: ethernetStateProcess.running = true } + // Internet connectivity check timer + // Always running every 15s + Timer { + id: connectivityCheckTimer + interval: 15000 + running: true + repeat: true + onTriggered: connectivityCheckProcess.running = true + } + // Core functions function syncWifiState() { wifiStateProcess.running = true @@ -211,6 +223,8 @@ Singleton { // Helper functions function signalIcon(signal, isConnected = false) { + if (isConnected && !root.internetConnectivity) + return "world-off" if (signal >= 80) return "wifi" if (signal >= 50) @@ -285,6 +299,79 @@ Singleton { } } + // Process to check the internet connectivity of the connected network + Process { + id: connectivityCheckProcess + running: false + command: ["nmcli", "networking", "connectivity", "check"] + + property int failedChecks: 0 + + stdout: StdioCollector { + onStreamFinished: { + const result = text.trim() + if (!result) { + return + } + + if (result === "none" && root.networkConnectivity !== result) { + root.networkConnectivity = result + connectivityCheckProcess.failedChecks = 0 + root.scan() + } + + if (result === "full" && root.networkConnectivity !== result) { + root.networkConnectivity = result + root.internetConnectivity = true + connectivityCheckProcess.failedChecks = 0 + root.scan() + } + + if ((result === "limited" || result === "portal") && root.networkConnectivity !== result) { + connectivityCheckProcess.failedChecks++ + if (connectivityCheckProcess.failedChecks === 3) { + root.networkConnectivity = result + pingCheckProcess.running = true + } + } + + if (result === "unknown" && root.networkConnectivity !== result) { + root.networkConnectivity = result + connectivityCheckProcess.failedChecks = 0 + } + } + } + + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + Logger.w("Network", "Connectivity check error: " + text) + } + } + } + } + + Process { + id: pingCheckProcess + command: ["sh", "-c", + "ping -c1 -W2 ping.archlinux.org >/dev/null 2>&1 || " + + "ping -c1 -W2 1.1.1.1 >/dev/null 2>&1 || " + + "curl -fsI --max-time 5 https://cloudflare.com/cdn-cgi/trace >/dev/null 2>&1" + ] + + onExited: (exitCode, exitStatus) => { + if (exitCode === 0) { + connectivityCheckProcess.failedChecks = 0 + } else { + root.internetConnectivity = false + Logger.i("Network", "No internet connectivity") + ToastService.showWarning(root.cachedLastConnected, I18n.tr("toast.internet.limited")) + connectivityCheckProcess.failedChecks = 0 + } + root.scan() + } + } + // Helper process to get existing profiles Process { id: profileCheckProcess From 8b0c615322787085c795fff23bcd044c7c15e9df Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 08:58:40 -0500 Subject: [PATCH 07/25] Launcher: restore Tab/BackTab navigation. Fix #737 --- Modules/Panels/Launcher/Launcher.qml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Modules/Panels/Launcher/Launcher.qml b/Modules/Panels/Launcher/Launcher.qml index 3ee4bc57..88d07bb2 100644 --- a/Modules/Panels/Launcher/Launcher.qml +++ b/Modules/Panels/Launcher/Launcher.qml @@ -50,7 +50,10 @@ SmartPanel { readonly property int badgeSize: Math.round(Style.baseWidgetSize * 1.6) readonly property int entryHeight: Math.round(badgeSize + Style.marginM * 2) - // Override keyboard handlers from SmartPanel for navigation + // Override keyboard handlers from SmartPanel for navigation. + // Launcher specific: onTabPressed() and onBackTabPressed() are special here. + // They are not coming from SmartPanelWindow as they are consumed by the search field before reaching the panel. + // They are instead being forwared from the search field NTextInput below. function onTabPressed() { selectNextWrapped() } @@ -346,6 +349,16 @@ SmartPanel { Component.onCompleted: { if (searchInput.inputItem) { searchInput.inputItem.forceActiveFocus() + // Intercept Tab keys before TextField handles them + searchInput.inputItem.Keys.onPressed.connect(function (event) { + if (event.key === Qt.Key_Tab) { + root.onTabPressed() + event.accepted = true + } else if (event.key === Qt.Key_Backtab) { + root.onBackTabPressed() + event.accepted = true + } + }) } } } From fcc8394e0754338617a566e6cd1d3063bd6402da Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:12:07 +0100 Subject: [PATCH 08/25] small fix for German translation --- Assets/Translations/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a40b6585..8948e1fb 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1441,8 +1441,8 @@ "label": "Immer im Vordergrund" }, "background-opacity": { - "description": "Controls the transparency of the OSD background.", - "label": "Background opacity" + "description": "Kontrolliert die Transparenz des OSD Hintergrunds.", + "label": "Hintergrund-Transparenz" }, "description": "Bildschirm-Overlays wie Lautstärke- und Helligkeitsanzeigen konfigurieren.", "duration": { From 278fbb7f76b847bd857fb4b2cd146a95b983f765 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 09:13:03 -0500 Subject: [PATCH 09/25] Settings: SystemMonitor tab, hidden for now, until it gets content. --- Assets/Translations/de.json | 9 ++++++++ Assets/Translations/en.json | 9 ++++++++ Assets/Translations/es.json | 9 ++++++++ Assets/Translations/fr.json | 9 ++++++++ Assets/Translations/nl.json | 9 ++++++++ Assets/Translations/pt.json | 9 ++++++++ Assets/Translations/ru.json | 9 ++++++++ Assets/Translations/tr.json | 9 ++++++++ Assets/Translations/uk-UA.json | 9 ++++++++ Assets/Translations/zh-CN.json | 10 ++++++++- Commons/IconsTabler.qml | 1 + Modules/Panels/Settings/SettingsPanel.qml | 21 +++++++++++++----- .../Panels/Settings/Tabs/SystemMonitorTab.qml | 22 +++++++++++++++++++ 13 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 Modules/Panels/Settings/Tabs/SystemMonitorTab.qml diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a40b6585..68684ca6 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1567,6 +1567,15 @@ }, "title": "Sitzungsmenü" }, + "system-monitor": { + "general": { + "section": { + "description": "Konfigurieren Sie das Verhalten und Aussehen des Systemmonitors.", + "label": "Allgemein" + } + }, + "title": "Systemmonitor" + }, "user-interface": { "animation-disable": { "description": "Deaktivieren Sie alle Animationen für eine schnellere und reaktionsfreudigere Erfahrung.", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 105cd572..5e649837 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1567,6 +1567,15 @@ }, "title": "Session Menu" }, + "system-monitor": { + "general": { + "section": { + "description": "Configure the System Monitor behavior and appearance.", + "label": "General" + } + }, + "title": "System Monitor" + }, "user-interface": { "animation-disable": { "description": "Disable all animations for a faster, more responsive experience.", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index e92bc358..2c85f07a 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1567,6 +1567,15 @@ }, "title": "Menú de sesión" }, + "system-monitor": { + "general": { + "section": { + "description": "Configurar el comportamiento y la apariencia del Monitor del sistema.", + "label": "General" + } + }, + "title": "Monitor del sistema" + }, "user-interface": { "animation-disable": { "description": "Desactiva todas las animaciones para una experiencia más rápida y con mayor capacidad de respuesta.", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 90d6e5f5..d1fd83d3 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1567,6 +1567,15 @@ }, "title": "Menu de session" }, + "system-monitor": { + "general": { + "section": { + "description": "Configurer le comportement et l'apparence du Moniteur Système.", + "label": "Général" + } + }, + "title": "Moniteur système" + }, "user-interface": { "animation-disable": { "description": "Désactiver toutes les animations pour une expérience plus rapide et plus réactive.", diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index acd1897a..b2752ea2 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -1567,6 +1567,15 @@ }, "title": "Sessiemenu" }, + "system-monitor": { + "general": { + "section": { + "description": "Configureer het gedrag en de weergave van de Systeemmonitor.", + "label": "Algemeen" + } + }, + "title": "Systeemmonitor" + }, "user-interface": { "animation-disable": { "description": "Schakel alle animaties uit voor een snellere, responsievere ervaring.", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 642ae508..aa3f25e7 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1567,6 +1567,15 @@ }, "title": "Menu de sessão" }, + "system-monitor": { + "general": { + "section": { + "description": "Configure o comportamento e a aparência do Monitor do Sistema.", + "label": "Geral" + } + }, + "title": "Monitor do Sistema" + }, "user-interface": { "animation-disable": { "description": "Desative todas as animações para uma experiência mais rápida e responsiva.", diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index f76968f5..7d0b2859 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -1567,6 +1567,15 @@ }, "title": "Меню сеанса" }, + "system-monitor": { + "general": { + "section": { + "description": "Настроить поведение и внешний вид системного монитора.", + "label": "Общее" + } + }, + "title": "Системный монитор" + }, "user-interface": { "animation-disable": { "description": "Отключить все анимации для более быстрого и отзывчивого взаимодействия.", diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 5347d72a..a9b5aa7a 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -1567,6 +1567,15 @@ }, "title": "Oturum Menüsü" }, + "system-monitor": { + "general": { + "section": { + "description": "Sistem İzleyicisinin davranışını ve görünümünü yapılandırın.", + "label": "Genel" + } + }, + "title": "Sistem İzleme" + }, "user-interface": { "animation-disable": { "description": "Daha hızlı ve daha duyarlı bir deneyim için tüm animasyonları devre dışı bırakın.", diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index cbbb30b7..077e6781 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -1567,6 +1567,15 @@ }, "title": "Меню сеансу" }, + "system-monitor": { + "general": { + "section": { + "description": "Налаштуйте поведінку та зовнішній вигляд Системного монітора.", + "label": "Загальне" + } + }, + "title": "Системний монітор" + }, "user-interface": { "animation-disable": { "description": "Вимкнути всі анімації для швидшого, більш відгукового досвіду.", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 1bc217de..bfc896e4 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1474,7 +1474,6 @@ "description": "配置屏显菜单(OSD)的可见性与行为。", "label": "常规" } - }, "title": "屏显菜单" }, @@ -1568,6 +1567,15 @@ }, "title": "会话菜单" }, + "system-monitor": { + "general": { + "section": { + "description": "配置系统监视器的行为和外观。", + "label": "通用" + } + }, + "title": "系统监视器" + }, "user-interface": { "animation-disable": { "description": "禁用所有动画以获得更快、更流畅的体验。", diff --git a/Commons/IconsTabler.qml b/Commons/IconsTabler.qml index f700360b..32512878 100644 --- a/Commons/IconsTabler.qml +++ b/Commons/IconsTabler.qml @@ -123,6 +123,7 @@ Singleton { "settings-about": "info-square-rounded", "settings-lock-screen": "lock", "settings-session-menu": "power", + "settings-system-monitor": "activity", "bluetooth": "bluetooth", "bt-device-generic": "bluetooth", "bt-device-headphones": "headphones", diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index 5fcdd3a7..8ca6e144 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -77,6 +77,7 @@ SmartPanel { Notifications, ScreenRecorder, SessionMenu, + SystemMonitor, UserInterface, Wallpaper } @@ -166,6 +167,10 @@ SmartPanel { id: sessionMenuTab SessionMenuTab {} } + Component { + id: systemMonitorTab + SystemMonitorTab {} + } // Order *DOES* matter function updateTabsModel() { @@ -249,16 +254,22 @@ SmartPanel { "label": "settings.screen-recorder.title", "icon": "settings-screen-recorder", "source": screenRecorderTab - }, { - "id": SettingsPanel.Tab.Hooks, - "label": "settings.hooks.title", - "icon": "settings-hooks", - "source": hooksTab }, { "id": SettingsPanel.Tab.SessionMenu, "label": "settings.session-menu.title", "icon": "settings-session-menu", "source": sessionMenuTab + }, // { + // "id": SettingsPanel.Tab.SystemMonitor, + // "label": "settings.system-monitor.title", + // "icon": "settings-system-monitor", + // "source": systemMonitorTab + // }, + { + "id": SettingsPanel.Tab.Hooks, + "label": "settings.hooks.title", + "icon": "settings-hooks", + "source": hooksTab }, { "id": SettingsPanel.Tab.About, "label": "settings.about.title", diff --git a/Modules/Panels/Settings/Tabs/SystemMonitorTab.qml b/Modules/Panels/Settings/Tabs/SystemMonitorTab.qml new file mode 100644 index 00000000..80d58f33 --- /dev/null +++ b/Modules/Panels/Settings/Tabs/SystemMonitorTab.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +ColumnLayout { + id: root + + spacing: Style.marginL + + Component.onCompleted: { + + } + + NHeader { + label: I18n.tr("settings.system-monitor.general.section.label") + description: I18n.tr("settings.system-monitor.general.section.description") + } +} From 1288924f87e6b015573929932151079fb54455d3 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 09:16:52 -0500 Subject: [PATCH 10/25] Dragging: removed a lot of dead code since we no longer support panel dragging. --- Modules/Panels/Settings/Tabs/BarTab.qml | 22 --------------- .../Panels/Settings/Tabs/ControlCenterTab.qml | 26 ----------------- .../Panels/Settings/Tabs/SessionMenuTab.qml | 28 ------------------- 3 files changed, 76 deletions(-) diff --git a/Modules/Panels/Settings/Tabs/BarTab.qml b/Modules/Panels/Settings/Tabs/BarTab.qml index 5e0c2fad..f39d11a9 100644 --- a/Modules/Panels/Settings/Tabs/BarTab.qml +++ b/Modules/Panels/Settings/Tabs/BarTab.qml @@ -24,22 +24,6 @@ ColumnLayout { }) } - // Handler for drag start - disables panel background clicks - function handleDragStart() { - var panel = PanelService.getPanel("settingsPanel", screen) - if (panel && panel.disableBackgroundClick) { - panel.disableBackgroundClick() - } - } - - // Handler for drag end - re-enables panel background clicks - function handleDragEnd() { - var panel = PanelService.getPanel("settingsPanel", screen) - if (panel && panel.enableBackgroundClick) { - panel.enableBackgroundClick() - } - } - NHeader { label: I18n.tr("settings.bar.appearance.section.label") description: I18n.tr("settings.bar.appearance.section.description") @@ -234,8 +218,6 @@ ColumnLayout { onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection) - onDragPotentialStarted: root.handleDragStart() - onDragPotentialEnded: root.handleDragEnd() } // Center Section @@ -251,8 +233,6 @@ ColumnLayout { onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection) - onDragPotentialStarted: root.handleDragStart() - onDragPotentialEnded: root.handleDragEnd() } // Right Section @@ -268,8 +248,6 @@ ColumnLayout { onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection) - onDragPotentialStarted: root.handleDragStart() - onDragPotentialEnded: root.handleDragEnd() } } } diff --git a/Modules/Panels/Settings/Tabs/ControlCenterTab.qml b/Modules/Panels/Settings/Tabs/ControlCenterTab.qml index 989f56e7..6cdfc69b 100644 --- a/Modules/Panels/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Panels/Settings/Tabs/ControlCenterTab.qml @@ -38,22 +38,6 @@ ColumnLayout { "required": false }] - // Handler for drag start - disables panel background clicks - function handleDragStart() { - var panel = PanelService.getPanel("settingsPanel", screen) - if (panel && panel.disableBackgroundClick) { - panel.disableBackgroundClick() - } - } - - // Handler for drag end - re-enables panel background clicks - function handleDragEnd() { - var panel = PanelService.getPanel("settingsPanel", screen) - if (panel && panel.enableBackgroundClick) { - panel.enableBackgroundClick() - } - } - function saveCards() { var toSave = [] for (var i = 0; i < cardsModel.length; i++) { @@ -202,12 +186,6 @@ ColumnLayout { Layout.fillWidth: true model: cardsModel disabledIds: Settings.data.location.weatherEnabled ? [] : ["weather-card"] - onDragPotentialStarted: { - root.handleDragStart() - } - onDragPotentialEnded: { - root.handleDragEnd() - } onItemToggled: function (index, enabled) { //Logger.i("ControlCenterTab", "Item", index, "toggled to", enabled) var newModel = cardsModel.slice() @@ -266,8 +244,6 @@ ColumnLayout { onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection) - onDragPotentialStarted: root.handleDragStart() - onDragPotentialEnded: root.handleDragEnd() } // Right @@ -285,8 +261,6 @@ ColumnLayout { onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection) - onDragPotentialStarted: root.handleDragStart() - onDragPotentialEnded: root.handleDragEnd() } } } diff --git a/Modules/Panels/Settings/Tabs/SessionMenuTab.qml b/Modules/Panels/Settings/Tabs/SessionMenuTab.qml index 184f2cba..4301cde0 100644 --- a/Modules/Panels/Settings/Tabs/SessionMenuTab.qml +++ b/Modules/Panels/Settings/Tabs/SessionMenuTab.qml @@ -39,28 +39,6 @@ ColumnLayout { "required": false }] - // Handler for drag start - disables panel background clicks - function handleDragStart() { - var currentScreen = Quickshell.screens && Quickshell.screens.length > 0 ? Quickshell.screens[0] : null - if (currentScreen) { - var panel = PanelService.getPanel("settingsPanel", currentScreen) - if (panel && panel.disableBackgroundClick) { - panel.disableBackgroundClick() - } - } - } - - // Handler for drag end - re-enables panel background clicks - function handleDragEnd() { - var currentScreen = Quickshell.screens && Quickshell.screens.length > 0 ? Quickshell.screens[0] : null - if (currentScreen) { - var panel = PanelService.getPanel("settingsPanel", currentScreen) - if (panel && panel.enableBackgroundClick) { - panel.enableBackgroundClick() - } - } - } - function saveEntries() { var toSave = [] for (var i = 0; i < entriesModel.length; i++) { @@ -201,12 +179,6 @@ ColumnLayout { Layout.fillWidth: true model: entriesModel disabledIds: [] - onDragPotentialStarted: { - root.handleDragStart() - } - onDragPotentialEnded: { - root.handleDragEnd() - } onItemToggled: function (index, enabled) { var newModel = entriesModel.slice() newModel[index] = Object.assign({}, newModel[index], { From 4fd5865978ebd9a4f2b002bd17735eae2dba1769 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Thu, 13 Nov 2025 15:41:46 +0100 Subject: [PATCH 11/25] DistroService: rename to HostService --- Modules/Bar/Widgets/ControlCenter.qml | 2 +- Modules/Panels/Settings/Tabs/GeneralTab.qml | 2 +- Services/System/{DistroService.qml => HostService.qml} | 10 +++++----- shell.qml | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) rename Services/System/{DistroService.qml => HostService.qml} (91%) diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index 02dbff04..789bdeb7 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -71,7 +71,7 @@ NIconButton { if (customIconPath !== "") return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath if (useDistroLogo) - return DistroService.osLogo + return HostService.osLogo return "" } visible: source !== "" diff --git a/Modules/Panels/Settings/Tabs/GeneralTab.qml b/Modules/Panels/Settings/Tabs/GeneralTab.qml index c3bdfa99..955ed3a7 100644 --- a/Modules/Panels/Settings/Tabs/GeneralTab.qml +++ b/Modules/Panels/Settings/Tabs/GeneralTab.qml @@ -233,7 +233,7 @@ ColumnLayout { } NButton { - visible: !DistroService.isNixOS + visible: !HostService.isNixOS text: I18n.tr("settings.general.launch-setup-wizard") onClicked: { var targetScreen = PanelService.openedPanel ? PanelService.openedPanel.screen : (Quickshell.screens.length > 0 ? Quickshell.screens[0] : null) diff --git a/Services/System/DistroService.qml b/Services/System/HostService.qml similarity index 91% rename from Services/System/DistroService.qml rename to Services/System/HostService.qml index 0865cd52..adcd766d 100644 --- a/Services/System/DistroService.qml +++ b/Services/System/HostService.qml @@ -15,7 +15,7 @@ Singleton { property bool isReady: false function init() { - Logger.i("DistroService", "Service started") + Logger.i("HostService", "Service started") } // Internal helpers @@ -81,7 +81,7 @@ Singleton { return l ? l.split("=")[1].replace(/"/g, "") : "" } root.osPretty = val("PRETTY_NAME") || val("NAME") - Logger.i("DistroService", root.osPretty) + Logger.i("HostService", root.osPretty) const osId = (val("ID") || "").toLowerCase() root.isNixOS = osId === "nixos" || (root.osPretty || "").toLowerCase().includes("nixos") @@ -91,7 +91,7 @@ Singleton { } root.isReady = true } catch (e) { - Logger.w("DistroService", "failed to read os-release", e) + Logger.w("HostService", "failed to read os-release", e) } } } @@ -102,10 +102,10 @@ Singleton { const p = String(stdout.text || "").trim() if (code === 0 && p) { root.osLogo = `file://${p}` - Logger.i("DistroService", "found", root.osLogo) + Logger.i("HostService", "found", root.osLogo) } else { root.osLogo = "" - Logger.w("DistroService", "none found") + Logger.w("HostService", "none found") } } stdout: StdioCollector {} diff --git a/shell.qml b/shell.qml index 35152cd7..fee3667c 100644 --- a/shell.qml +++ b/shell.qml @@ -81,7 +81,7 @@ ShellRoot { BatteryService.init() IdleInhibitorService.init() PowerProfileService.init() - DistroService.init() + HostService.init() FontService.init() // Only open the setup wizard for new users @@ -128,13 +128,13 @@ ShellRoot { function checkSetupWizard() { // Wait for distro service - if (!DistroService.isReady) { + if (!HostService.isReady) { Qt.callLater(checkSetupWizard) return } // No setup wizard on NixOS - if (DistroService.isNixOS) { + if (HostService.isNixOS) { Settings.data.setupCompleted = true return } From 9b2abd313dd95fa00051cb25c06960f79c970477 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 09:52:29 -0500 Subject: [PATCH 12/25] Tray: fix drawer toggle/caret theming in light theme. --- Modules/Bar/Widgets/Tray.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 6d914e85..921a66c4 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -396,8 +396,8 @@ Rectangle { density: Settings.data.bar.density baseSize: Style.capsuleHeight applyUiScale: false - colorBg: Settings.data.colorSchemes.darkMode ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary - colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary + colorBg: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent + colorFg: Color.mOnSurface colorBorder: Color.transparent colorBorderHover: Color.transparent icon: { From 3d81d445a31886fb327fd503a062096b7d852e23 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 10:26:51 -0500 Subject: [PATCH 13/25] Network: support for ethernet-off icon even tho the logic is not in yet. --- Assets/settings-default.json | 2 +- Commons/IconsTabler.qml | 1 + Modules/Bar/Widgets/WiFi.qml | 4 ++-- Modules/Panels/ControlCenter/Widgets/WiFi.qml | 4 ++-- Services/Networking/NetworkService.qml | 20 ++++++++----------- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index d7f03eff..320adebc 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -322,4 +322,4 @@ "battery": { "chargingMode": 0 } -} +} \ No newline at end of file diff --git a/Commons/IconsTabler.qml b/Commons/IconsTabler.qml index fa0f029d..2db54ae7 100644 --- a/Commons/IconsTabler.qml +++ b/Commons/IconsTabler.qml @@ -42,6 +42,7 @@ Singleton { "powersaver": "leaf", "storage": "database", "ethernet": "sitemap", + "ethernet-off": "sitemap-off", "keyboard": "keyboard", "shutdown": "power", "lock": "lock", diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index 2c23542b..2508b0ba 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -41,7 +41,7 @@ Item { icon: { try { if (NetworkService.ethernetConnected) { - return "ethernet" + return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off" } let connected = false let signalStrength = 0 @@ -55,7 +55,7 @@ Item { return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off" } catch (error) { Logger.e("Wi-Fi", "Error getting icon:", error) - return "signal_wifi_bad" + return "wifi-off" } } text: { diff --git a/Modules/Panels/ControlCenter/Widgets/WiFi.qml b/Modules/Panels/ControlCenter/Widgets/WiFi.qml index 2ea25141..fae2b122 100644 --- a/Modules/Panels/ControlCenter/Widgets/WiFi.qml +++ b/Modules/Panels/ControlCenter/Widgets/WiFi.qml @@ -11,7 +11,7 @@ NIconButtonHot { icon: { try { if (NetworkService.ethernetConnected) { - return "ethernet" + return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off" } let connected = false let signalStrength = 0 @@ -25,7 +25,7 @@ NIconButtonHot { return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off" } catch (error) { Logger.e("Wi-Fi", "Error getting icon:", error) - return "signal_wifi_bad" + return "wifi-off" } } diff --git a/Services/Networking/NetworkService.qml b/Services/Networking/NetworkService.qml index ebd94595..543a9954 100644 --- a/Services/Networking/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -304,29 +304,29 @@ Singleton { id: connectivityCheckProcess running: false command: ["nmcli", "networking", "connectivity", "check"] - + property int failedChecks: 0 - + stdout: StdioCollector { onStreamFinished: { const result = text.trim() if (!result) { return } - + if (result === "none" && root.networkConnectivity !== result) { root.networkConnectivity = result connectivityCheckProcess.failedChecks = 0 root.scan() } - + if (result === "full" && root.networkConnectivity !== result) { root.networkConnectivity = result root.internetConnectivity = true connectivityCheckProcess.failedChecks = 0 root.scan() } - + if ((result === "limited" || result === "portal") && root.networkConnectivity !== result) { connectivityCheckProcess.failedChecks++ if (connectivityCheckProcess.failedChecks === 3) { @@ -334,7 +334,7 @@ Singleton { pingCheckProcess.running = true } } - + if (result === "unknown" && root.networkConnectivity !== result) { root.networkConnectivity = result connectivityCheckProcess.failedChecks = 0 @@ -350,14 +350,10 @@ Singleton { } } } - + Process { id: pingCheckProcess - command: ["sh", "-c", - "ping -c1 -W2 ping.archlinux.org >/dev/null 2>&1 || " + - "ping -c1 -W2 1.1.1.1 >/dev/null 2>&1 || " + - "curl -fsI --max-time 5 https://cloudflare.com/cdn-cgi/trace >/dev/null 2>&1" - ] + command: ["sh", "-c", "ping -c1 -W2 ping.archlinux.org >/dev/null 2>&1 || " + "ping -c1 -W2 1.1.1.1 >/dev/null 2>&1 || " + "curl -fsI --max-time 5 https://cloudflare.com/cdn-cgi/trace >/dev/null 2>&1"] onExited: (exitCode, exitStatus) => { if (exitCode === 0) { From 52071efc085c0218b2183ffb52fefb3f0c053023 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Thu, 13 Nov 2025 16:50:24 +0100 Subject: [PATCH 14/25] SettingsWindow make SideBar more compact --- Modules/Panels/Settings/SettingsPanel.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index 8ca6e144..4ae0cff5 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -412,14 +412,14 @@ SmartPanel { anchors.fill: parent anchors.margins: Style.marginS model: root.tabsModel - spacing: Style.marginXS + spacing: Style.marginXXS currentIndex: root.currentTabIndex verticalPolicy: ScrollBar.AsNeeded delegate: Rectangle { id: tabItem width: sidebarList.verticalScrollBarActive ? sidebarList.width - sidebarList.scrollBarWidth - Style.marginXS : sidebarList.width - height: tabEntryRow.implicitHeight + Style.marginM * 2 + height: tabEntryRow.implicitHeight + Style.marginS * 2 radius: Style.radiusS color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mHover : Color.transparent) readonly property bool selected: index === root.currentTabIndex From e1f692be04f011c19891af90d726115a85fa6163 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Thu, 13 Nov 2025 16:54:45 +0100 Subject: [PATCH 15/25] SettingsWindow: reorder SideBar --- Modules/Panels/Settings/SettingsPanel.qml | 66 +++++++++++------------ 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index 4ae0cff5..6333a26b 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -184,31 +184,56 @@ SmartPanel { "label": "settings.user-interface.title", "icon": "settings-user-interface", "source": userInterfaceTab + }, { + "id": SettingsPanel.Tab.ColorScheme, + "label": "settings.color-scheme.title", + "icon": "settings-color-scheme", + "source": colorSchemeTab + }, { + "id": SettingsPanel.Tab.Wallpaper, + "label": "settings.wallpaper.title", + "icon": "settings-wallpaper", + "source": wallpaperTab }, { "id": SettingsPanel.Tab.Bar, "label": "settings.bar.title", "icon": "settings-bar", "source": barTab - }, { - "id": SettingsPanel.Tab.ControlCenter, - "label": "settings.control-center.title", - "icon": "settings-control-center", - "source": controlCenterTab }, { "id": SettingsPanel.Tab.Dock, "label": "settings.dock.title", "icon": "settings-dock", "source": dockTab + }, { + "id": SettingsPanel.Tab.ControlCenter, + "label": "settings.control-center.title", + "icon": "settings-control-center", + "source": controlCenterTab }, { "id": SettingsPanel.Tab.Launcher, "label": "settings.launcher.title", "icon": "settings-launcher", "source": launcherTab + }, { + "id": SettingsPanel.Tab.Notifications, + "label": "settings.notifications.title", + "icon": "settings-notifications", + "source": notificationsTab + }, { + "id": SettingsPanel.Tab.OSD, + "label": "settings.osd.title", + "icon": "settings-osd", + "source": osdTab }, { "id": SettingsPanel.Tab.LockScreen, "label": "settings.lock-screen.title", "icon": "settings-lock-screen", "source": lockScreenTab + }, { + "id": SettingsPanel.Tab.SessionMenu, + "label": "settings.session-menu.title", + "icon": "settings-session-menu", + "source": sessionMenuTab }, { "id": SettingsPanel.Tab.Audio, "label": "settings.audio.title", @@ -219,16 +244,6 @@ SmartPanel { "label": "settings.display.title", "icon": "settings-display", "source": displayTab - }, { - "id": SettingsPanel.Tab.OSD, - "label": "settings.osd.title", - "icon": "settings-osd", - "source": osdTab - }, { - "id": SettingsPanel.Tab.Notifications, - "label": "settings.notifications.title", - "icon": "settings-notifications", - "source": notificationsTab }, { "id": SettingsPanel.Tab.Network, "label": "settings.network.title", @@ -239,33 +254,12 @@ SmartPanel { "label": "settings.location.title", "icon": "settings-location", "source": locationTab - }, { - "id": SettingsPanel.Tab.ColorScheme, - "label": "settings.color-scheme.title", - "icon": "settings-color-scheme", - "source": colorSchemeTab - }, { - "id": SettingsPanel.Tab.Wallpaper, - "label": "settings.wallpaper.title", - "icon": "settings-wallpaper", - "source": wallpaperTab }, { "id": SettingsPanel.Tab.ScreenRecorder, "label": "settings.screen-recorder.title", "icon": "settings-screen-recorder", "source": screenRecorderTab }, { - "id": SettingsPanel.Tab.SessionMenu, - "label": "settings.session-menu.title", - "icon": "settings-session-menu", - "source": sessionMenuTab - }, // { - // "id": SettingsPanel.Tab.SystemMonitor, - // "label": "settings.system-monitor.title", - // "icon": "settings-system-monitor", - // "source": systemMonitorTab - // }, - { "id": SettingsPanel.Tab.Hooks, "label": "settings.hooks.title", "icon": "settings-hooks", From 6e368601b40e0c496f84f2837c80f71501afde89 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 12:00:15 -0500 Subject: [PATCH 16/25] Wallpaper: improved image switching to avoid main thread micro freeze. --- Modules/Background/Background.qml | 41 ++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 8c01040d..d5023fab 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -306,20 +306,37 @@ Variants { duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration * 1.6 : Settings.data.wallpaper.transitionDuration easing.type: Easing.InOutCubic onFinished: { - // Assign new image to current BEFORE clearing to prevent flicker + // Strategy: Keep transitionProgress at 1.0 (showing nextWallpaper) + // until currentWallpaper finishes loading asynchronously const tempSource = nextWallpaper.source - currentWallpaper.source = tempSource - transitionProgress = 0.0 + const tempSourceSize = nextWallpaper.sourceSize - // Now clear nextWallpaper after currentWallpaper has the new source - // Force complete cleanup to free texture memory (~18-25MB per monitor) - Qt.callLater(() => { - nextWallpaper.source = "" - nextWallpaper.sourceSize = undefined - Qt.callLater(() => { - currentWallpaper.asynchronous = true - }) - }) + // Enable async loading to prevent blocking + currentWallpaper.asynchronous = true + + // Create one-time connection to wait for async load to complete + const onCurrentLoaded = function () { + if (currentWallpaper.status === Image.Ready || currentWallpaper.status === Image.Error) { + // Disconnect this handler + currentWallpaper.statusChanged.disconnect(onCurrentLoaded) + + // Now it's safe to reset progress and cleanup + transitionProgress = 0.0 + + // Force complete cleanup to free texture memory (~18-25MB per monitor) + Qt.callLater(() => { + nextWallpaper.source = "" + nextWallpaper.sourceSize = undefined + }) + } + } + + // Connect the handler BEFORE changing source + currentWallpaper.statusChanged.connect(onCurrentLoaded) + + // Trigger async load (keeps nextWallpaper visible via progress=1.0) + currentWallpaper.sourceSize = tempSourceSize + currentWallpaper.source = tempSource } } From 17ff97f8c5ce8de43cf4e0c34cbd0931d1caef4e Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 13:28:47 -0500 Subject: [PATCH 17/25] WLR Layer Name: noctalia-screen-* changed to noctalia-background-* --- Modules/MainScreen/MainScreen.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/MainScreen/MainScreen.qml b/Modules/MainScreen/MainScreen.qml index 2c15f406..3f51f134 100644 --- a/Modules/MainScreen/MainScreen.qml +++ b/Modules/MainScreen/MainScreen.qml @@ -67,7 +67,7 @@ PanelWindow { // Wayland WlrLayershell.layer: WlrLayer.Top - WlrLayershell.namespace: "noctalia-screen-" + (screen?.name || "unknown") + WlrLayershell.namespace: "noctalia-background-" + (screen?.name || "unknown") WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone handles that WlrLayershell.keyboardFocus: WlrKeyboardFocus.None From 0ab9aa1c0e27f0703e1d0ef4cf441ad114eefcb0 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 14:42:42 -0500 Subject: [PATCH 18/25] Tray: fix screen access that does not exists on first load. --- Modules/Bar/Extras/TrayMenu.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml index 05729c38..f4e17e78 100644 --- a/Modules/Bar/Extras/TrayMenu.qml +++ b/Modules/Bar/Extras/TrayMenu.qml @@ -8,13 +8,15 @@ import qs.Widgets PopupWindow { id: root + + property ShellScreen screen + property var trayItem: null property var anchorItem: null property real anchorX property real anchorY property bool isSubMenu: false property bool isHovered: rootMouseArea.containsMouse - property ShellScreen screen property string widgetSection: "" property int widgetIndex: -1 @@ -45,7 +47,7 @@ PopupWindow { implicitWidth: menuWidth // Use the content height of the Flickable for implicit height - implicitHeight: Math.min(screen.height * 0.9, flickable.contentHeight + (Style.marginS * 2)) + implicitHeight: Math.min(screen?.height * 0.9, flickable.contentHeight + (Style.marginS * 2)) visible: false color: Color.transparent anchor.item: anchorItem From 6004807776908e5e8334a8f611a588dff8f99079 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 14:43:14 -0500 Subject: [PATCH 19/25] AllBackgrounds: centralize panel background color computation. --- .../MainScreen/Backgrounds/AllBackgrounds.qml | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml index bd5e082e..50123f9a 100644 --- a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml +++ b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml @@ -23,6 +23,8 @@ Item { // Reference to MainScreen (for panel access) required property var windowRoot + readonly property color panelBackgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + anchors.fill: parent // Wrapper with layer caching for better shadow performance @@ -69,91 +71,91 @@ Item { PanelBackground { panel: root.windowRoot.audioPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Battery PanelBackground { panel: root.windowRoot.batteryPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Bluetooth PanelBackground { panel: root.windowRoot.bluetoothPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Calendar PanelBackground { panel: root.windowRoot.calendarPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Control Center PanelBackground { panel: root.windowRoot.controlCenterPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Launcher PanelBackground { panel: root.windowRoot.launcherPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Notification History PanelBackground { panel: root.windowRoot.notificationHistoryPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Session Menu PanelBackground { panel: root.windowRoot.sessionMenuPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Settings PanelBackground { panel: root.windowRoot.settingsPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Setup Wizard PanelBackground { panel: root.windowRoot.setupWizardPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // TrayDrawer PanelBackground { panel: root.windowRoot.trayDrawerPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // Wallpaper PanelBackground { panel: root.windowRoot.wallpaperPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } // WiFi PanelBackground { panel: root.windowRoot.wifiPanelPlaceholder shapeContainer: backgroundsShape - backgroundColor: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) + backgroundColor: panelBackgroundColor } } From aace0a8ada1998d93d7eb24bbeeb8e0bc1aca2a9 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Thu, 13 Nov 2025 21:00:12 +0100 Subject: [PATCH 20/25] TaskbarGrouped: add scroll support TaskbarGroupoedSettings: add colorize, hide when unoccupied, name display settings --- Modules/Bar/Widgets/TaskbarGrouped.qml | 84 ++++++++++++++++++- .../WidgetSettings/TaskbarGroupedSettings.qml | 44 +++++++++- Modules/Panels/Settings/SettingsPanel.qml | 11 +++ Services/UI/BarWidgetRegistry.qml | 6 +- 4 files changed, 138 insertions(+), 7 deletions(-) diff --git a/Modules/Bar/Widgets/TaskbarGrouped.qml b/Modules/Bar/Widgets/TaskbarGrouped.qml index d6f7f765..a30d16db 100644 --- a/Modules/Bar/Widgets/TaskbarGrouped.qml +++ b/Modules/Bar/Widgets/TaskbarGrouped.qml @@ -33,14 +33,21 @@ Item { } return {} } - readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : false + readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode + readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied + readonly property int characterCount: 2 readonly property bool showWorkspaceNumbers: (widgetSettings.showWorkspaceNumbers !== undefined) ? widgetSettings.showWorkspaceNumbers : true readonly property bool showNumbersOnlyWhenOccupied: (widgetSettings.showNumbersOnlyWhenOccupied !== undefined) ? widgetSettings.showNumbersOnlyWhenOccupied : true + readonly property bool colorizeIcons: (widgetSettings.colorizeIcons !== undefined) ? widgetSettings.colorizeIcons : widgetMetadata.colorizeIcons property ListModel localWorkspaces: ListModel {} property real masterProgress: 0.0 property bool effectsActive: false property color effectColor: Color.mPrimary + // Wheel scroll handling + property int wheelAccumulatedDelta: 0 + property bool wheelCooldown: false + function refreshWorkspaces() { localWorkspaces.clear() if (!screen) @@ -80,11 +87,34 @@ Item { } } + function getFocusedLocalIndex() { + for (var i = 0; i < localWorkspaces.count; i++) { + if (localWorkspaces.get(i).isFocused === true) + return i + } + return -1 + } + + function switchByOffset(offset) { + if (localWorkspaces.count === 0) + return + var current = getFocusedLocalIndex() + if (current < 0) + current = 0 + var next = (current + offset) % localWorkspaces.count + if (next < 0) + next = localWorkspaces.count - 1 + const ws = localWorkspaces.get(next) + if (ws && ws.idx !== undefined) + CompositorService.switchToWorkspace(ws) + } + Component.onCompleted: { refreshWorkspaces() } onScreenChanged: refreshWorkspaces() + onHideUnoccupiedChanged: refreshWorkspaces() implicitWidth: isVerticalBar ? taskbarGrid.implicitWidth + Style.marginM * 2 : Math.round(taskbarGrid.implicitWidth + Style.marginM * 2) implicitHeight: isVerticalBar ? Math.round(taskbarGrid.implicitHeight + Style.marginM * 2) : Style.barHeight @@ -128,6 +158,46 @@ Item { } } + // Debounce timer for wheel interactions + Timer { + id: wheelDebounce + interval: 150 + repeat: false + onTriggered: { + root.wheelCooldown = false + root.wheelAccumulatedDelta = 0 + } + } + + // Scroll to switch workspaces + WheelHandler { + id: wheelHandler + target: root + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: function (event) { + if (root.wheelCooldown) + return + // Prefer vertical delta, fall back to horizontal if needed + var dy = event.angleDelta.y + var dx = event.angleDelta.x + var useDy = Math.abs(dy) >= Math.abs(dx) + var delta = useDy ? dy : dx + // One notch is typically 120 + root.wheelAccumulatedDelta += delta + var step = 120 + if (Math.abs(root.wheelAccumulatedDelta) >= step) { + var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1 + // For vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign) + // For horizontal layout, same mapping using vertical wheel + root.switchByOffset(direction) + root.wheelCooldown = true + wheelDebounce.restart() + root.wheelAccumulatedDelta = 0 + event.accepted = true + } + } + } + Component { id: workspaceRepeaterDelegate @@ -192,7 +262,7 @@ Item { smooth: true asynchronous: true opacity: model.isFocused ? Style.opacityFull : 0.6 - layer.enabled: widgetSettings.colorizeIcons === true + layer.enabled: root.colorizeIcons && !model.isFocused Behavior on opacity { NumberAnimation { @@ -252,7 +322,7 @@ Item { Item { id: workspaceNumberContainer - visible: root.showWorkspaceNumbers && (!root.showNumbersOnlyWhenOccupied || container.hasWindows) + visible: root.labelMode !== "none" && root.showWorkspaceNumbers && (!root.showNumbersOnlyWhenOccupied || container.hasWindows) anchors { left: parent.left @@ -318,7 +388,13 @@ Item { anchors.centerIn: parent - text: workspaceModel.idx.toString() + text: { + if (root.labelMode === "name" && workspaceModel.name && workspaceModel.name.length > 0) { + return workspaceModel.name.substring(0, root.characterCount) + } else { + return workspaceModel.idx.toString() + } + } family: Settings.data.ui.fontFixed font { diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarGroupedSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarGroupedSettings.qml index 09342c35..2f5ac5d6 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarGroupedSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarGroupedSettings.qml @@ -12,16 +12,56 @@ ColumnLayout { property var widgetData: null property var widgetMetadata: null - property bool valueShowWorkspaceNumbers: widgetData.showWorkspaceNumbers !== undefined ? widgetData.showWorkspaceNumbers : widgetMetadata.showWorkspaceNumbers - property bool valueShowNumbersOnlyWhenOccupied: widgetData.showNumbersOnlyWhenOccupied !== undefined ? widgetData.showNumbersOnlyWhenOccupied : widgetMetadata.showNumbersOnlyWhenOccupied + property string valueLabelMode: widgetData.labelMode !== undefined ? widgetData.labelMode : (widgetMetadata ? widgetMetadata.labelMode : "index") + property bool valueHideUnoccupied: widgetData.hideUnoccupied !== undefined ? widgetData.hideUnoccupied : (widgetMetadata ? widgetMetadata.hideUnoccupied : false) + property bool valueShowWorkspaceNumbers: widgetData.showWorkspaceNumbers !== undefined ? widgetData.showWorkspaceNumbers : (widgetMetadata ? widgetMetadata.showWorkspaceNumbers : true) + property bool valueShowNumbersOnlyWhenOccupied: widgetData.showNumbersOnlyWhenOccupied !== undefined ? widgetData.showNumbersOnlyWhenOccupied : (widgetMetadata ? widgetMetadata.showNumbersOnlyWhenOccupied : true) + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : (widgetMetadata ? widgetMetadata.colorizeIcons : false) function saveSettings() { var settings = Object.assign({}, widgetData || {}) + settings.labelMode = valueLabelMode + settings.hideUnoccupied = valueHideUnoccupied settings.showWorkspaceNumbers = valueShowWorkspaceNumbers settings.showNumbersOnlyWhenOccupied = valueShowNumbersOnlyWhenOccupied + settings.colorizeIcons = valueColorizeIcons return settings } + NComboBox { + id: labelModeCombo + label: I18n.tr("bar.widget-settings.workspace.label-mode.label") + description: I18n.tr("bar.widget-settings.workspace.label-mode.description") + model: [{ + "key": "none", + "name": I18n.tr("options.workspace-labels.none") + }, { + "key": "index", + "name": I18n.tr("options.workspace-labels.index") + }, { + "key": "name", + "name": I18n.tr("options.workspace-labels.name") + }] + currentKey: widgetData.labelMode || widgetMetadata.labelMode + onSelected: key => valueLabelMode = key + minimumWidth: 200 + } + + NToggle { + label: I18n.tr("bar.widget-settings.workspace.hide-unoccupied.label") + description: I18n.tr("bar.widget-settings.workspace.hide-unoccupied.description") + checked: valueHideUnoccupied + onToggled: checked => valueHideUnoccupied = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.active-window.colorize-icons.label") + description: I18n.tr("bar.widget-settings.active-window.colorize-icons.description") + checked: root.valueColorizeIcons + onToggled: checked => root.valueColorizeIcons = checked + } + NToggle { Layout.fillWidth: true label: I18n.tr("bar.widget-settings.taskbar-grouped.show-workspace-numbers.label") diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index 6333a26b..c0dda187 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -260,6 +260,17 @@ SmartPanel { "icon": "settings-screen-recorder", "source": screenRecorderTab }, { + "id": SettingsPanel.Tab.SessionMenu, + "label": "settings.session-menu.title", + "icon": "settings-session-menu", + "source": sessionMenuTab + }, // { + // "id": SettingsPanel.Tab.SystemMonitor, + // "label": "settings.system-monitor.title", + // "icon": "settings-system-monitor", + // "source": systemMonitorTab + // }, + { "id": SettingsPanel.Tab.Hooks, "label": "settings.hooks.title", "icon": "settings-hooks", diff --git a/Services/UI/BarWidgetRegistry.qml b/Services/UI/BarWidgetRegistry.qml index c6427c9e..c4133e47 100644 --- a/Services/UI/BarWidgetRegistry.qml +++ b/Services/UI/BarWidgetRegistry.qml @@ -162,7 +162,11 @@ Singleton { "TaskbarGrouped": { "allowUserSettings": true, "showWorkspaceNumbers": true, - "showNumbersOnlyWhenOccupied": true + "showNumbersOnlyWhenOccupied": true, + "labelMode": "index", + "hideUnoccupied": false, + "characterCount": 2, + "colorizeIcons": false }, "Tray": { "allowUserSettings": true, From 04fcb94ebcad71aad63336492f3cf7191104dfa3 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 13 Nov 2025 15:45:46 -0500 Subject: [PATCH 21/25] Revert "Wallpaper: improved image switching to avoid main thread micro freeze." This reverts commit 6e368601b40e0c496f84f2837c80f71501afde89. --- Modules/Background/Background.qml | 41 +++++++++---------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index d5023fab..8c01040d 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -306,37 +306,20 @@ Variants { duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration * 1.6 : Settings.data.wallpaper.transitionDuration easing.type: Easing.InOutCubic onFinished: { - // Strategy: Keep transitionProgress at 1.0 (showing nextWallpaper) - // until currentWallpaper finishes loading asynchronously + // Assign new image to current BEFORE clearing to prevent flicker const tempSource = nextWallpaper.source - const tempSourceSize = nextWallpaper.sourceSize - - // Enable async loading to prevent blocking - currentWallpaper.asynchronous = true - - // Create one-time connection to wait for async load to complete - const onCurrentLoaded = function () { - if (currentWallpaper.status === Image.Ready || currentWallpaper.status === Image.Error) { - // Disconnect this handler - currentWallpaper.statusChanged.disconnect(onCurrentLoaded) - - // Now it's safe to reset progress and cleanup - transitionProgress = 0.0 - - // Force complete cleanup to free texture memory (~18-25MB per monitor) - Qt.callLater(() => { - nextWallpaper.source = "" - nextWallpaper.sourceSize = undefined - }) - } - } - - // Connect the handler BEFORE changing source - currentWallpaper.statusChanged.connect(onCurrentLoaded) - - // Trigger async load (keeps nextWallpaper visible via progress=1.0) - currentWallpaper.sourceSize = tempSourceSize currentWallpaper.source = tempSource + transitionProgress = 0.0 + + // Now clear nextWallpaper after currentWallpaper has the new source + // Force complete cleanup to free texture memory (~18-25MB per monitor) + Qt.callLater(() => { + nextWallpaper.source = "" + nextWallpaper.sourceSize = undefined + Qt.callLater(() => { + currentWallpaper.asynchronous = true + }) + }) } } From 96ae2c0d6fe26a64244659c8654535c553e9c45e Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 14 Nov 2025 07:40:15 +0100 Subject: [PATCH 22/25] Matugen/Code: add VSCodium support --- Assets/Translations/de.json | 2 +- Assets/Translations/en.json | 2 +- Assets/Translations/es.json | 2 +- Assets/Translations/fr.json | 2 +- Assets/Translations/nl.json | 2 +- Assets/Translations/pt.json | 2 +- Assets/Translations/ru.json | 2 +- Assets/Translations/tr.json | 2 +- Assets/Translations/uk-UA.json | 2 +- Assets/Translations/zh-CN.json | 2 +- .../Panels/Settings/Tabs/ColorSchemeTab.qml | 49 +++++++++++--- Services/System/ProgramCheckerService.qml | 66 ++++++++++++++++++- Services/Theming/TemplateProcessor.qml | 59 +++++++++++++++++ Services/Theming/TemplateRegistry.qml | 35 +++++++++- 14 files changed, 206 insertions(+), 23 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a40c3374..0474b777 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Schreibe {Dateipfad}. Das Hyprluna-Theme muss manuell installiert und aktiviert werden", - "description-missing": "Benötigt die Installation von {app}" + "description-missing": "Kein Code-Client erkannt. Installieren Sie VSCode oder VSCodium." }, "description": "Anwendungsspezifisches Theming.", "discord": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 5c4ab9f1..b92c800e 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Write {filepath}. Hyprluna theme needs to be installed and activated manually.", - "description-missing": "Requires {app} to be installed" + "description-missing": "No Code client detected. Install VSCode or VSCodium." }, "description": "Application-specific theming.", "discord": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 76decb25..9076a78d 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Escribe {filepath}. El tema Hyprluna debe ser instalado y activado manualmente.", - "description-missing": "Requiere que {app} esté instalado/a." + "description-missing": "No se detectó cliente de Code. Instala VSCode o VSCodium." }, "description": "Tematización específica de aplicaciones.", "discord": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 6f0a1bae..10e96498 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Écrire {filepath}. Le thème Hyprluna doit être installé et activé manuellement.", - "description-missing": "Nécessite l'installation de {app}" + "description-missing": "Aucun client Code détecté. Installez VSCode ou VSCodium." }, "description": "Thématisation spécifique aux applications.", "discord": { diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index f1f74974..0568efee 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Schrijf {filepath}. Het Hyprluna-thema moet handmatig worden geïnstalleerd en geactiveerd.", - "description-missing": "Vereist dat {app} is geïnstalleerd." + "description-missing": "Geen Code-client gedetecteerd. Installeer VSCode of VSCodium." }, "description": "Toepassingsspecifieke theming.", "discord": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index fac180ae..7d62b4ae 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Escreva em {filepath}. O tema Hyprluna precisa ser instalado e ativado manualmente.", - "description-missing": "Requer que o {app} esteja instalado." + "description-missing": "Nenhum cliente Code detectado. Instale VSCode ou VSCodium." }, "description": "Tematização específica de aplicativos.", "discord": { diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index 45bb6fe6..64648c1f 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Записать {filepath}. Тему Hyprluna нужно установить и активировать вручную.", - "description-missing": "Требуется установка {app}" + "description-missing": "Клиент Code не обнаружен. Установите VSCode или VSCodium." }, "description": "Тематика для конкретных приложений.", "discord": { diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 9fafd582..30335a5c 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "{filepath} dosyasına yaz. Hyprluna temasının kurulu ve manuel olarak etkinleştirilmiş olması gerekir.", - "description-missing": "Kurulum için {app} gereklidir" + "description-missing": "Code istemcisi tespit edilmedi. VSCode veya VSCodium kurun." }, "description": "Uygulamaya özel temalandırma.", "discord": { diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index e8eb376a..ef948d3e 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Записати {filepath}. Тему Hyprluna потрібно встановити та активувати вручну.", - "description-missing": "Потрібна установка {app}" + "description-missing": "Клієнт Code не виявлено. Встановіть VSCode або VSCodium." }, "description": "Оформлення окремих програм.", "discord": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index c72c6728..8f16bd51 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "写入 {filepath}。Hyprluna 主题需要手动安装和激活。", - "description-missing": "需要安装 {app}" + "description-missing": "未检测到 Code 客户端。请安装 VSCode 或 VSCodium。" }, "description": "应用程序特定主题。", "discord": { diff --git a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml index d11e6e68..dc8e59df 100644 --- a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml @@ -740,19 +740,48 @@ ColumnLayout { } } + // Code clients - single toggle with dynamic description NCheckbox { + id: codeToggle label: "Code" - description: ProgramCheckerService.codeAvailable ? I18n.tr("settings.color-scheme.templates.programs.code.description", { - "filepath": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" - }) : I18n.tr("settings.color-scheme.templates.programs.code.description-missing", { - "app": "code" - }) - checked: Settings.data.templates.code - enabled: ProgramCheckerService.codeAvailable - opacity: ProgramCheckerService.codeAvailable ? 1.0 : 0.6 + description: { + if (ProgramCheckerService.availableCodeClients.length === 0) { + return I18n.tr("settings.color-scheme.templates.programs.code.description-missing") + } else { + // Show detected clients + var clientInfo = [] + for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { + var client = ProgramCheckerService.availableCodeClients[i] + // Capitalize first letter and format nicely + var clientName = client.name === "code" ? "VSCode" : "VSCodium" + clientInfo.push(clientName) + } + return "Detected: " + clientInfo.join(", ") + } + } + Layout.fillWidth: true + Layout.preferredWidth: -1 + checked: { + // Check if any Code client template is enabled + var anyEnabled = false + for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { + var client = ProgramCheckerService.availableCodeClients[i] + if (Settings.data.templates["code_" + client.name]) { + anyEnabled = true + break + } + } + return anyEnabled + } + enabled: ProgramCheckerService.availableCodeClients.length > 0 + opacity: ProgramCheckerService.availableCodeClients.length > 0 ? 1.0 : 0.6 onToggled: checked => { - if (ProgramCheckerService.codeAvailable) { - Settings.data.templates.code = checked + // Enable/disable all detected Code clients + for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { + var client = ProgramCheckerService.availableCodeClients[i] + Settings.data.templates["code_" + client.name] = checked + } + if (ProgramCheckerService.availableCodeClients.length > 0) { AppThemeService.generate() } } diff --git a/Services/System/ProgramCheckerService.qml b/Services/System/ProgramCheckerService.qml index 10a2eff0..17dd6983 100644 --- a/Services/System/ProgramCheckerService.qml +++ b/Services/System/ProgramCheckerService.qml @@ -31,6 +31,9 @@ Singleton { // Discord client auto-detection property var availableDiscordClients: [] + // Code client auto-detection + property var availableCodeClients: [] + // Signal emitted when all checks are complete signal checksCompleted @@ -97,6 +100,66 @@ Singleton { stderr: StdioCollector {} } + // Function to detect Code client by checking config directories + function detectCodeClient() { + // Build shell script to check each client + var scriptParts = ["available_clients=\"\";"] + + for (var i = 0; i < TemplateRegistry.codeClients.length; i++) { + var client = TemplateRegistry.codeClients[i] + var clientName = client.name + var configPath = client.configPath + + // Check if the config directory exists + scriptParts.push("if [ -d \"$HOME" + configPath.substring(1) + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;") + } + + scriptParts.push("echo \"$available_clients\"") + + // Use a Process to check directory existence for all clients + codeDetector.command = ["sh", "-c", scriptParts.join(" ")] + codeDetector.running = true + } + + // Process to detect Code client directories + Process { + id: codeDetector + running: false + + onExited: function (exitCode) { + availableCodeClients = [] + + if (exitCode === 0) { + var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) { + return client.length > 0 + }) + + if (detectedClients.length > 0) { + // Build list of available clients + for (var i = 0; i < detectedClients.length; i++) { + var clientName = detectedClients[i] + for (var j = 0; j < TemplateRegistry.codeClients.length; j++) { + var client = TemplateRegistry.codeClients[j] + if (client.name === clientName) { + availableCodeClients.push(client) + break + } + } + } + + Logger.i("ProgramChecker", "Detected Code clients:", detectedClients.join(", ")) + } + } + + if (availableCodeClients.length === 0) { + Logger.d("ProgramChecker", "No Code clients detected") + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + // Programs to check - maps property names to commands readonly property var programsToCheck: ({ "matugenAvailable": ["which", "matugen"], @@ -140,8 +203,9 @@ Singleton { // Check next program or emit completion signal if (root.completedChecks >= root.totalChecks) { - // Run Discord client detection after all checks are complete + // Run Discord and Code client detection after all checks are complete root.detectDiscordClient() + root.detectCodeClient() root.checksCompleted() } else { root.checkNextProgram() diff --git a/Services/Theming/TemplateProcessor.qml b/Services/Theming/TemplateProcessor.qml index 63e059ed..9992bf1b 100644 --- a/Services/Theming/TemplateProcessor.qml +++ b/Services/Theming/TemplateProcessor.qml @@ -114,6 +114,18 @@ Singleton { } }) } + } else if (app.id === "code") { + // Handle Code clients specially + if (Settings.data.templates.code) { + app.clients.forEach(client => { + // Check if this specific client is detected + if (isCodeClientEnabled(client.name)) { + lines.push(`\n[templates.code_${client.name}]`) + lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`) + lines.push(`output_path = "${client.path}"`) + } + }) + } } else { // Handle regular apps if (Settings.data.templates[app.id]) { @@ -140,6 +152,16 @@ Singleton { return false } + function isCodeClientEnabled(clientName) { + // Check ProgramCheckerService to see if client is detected + for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { + if (ProgramCheckerService.availableCodeClients[i].name === clientName) { + return true + } + } + return false + } + function buildMatugenScript(content, wallpaper, mode) { const delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9) const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''") @@ -163,6 +185,10 @@ Singleton { if (Settings.data.templates.discord) { script += processDiscordClients(app, colors, mode, homeDir) } + } else if (app.id === "code") { + if (Settings.data.templates.code) { + script += processCodeClients(app, colors, mode, homeDir) + } } else { if (Settings.data.templates[app.id]) { script += processTemplate(app, colors, mode, homeDir) @@ -198,6 +224,39 @@ Singleton { return script } + function processCodeClients(codeApp, colors, mode, homeDir) { + let script = "" + const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false) + + codeApp.clients.forEach(client => { + if (!isCodeClientEnabled(client.name)) + return + + const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${codeApp.input}` + const outputPath = client.path.replace("~", homeDir) + const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) + + // Extract base config directory for checking + var baseConfigDir = "" + if (client.name === "code") { + baseConfigDir = "~/.vscode".replace("~", homeDir) + } else if (client.name === "codium") { + baseConfigDir = "~/.vscode-oss".replace("~", homeDir) + } + + script += `\n` + script += `if [ -d "${baseConfigDir}" ]; then\n` + script += ` mkdir -p ${outputDir}\n` + script += ` cp '${templatePath}' '${outputPath}'\n` + script += ` ${replaceColorsInFile(outputPath, palette)}` + script += `else\n` + script += ` echo "Code client ${client.name} not found at ${baseConfigDir}, skipping"\n` + script += `fi\n` + }) + + return script + } + function processTemplate(app, colors, mode, homeDir) { const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, app.strict || false) let script = "" diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index ebb67a01..b810f78e 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -142,14 +142,18 @@ Singleton { "path": "~/.config/discord", "requiresThemesFolder": true }] - }, // VSCode with hardcoded path (requirement #5) + }, { "id": "code", "name": "VSCode", "category": "applications", "input": "code.json", - "outputs": [{ + "clients": [{ + "name": "code", "path": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" + }, { + "name": "codium", + "path": "~/.vscode-oss/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" }] }, { "id": "spicetify", @@ -179,6 +183,33 @@ Singleton { return clients } + // Extract Code clients for ProgramCheckerService compatibility + readonly property var codeClients: { + var clients = [] + var codeApp = applications.find(app => app.id === "code") + if (codeApp && codeApp.clients) { + codeApp.clients.forEach(client => { + // Extract base config directory from theme path + var themePath = client.path + var baseConfigDir = "" + if (client.name === "code") { + // For VSCode: ~/.vscode/extensions/... -> ~/.vscode + baseConfigDir = "~/.vscode" + } else if (client.name === "codium") { + // For VSCodium: ~/.vscode-oss/extensions/... -> ~/.vscode-oss + baseConfigDir = "~/.vscode-oss" + } + clients.push({ + "name": client.name, + "configPath": baseConfigDir, + "themePath": themePath, + "requiresThemesFolder": false + }) + }) + } + return clients + } + // Build user templates TOML content function buildUserTemplatesToml() { var lines = [] From 2d99a2c233c713020ab1637ea380c5df35ac9e21 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 14 Nov 2025 07:56:20 +0100 Subject: [PATCH 23/25] Matugen/Code: fix VSCodium support --- Commons/Settings.qml | 3 +++ Modules/Panels/Settings/Tabs/ColorSchemeTab.qml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 43e04798..868b380c 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -439,6 +439,9 @@ Singleton { property bool spicetify: false property bool enableUserTemplates: false + property bool code_code: false + property bool code_codium: false + property bool discord_vesktop: false // To be deleted soon property bool discord_webcord: false // To be deleted soon property bool discord_armcord: false // To be deleted soon diff --git a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml index dc8e59df..ca9c23f8 100644 --- a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml @@ -677,6 +677,8 @@ ColumnLayout { enabled: ProgramCheckerService.availableDiscordClients.length > 0 opacity: ProgramCheckerService.availableDiscordClients.length > 0 ? 1.0 : 0.6 onToggled: checked => { + // Set unified discord property + Settings.data.templates.discord = checked // Enable/disable all detected Discord clients for (var i = 0; i < ProgramCheckerService.availableDiscordClients.length; i++) { var client = ProgramCheckerService.availableDiscordClients[i] @@ -776,6 +778,8 @@ ColumnLayout { enabled: ProgramCheckerService.availableCodeClients.length > 0 opacity: ProgramCheckerService.availableCodeClients.length > 0 ? 1.0 : 0.6 onToggled: checked => { + // Set unified code property + Settings.data.templates.code = checked // Enable/disable all detected Code clients for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { var client = ProgramCheckerService.availableCodeClients[i] From 5bd844ec51c66c85beda5c48d5ac3b381b94dc4c Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 14 Nov 2025 08:18:05 +0100 Subject: [PATCH 24/25] Matugen/Discord: fix Vencord path --- Services/System/ProgramCheckerService.qml | 8 ++++++-- Services/Theming/TemplateRegistry.qml | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Services/System/ProgramCheckerService.qml b/Services/System/ProgramCheckerService.qml index 17dd6983..0d2cd0c8 100644 --- a/Services/System/ProgramCheckerService.qml +++ b/Services/System/ProgramCheckerService.qml @@ -45,12 +45,16 @@ Singleton { for (var i = 0; i < TemplateRegistry.discordClients.length; i++) { var client = TemplateRegistry.discordClients[i] var clientName = client.name + var configPath = client.configPath + + // Use the actual config path from the client, removing ~ prefix + var checkPath = configPath.startsWith("~") ? configPath.substring(2) : configPath.substring(1) // Check if this client requires themes folder to exist if (client.requiresThemesFolder) { - scriptParts.push("if [ -d \"$HOME/.config/" + clientName + "/themes\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;") + scriptParts.push("if [ -d \"$HOME/" + checkPath + "/themes\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;") } else { - scriptParts.push("if [ -d \"$HOME/.config/" + clientName + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;") + scriptParts.push("if [ -d \"$HOME/" + checkPath + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;") } } diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index b810f78e..24da2974 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -139,8 +139,8 @@ Singleton { "requiresThemesFolder": false }, { "name": "vencord", - "path": "~/.config/discord", - "requiresThemesFolder": true + "path": "~/.config/Vencord", + "requiresThemesFolder": false }] }, { From 68e83f4d63187de84d9b33fb3b7cb9c7e6e93dfa Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 14 Nov 2025 08:21:54 +0100 Subject: [PATCH 25/25] Matugen/Discord: fix Vencord again --- Commons/Settings.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 868b380c..d03d3a8b 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -448,6 +448,7 @@ Singleton { property bool discord_equibop: false // To be deleted soon property bool discord_lightcord: false // To be deleted soon property bool discord_dorion: false // To be deleted soon + property bool discord_vencord: false // To be deleted soon } // night light @@ -660,7 +661,7 @@ Singleton { var anyDiscordEnabled = false // Check if any Discord client was enabled - const discordClients = ["discord_vesktop", "discord_webcord", "discord_armcord", "discord_equibop", "discord_lightcord", "discord_dorion"] + const discordClients = ["discord_vesktop", "discord_webcord", "discord_armcord", "discord_equibop", "discord_lightcord", "discord_dorion", "discord_vencord"] for (var i = 0; i < discordClients.length; i++) { if (adapter.templates[discordClients[i]]) {