From 58dde183e08230f213f8e5f505004591cc62ff54 Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Sat, 8 Nov 2025 02:39:58 +0100 Subject: [PATCH 001/107] Add wordwrap to Bluetooth- and WiFi-Panel; some fixes for the German translation --- Assets/Translations/de.json | 36 ++++++++++----------- Modules/Panels/Bluetooth/BluetoothPanel.qml | 25 +++++++++----- Modules/Panels/WiFi/WiFiPanel.qml | 6 ++-- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f82cef4e..e1aa3e21 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -110,8 +110,8 @@ "custom-button": { "browse": "Durchsuchen", "collapse-condition": { - "description": "If the output text matches this value, the button will collapse.", - "label": "Collapse condition" + "description": "Wenn der Ausgabetext diesem Wert entspricht, wird die Schaltfläche eingeklappt.", + "label": "Einklappbedingung" }, "display-command-output": { "description": "Befehl eingeben, der in regelmäßigen Abständen ausgeführt wird. Die erste Zeile seiner Ausgabe wird als Text angezeigt.", @@ -145,7 +145,7 @@ }, "text-stream": { "description": "Gestreamte Zeilen aus dem Befehl werden als Text auf der Schaltfläche angezeigt.", - "label": "Strom" + "label": "Stream" } }, "dialog": { @@ -161,7 +161,7 @@ "lock-keys": { "browse": "Durchsuchen", "show-caps-lock": { - "description": "Caps Lock Status anzeigen.", + "description": "Caps-Lock-Status anzeigen.", "label": "Feststelltaste" }, "show-num-lock": { @@ -796,13 +796,13 @@ "matugen-scheme-type": { "description": { "scheme-content": "Leitet Farben ab, die eng mit dem zugrunde liegenden Bild übereinstimmen.", - "scheme-expressive": "Lebendige Farbpalette mit verspielter Sättigung", - "scheme-fidelity": "Farbtreue Palette, die die Originalfarbtöne beibehält", - "scheme-fruit-salad": "Farbenfroher Mix aus leuchtenden, kontrastreichen Akzenten", + "scheme-expressive": "Lebendige Farbpalette mit verspielter Sättigung.", + "scheme-fidelity": "Farbtreue Palette, die die Originalfarbtöne beibehält.", + "scheme-fruit-salad": "Farbenfroher Mix aus leuchtenden, kontrastreichen Akzenten.", "scheme-monochrome": "Minimale Farbpalette, die auf einem einzigen Farbton basiert.", "scheme-neutral": "Gedämpfte Farbpalette mit dezenten und beruhigenden Tönen.", - "scheme-rainbow": "Vielfältige Palette, die das gesamte Spektrum abdeckt", - "scheme-tonal-spot": "Ausgewogene Farbpalette mit gezielten Akzenten" + "scheme-rainbow": "Vielfältige Palette, die das gesamte Spektrum abdeckt.", + "scheme-tonal-spot": "Ausgewogene Farbpalette mit gezielten Akzenten." }, "label": "Matugen-Schema-Typ" }, @@ -849,7 +849,7 @@ }, "programs": { "code": { - "description": "Schreibe {Dateipfad}. Das Hyprluna-Theme muss manuell installiert und aktiviert werden.", + "description": "Schreibe {Dateipfad}. Das Hyprluna-Theme muss manuell installiert und aktiviert werden", "description-missing": "Benötigt die Installation von {app}" }, "description": "Anwendungsspezifisches Theming.", @@ -871,7 +871,7 @@ "description-missing": "Erfordert die Installation von {app}" }, "walker": { - "description": "Schreibt {filepath} und setzt das Theme auf noctalia", + "description": "Schreibt {filepath} und setzt das Theme auf Noctalia", "description-missing": "Erfordert die Installation von {app}" } }, @@ -899,7 +899,7 @@ }, "label": "Terminal", "wezterm": { - "description": "Schreibe {Dateipfad} und lade neu.", + "description": "Schreibe {Dateipfad} und lade neu", "description-missing": "Erfordert die Installation von {app}" } }, @@ -942,7 +942,7 @@ "label": "Befehl" }, "enable-on-state-logic": { - "description": "Aktiviert ein zweites Symbol und 'aktiv' Zustand basierend auf einem Prüfbefehl.", + "description": "Aktiviert ein zweites Symbol und 'Aktiv'-Zustand basierend auf einem Prüfbefehl.", "label": "Aktiv-Zustand-Logik aktivieren" }, "general-tooltip-text": { @@ -966,11 +966,11 @@ "label": "Rechtsklick-Befehl" }, "on-state-command": { - "description": "Befehl, der ausgeführt wird, um zu prüfen, ob sich die Schaltfläche im 'aktiv' Zustand befinden soll. Gibt 0 für aktiv, ungleich 0 für inaktiv zurück.", + "description": "Befehl, der ausgeführt wird, um zu prüfen, ob sich die Schaltfläche im 'Aktiv'-Zustand befinden soll. Gibt 0 für aktiv, ungleich 0 für inaktiv zurück.", "label": "Befehl für Aktiv-Zustand-Prüfung" }, "on-state-icon": { - "description": "Das Symbol für die Schaltfläche, wenn sie sich im 'aktiv' Zustand befindet.", + "description": "Das Symbol für die Schaltfläche, wenn sie sich im 'Aktiv'-Zustand befindet.", "label": "Symbol im Aktiv-Zustand" }, "state-checks": { @@ -1087,7 +1087,7 @@ } }, "enabled": { - "description": "Dock vollständig anzeigen oder ausblenden", + "description": "Dock vollständig anzeigen oder ausblenden.", "label": "Dock aktivieren" }, "monitors": { @@ -1377,7 +1377,7 @@ "label": "Bildschirmanzeige aktivieren" }, "enabled": { - "description": "Benachrichtigungs-Daemon aktivieren oder deaktivieren, erfordert einen Neustart von noctalia-shell.", + "description": "Benachrichtigungs-Daemon aktivieren oder deaktivieren, erfordert einen Neustart von Noctalia Shell.", "label": "Benachrichtigungen aktivieren" }, "location": { @@ -1505,7 +1505,7 @@ }, "dim-desktop": { "description": "Den Desktop abdunkeln, wenn Fenster oder Menüs geöffnet sind.", - "label": "Dimmer Schreibtisch" + "label": "Desktop abdunkeln" }, "panels-attached-to-bar": { "description": "Die Paneele rasten an der Stange und den Bildschirmrändern ein und sorgen so für ein nahtloses Erscheinungsbild mit stilvollen, umgekehrten Ecken.", diff --git a/Modules/Panels/Bluetooth/BluetoothPanel.qml b/Modules/Panels/Bluetooth/BluetoothPanel.qml index db1402ff..6554dfb8 100644 --- a/Modules/Panels/Bluetooth/BluetoothPanel.qml +++ b/Modules/Panels/Bluetooth/BluetoothPanel.qml @@ -13,12 +13,12 @@ SmartPanel { id: root preferredWidth: Math.round(340 * Style.uiScaleRatio) - preferredHeight: 500 * Style.uiScaleRatio + preferredHeight: Math.round(500 * Style.uiScaleRatio) panelContent: Rectangle { color: Color.transparent - property real contentPreferredHeight: Math.min(preferredHeight, Math.max(240, mainColumn.implicitHeight + Style.marginL * 2)) + property real contentPreferredHeight: Math.min(preferredHeight, Math.max(280 * Style.uiScaleRatio, mainColumn.implicitHeight + Style.marginL * 2)) ColumnLayout { id: mainColumn @@ -29,13 +29,12 @@ SmartPanel { // Header NBox { Layout.fillWidth: true - implicitHeight: headerRow.implicitHeight + (Style.marginM * 2) + Layout.preferredHeight: headerRow.implicitHeight + Style.marginM * 2 RowLayout { id: headerRow anchors.fill: parent - anchors.leftMargin: Style.marginM - anchors.rightMargin: Style.marginM + anchors.margins: Style.marginM spacing: Style.marginM NIcon { @@ -90,9 +89,13 @@ SmartPanel { // Center the content within this rectangle ColumnLayout { - anchors.centerIn: parent + anchors.fill: parent spacing: Style.marginM + Item { + Layout.fillHeight: true + } + NIcon { icon: "bluetooth-off" pointSize: 48 @@ -111,7 +114,13 @@ SmartPanel { text: I18n.tr("bluetooth.panel.enable-message") pointSize: Style.fontSizeS color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + Item { + Layout.fillHeight: true } } } @@ -237,4 +246,4 @@ SmartPanel { } } } -} +} \ No newline at end of file diff --git a/Modules/Panels/WiFi/WiFiPanel.qml b/Modules/Panels/WiFi/WiFiPanel.qml index accb5782..cca80cb3 100644 --- a/Modules/Panels/WiFi/WiFiPanel.qml +++ b/Modules/Panels/WiFi/WiFiPanel.qml @@ -150,7 +150,9 @@ SmartPanel { text: I18n.tr("wifi.panel.enable-message") pointSize: Style.fontSizeS color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + wrapMode: Text.WordWrap } Item { @@ -590,4 +592,4 @@ SmartPanel { } } } -} +} \ No newline at end of file From 4a349b3bc22840a73119ec45d33114f06e019483 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 00:31:15 -0500 Subject: [PATCH 002/107] Imports cleanup --- CLAUDE.md | 4 ++-- Commons/Color.qml | 1 - Commons/ThemeIcons.qml | 1 - Commons/Time.qml | 1 - Modules/Background/Background.qml | 1 - Modules/Background/Overview.qml | 3 +-- .../Settings/Bar/WidgetSettings/ActiveWindowSettings.qml | 1 - .../Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml | 1 - .../Panels/Settings/Bar/WidgetSettings/BatterySettings.qml | 1 - .../Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml | 1 - .../Settings/Bar/WidgetSettings/ControlCenterSettings.qml | 1 - .../Settings/Bar/WidgetSettings/CustomButtonSettings.qml | 1 - .../Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml | 1 - .../Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml | 1 - .../Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml | 1 - .../Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml | 1 - .../Bar/WidgetSettings/NotificationHistorySettings.qml | 1 - Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml | 1 - .../Settings/Bar/WidgetSettings/SystemMonitorSettings.qml | 1 - .../Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml | 1 - Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml | 1 - .../Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml | 1 - Modules/Panels/Settings/Tabs/LockScreenTab.qml | 1 - Modules/Panels/Settings/Tabs/ScreenRecorderTab.qml | 1 - Modules/Panels/Settings/Tabs/UserInterfaceTab.qml | 1 - Widgets/NBox.qml | 2 -- Widgets/NBusyIndicator.qml | 1 - Widgets/NCircleStat.qml | 1 - Widgets/NClock.qml | 1 - Widgets/NColorPicker.qml | 1 - Widgets/NDivider.qml | 1 - Widgets/NImageCircled.qml | 1 - Widgets/NImageRounded.qml | 1 - Widgets/NInputAction.qml | 1 - Widgets/NRadioButton.qml | 1 - Widgets/NReorderCheckboxes.qml | 1 - Widgets/NSectionEditor.qml | 1 - Widgets/NSpinBox.qml | 1 - Widgets/NTabBar.qml | 1 - Widgets/NTabButton.qml | 1 - Widgets/NText.qml | 1 - Widgets/NTextInput.qml | 1 - Widgets/NToggle.qml | 1 - Widgets/NValueSlider.qml | 1 - 44 files changed, 3 insertions(+), 47 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 694af961..7f36f0aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -134,8 +134,8 @@ Common components used throughout the shell: - **Input**: NButton, NIconButton, NIconButtonHot, NTextInput, NSlider, NSpinBox, NToggle, NCheckbox, NRadioButton, NComboBox, NSearchableComboBox - **Display**: NLabel, NText, NIcon, NHeader, NImageCached, NImageCircled, NImageRounded - **Dialogs**: NColorPickerDialog, NFilePicker -- **Special**: NContextMenu, NColorPicker, NIconPicker, NCircleStat, NCollapsible, NSectionEditor, NReorderCheckboxes, NDateTimeTokens, NBusyIndicator, NShapedRectangle -- **System**: NFullScreenWindow, BarExclusionZone +- **Special**: NContextMenu, NColorPicker, NIconPicker, NCircleStat, NCollapsible, NSectionEditor, NReorderCheckboxes, NDateTimeTokens, NBusyIndicator +- **System**: MainScreen, BarExclusionZone #### `/Helpers/` - JavaScript Utilities Helper JavaScript modules: diff --git a/Commons/Color.qml b/Commons/Color.qml index 91fde33c..1e358344 100644 --- a/Commons/Color.qml +++ b/Commons/Color.qml @@ -4,7 +4,6 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services /* diff --git a/Commons/ThemeIcons.qml b/Commons/ThemeIcons.qml index aa8c4aea..addede93 100644 --- a/Commons/ThemeIcons.qml +++ b/Commons/ThemeIcons.qml @@ -2,7 +2,6 @@ pragma Singleton import QtQuick import Quickshell -import qs.Services Singleton { id: root diff --git a/Commons/Time.qml b/Commons/Time.qml index c346d1e0..b8245808 100644 --- a/Commons/Time.qml +++ b/Commons/Time.qml @@ -3,7 +3,6 @@ pragma Singleton import Quickshell import QtQuick import qs.Commons -import qs.Services Singleton { id: root diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 8a0f43f3..9c93b40b 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -3,7 +3,6 @@ import Quickshell import Quickshell.Wayland import qs.Commons import qs.Services -import qs.Widgets Variants { id: backgroundVariants diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index 394276a3..a5ee733d 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -1,10 +1,9 @@ import QtQuick -import QtQuick.Effects import Quickshell +import QtQuick.Effects import Quickshell.Wayland import qs.Commons import qs.Services -import qs.Widgets Loader { active: CompositorService.isNiri && Settings.data.wallpaper.enabled && Settings.data.wallpaper.overviewEnabled diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index 514733dd..140fc511 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml index 936260a6..dea5809d 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml index 8377958d..7a5f046c 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml index bc572776..ecf4869c 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml index 66c30c11..043ec23c 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml @@ -4,7 +4,6 @@ import QtQuick.Layouts import Quickshell import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml index 129613ab..5d11c8de 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml @@ -4,7 +4,6 @@ import QtQuick.Layouts import QtQuick.Window import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml index 86ada35a..5b2bc230 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml index 012879cc..dd9b12ef 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml index 5f8c039d..808fe3dc 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml index e62ac1da..3ff2ee58 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml index 61c4e10d..ba6475af 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml index 9679c619..423f0d65 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml index 323bacfd..9ca62f78 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml index 9ea560e8..825d32d5 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml index 3b131d38..09032661 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml index 4beecd41..83d0bbad 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services ColumnLayout { id: root diff --git a/Modules/Panels/Settings/Tabs/LockScreenTab.qml b/Modules/Panels/Settings/Tabs/LockScreenTab.qml index 140ed960..f7b938c5 100644 --- a/Modules/Panels/Settings/Tabs/LockScreenTab.qml +++ b/Modules/Panels/Settings/Tabs/LockScreenTab.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/ScreenRecorderTab.qml b/Modules/Panels/Settings/Tabs/ScreenRecorderTab.qml index d5e45091..580af116 100644 --- a/Modules/Panels/Settings/Tabs/ScreenRecorderTab.qml +++ b/Modules/Panels/Settings/Tabs/ScreenRecorderTab.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/UserInterfaceTab.qml b/Modules/Panels/Settings/Tabs/UserInterfaceTab.qml index 7ccf511c..abe077e4 100644 --- a/Modules/Panels/Settings/Tabs/UserInterfaceTab.qml +++ b/Modules/Panels/Settings/Tabs/UserInterfaceTab.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services import qs.Widgets ColumnLayout { diff --git a/Widgets/NBox.qml b/Widgets/NBox.qml index 10ed50bd..7d775bfb 100644 --- a/Widgets/NBox.qml +++ b/Widgets/NBox.qml @@ -1,7 +1,5 @@ import QtQuick import qs.Commons -import qs.Commons -import qs.Services // Rounded group container using the variant surface color. // To be used in side panels and settings panes to group fields or buttons. diff --git a/Widgets/NBusyIndicator.qml b/Widgets/NBusyIndicator.qml index f65da231..e720debe 100644 --- a/Widgets/NBusyIndicator.qml +++ b/Widgets/NBusyIndicator.qml @@ -1,6 +1,5 @@ import QtQuick import qs.Commons -import qs.Services Item { id: root diff --git a/Widgets/NCircleStat.qml b/Widgets/NCircleStat.qml index 169aa445..9cdb338b 100644 --- a/Widgets/NCircleStat.qml +++ b/Widgets/NCircleStat.qml @@ -1,7 +1,6 @@ import QtQuick import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets // Compact circular statistic display using Layout management diff --git a/Widgets/NClock.qml b/Widgets/NClock.qml index fccc6c0c..5615aa45 100644 --- a/Widgets/NClock.qml +++ b/Widgets/NClock.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services import Quickshell import "../Helpers/ColorsConvert.js" as ColorsConvert diff --git a/Widgets/NColorPicker.qml b/Widgets/NColorPicker.qml index a37bb6cd..d25087c4 100644 --- a/Widgets/NColorPicker.qml +++ b/Widgets/NColorPicker.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets Rectangle { diff --git a/Widgets/NDivider.qml b/Widgets/NDivider.qml index d11e82c7..737807d7 100644 --- a/Widgets/NDivider.qml +++ b/Widgets/NDivider.qml @@ -2,7 +2,6 @@ import QtQuick import Quickshell import Quickshell.Widgets import qs.Commons -import qs.Services Rectangle { width: parent.width diff --git a/Widgets/NImageCircled.qml b/Widgets/NImageCircled.qml index 55ef7568..c97e4440 100644 --- a/Widgets/NImageCircled.qml +++ b/Widgets/NImageCircled.qml @@ -3,7 +3,6 @@ import QtQuick.Effects import Quickshell import Quickshell.Widgets import qs.Commons -import qs.Services Rectangle { id: root diff --git a/Widgets/NImageRounded.qml b/Widgets/NImageRounded.qml index 32987e28..a7a5789f 100644 --- a/Widgets/NImageRounded.qml +++ b/Widgets/NImageRounded.qml @@ -3,7 +3,6 @@ import QtQuick.Effects import Quickshell import Quickshell.Widgets import qs.Commons -import qs.Services Rectangle { id: root diff --git a/Widgets/NInputAction.qml b/Widgets/NInputAction.qml index b4b86879..6fac9618 100644 --- a/Widgets/NInputAction.qml +++ b/Widgets/NInputAction.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services // Input and button row RowLayout { diff --git a/Widgets/NRadioButton.qml b/Widgets/NRadioButton.qml index d61b0719..ea3cacbd 100644 --- a/Widgets/NRadioButton.qml +++ b/Widgets/NRadioButton.qml @@ -1,7 +1,6 @@ import QtQuick import QtQuick.Controls import qs.Commons -import qs.Services import qs.Widgets RadioButton { diff --git a/Widgets/NReorderCheckboxes.qml b/Widgets/NReorderCheckboxes.qml index a3a7f7e8..078c5e7a 100644 --- a/Widgets/NReorderCheckboxes.qml +++ b/Widgets/NReorderCheckboxes.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services Item { id: root diff --git a/Widgets/NSectionEditor.qml b/Widgets/NSectionEditor.qml index 65fe857f..a2ecbedf 100644 --- a/Widgets/NSectionEditor.qml +++ b/Widgets/NSectionEditor.qml @@ -4,7 +4,6 @@ import QtQuick.Effects import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services NBox { id: root diff --git a/Widgets/NSpinBox.qml b/Widgets/NSpinBox.qml index a0fdbc16..0b96cfc4 100644 --- a/Widgets/NSpinBox.qml +++ b/Widgets/NSpinBox.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets RowLayout { diff --git a/Widgets/NTabBar.qml b/Widgets/NTabBar.qml index db218606..5c0259b7 100644 --- a/Widgets/NTabBar.qml +++ b/Widgets/NTabBar.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets Rectangle { diff --git a/Widgets/NTabButton.qml b/Widgets/NTabButton.qml index d9415836..a1def44b 100644 --- a/Widgets/NTabButton.qml +++ b/Widgets/NTabButton.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets Rectangle { diff --git a/Widgets/NText.qml b/Widgets/NText.qml index 4a9060ed..57c20ed0 100644 --- a/Widgets/NText.qml +++ b/Widgets/NText.qml @@ -1,7 +1,6 @@ import QtQuick import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets Text { diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index f9ba4f05..b2a5e0ea 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services ColumnLayout { id: root diff --git a/Widgets/NToggle.qml b/Widgets/NToggle.qml index 4cdc91f9..82c54711 100644 --- a/Widgets/NToggle.qml +++ b/Widgets/NToggle.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services RowLayout { id: root diff --git a/Widgets/NValueSlider.qml b/Widgets/NValueSlider.qml index 19cb8e4c..62006780 100644 --- a/Widgets/NValueSlider.qml +++ b/Widgets/NValueSlider.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets RowLayout { From 00df8dcd3331400679d6cb981c40d8435f0c3190 Mon Sep 17 00:00:00 2001 From: notiant <238434866+notiant@users.noreply.github.com> Date: Sat, 8 Nov 2025 08:24:36 +0100 Subject: [PATCH 003/107] make BluetoothPanel height adaptable --- Modules/Panels/Bluetooth/BluetoothPanel.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Panels/Bluetooth/BluetoothPanel.qml b/Modules/Panels/Bluetooth/BluetoothPanel.qml index 6554dfb8..210c9793 100644 --- a/Modules/Panels/Bluetooth/BluetoothPanel.qml +++ b/Modules/Panels/Bluetooth/BluetoothPanel.qml @@ -18,7 +18,7 @@ SmartPanel { panelContent: Rectangle { color: Color.transparent - property real contentPreferredHeight: Math.min(preferredHeight, Math.max(280 * Style.uiScaleRatio, mainColumn.implicitHeight + Style.marginL * 2)) + property real contentPreferredHeight: !(BluetoothService.adapter && BluetoothService.adapter.enabled) ? Math.min(preferredHeight, Math.max(280 * Style.uiScaleRatio, mainColumn.implicitHeight + Style.marginL * 2)) : (mainColumn.implicitHeight + Style.marginL * 2) ColumnLayout { id: mainColumn @@ -246,4 +246,4 @@ SmartPanel { } } } -} \ No newline at end of file +} From 9458fc1fc86f38994a209f77671d82c9af23d9d7 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 08:54:34 +0100 Subject: [PATCH 004/107] TrayMenuPanel: disable dragging --- Modules/Panels/Tray/TrayMenuPanel.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Panels/Tray/TrayMenuPanel.qml b/Modules/Panels/Tray/TrayMenuPanel.qml index e842f161..c55115c0 100644 --- a/Modules/Panels/Tray/TrayMenuPanel.qml +++ b/Modules/Panels/Tray/TrayMenuPanel.qml @@ -103,7 +103,7 @@ SmartPanel { id: subMenuFlickable anchors.fill: parent contentHeight: subMenuColumnLayout.implicitHeight - interactive: true + interactive: false z: 0 ColumnLayout { @@ -312,7 +312,7 @@ SmartPanel { Layout.preferredWidth: root.menuWidth Layout.fillHeight: true contentHeight: mainColumnLayout.implicitHeight - interactive: true + interactive: false visible: !(content.activeSubMenu !== null && content.inPlaceSubmenu) ColumnLayout { From f8131c9a144d5be33ef87c865a08df676c4fea4d Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 09:00:42 +0100 Subject: [PATCH 005/107] Tray: use getPillDirection logic to place chevron to the correct position based on bar section --- Modules/Bar/Widgets/Tray.qml | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 11347224..7971eac6 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -198,6 +198,36 @@ Rectangle { spacing: Style.marginXS flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight + // Drawer opener (before items if opposite direction) + NIconButton { + id: chevronIconBefore + visible: root.drawerEnabled && dropdownItems.length > 0 && BarService.getPillDirection(root) + tooltipText: I18n.tr("tooltips.open-tray-dropdown") + tooltipDirection: BarService.getTooltipDirection() + 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 + colorBorder: Color.transparent + colorBorderHover: Color.transparent + icon: { + switch (barPosition) { + case "bottom": + return "caret-up" + case "left": + return "caret-right" + case "right": + return "caret-left" + case "top": + default: + return "caret-down" + } + } + onClicked: toggleDrawer(this) + onRightClicked: toggleDrawer(this) + } + Repeater { id: repeater model: root.filteredItems @@ -311,10 +341,10 @@ Rectangle { } } - // Drawer opener + // Drawer opener (after items if normal direction) NIconButton { - id: chevronIcon - visible: root.drawerEnabled && dropdownItems.length > 0 + id: chevronIconAfter + visible: root.drawerEnabled && dropdownItems.length > 0 && !BarService.getPillDirection(root) tooltipText: I18n.tr("tooltips.open-tray-dropdown") tooltipDirection: BarService.getTooltipDirection() density: Settings.data.bar.density @@ -337,12 +367,8 @@ Rectangle { return "caret-down" } } - onClicked: { - toggleDrawer(this) - } - onRightClicked: { - toggleDrawer(this) - } + onClicked: toggleDrawer(this) + onRightClicked: toggleDrawer(this) } } } From cd91b00b40e4455705123b13d2f6246cafa3d8ca Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 09:04:30 +0100 Subject: [PATCH 006/107] NotificationService: dedupe identical notifications --- Services/NotificationService.qml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index c943d875..46635e0c 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -118,6 +118,15 @@ Singleton { } } + // Helper function to generate content-based ID for deduplication (without timestamp) + function getContentId(summary, body, appName) { + return Checksum.sha256(JSON.stringify({ + "summary": summary || "", + "body": body || "", + "app": appName || "" + })) + } + // Main handler function handleNotification(notification) { const data = createData(notification) @@ -126,6 +135,26 @@ Singleton { if (Settings.data.notifications?.doNotDisturb || PowerProfileService.noctaliaPerformanceMode) return + // Check for duplicate notification (same content) + const normalizedAppName = getAppName(notification.appName || notification.desktopEntry || "") + const contentId = getContentId(notification.summary || "", notification.body || "", normalizedAppName) + let duplicateIndex = -1 + for (var i = 0; i < activeList.count; i++) { + const existing = activeList.get(i) + const existingContentId = getContentId(existing.summary || "", existing.body || "", existing.appName || "") + if (existingContentId === contentId) { + duplicateIndex = i + break + } + } + + // If duplicate found, remove the old one + if (duplicateIndex >= 0) { + const oldNotif = activeList.get(duplicateIndex) + activeMap[oldNotif.id]?.dismiss() + removeActive(oldNotif.id) + } + activeMap[data.id] = notification notification.tracked = true notification.closed.connect(() => removeActive(data.id)) From 3fafc57f365a07de338cf45a68477185a32a3e62 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 09:05:47 +0100 Subject: [PATCH 007/107] ControlCenter: middle mouse button opens launcher --- Modules/Bar/Widgets/ControlCenter.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index ae009ee1..d703a05f 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -54,6 +54,7 @@ NIconButton { } } onRightClicked: PanelService.getPanel("settingsPanel", screen)?.toggle() + onMiddleClicked: PanelService.getPanel("launcherPanel", screen)?.toggle() IconImage { id: customOrDistroLogo From 061b92697cbcc637c96d04b0e89bf9cb94dee843 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 09:13:48 +0100 Subject: [PATCH 008/107] NoctaliaPerformance: add performance toggle (disables shadows & animations) --- Assets/Translations/de.json | 2 ++ Assets/Translations/en.json | 2 ++ Assets/Translations/es.json | 2 ++ Assets/Translations/fr.json | 2 ++ Assets/Translations/pt.json | 2 ++ Assets/Translations/tr.json | 2 ++ Assets/Translations/uk-UA.json | 2 ++ Assets/Translations/zh-CN.json | 2 ++ Modules/Bar/Widgets/NoctaliaPerformance.qml | 30 +++++++++++++++++++++ Services/BarWidgetRegistry.qml | 4 +++ Services/PowerProfileService.qml | 4 +-- 11 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 Modules/Bar/Widgets/NoctaliaPerformance.qml diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f82cef4e..7b336459 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1798,6 +1798,8 @@ "night-light-enabled": "Nachtlicht ist aktiviert.\nLinksklick zum Wechseln des Modus.\nRechtsklick für Einstellungen.", "night-light-forced": "Nachtlicht ist erzwungen.\nLinksklick zum Wechseln des Modus.\nRechtsklick für Einstellungen.", "night-light-not-installed": "Nachtlicht ist nicht verfügbar.\nwlsunset ist nicht installiert.", + "noctalia-performance-disabled": "Noctalia-Leistungsmodus ist deaktiviert.\nLinksklick zum Aktivieren.", + "noctalia-performance-enabled": "Noctalia-Leistungsmodus ist aktiviert.\nLinksklick zum Deaktivieren.", "open-control-center": "Kontrollzentrum öffnen", "open-notification-history-disable-dnd": "Benachrichtigungsverlauf öffnen\nRechtsklick um 'Nicht stören' zu deaktivieren.", "open-notification-history-enable-dnd": "Benachrichtigungsverlauf öffnen\nRechtsklick um 'Nicht stören' zu aktivieren.", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index ff6b949f..518eefb9 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1798,6 +1798,8 @@ "night-light-enabled": "Night light is enabled.\nLeft click to cycle mode.\nRight click to access settings.", "night-light-forced": "Night light is forced.\nLeft click to cycle mode.\nRight click to access settings.", "night-light-not-installed": "Night light is not available.\nwlsunset is not installed.", + "noctalia-performance-disabled": "Noctalia performance mode is disabled.\nLeft click to enable.", + "noctalia-performance-enabled": "Noctalia performance mode is enabled.\nLeft click to disable.", "open-control-center": "Open control center", "open-notification-history-disable-dnd": "Open notification history\nRight-click to disable \"Do not disturb\".", "open-notification-history-enable-dnd": "Open notification history\nRight-click to enable \"Do not disturb\".", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 147d9b3f..9399c515 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1798,6 +1798,8 @@ "night-light-enabled": "Luz nocturna activada.\nClic izquierdo para cambiar de modo.\nClic derecho para acceder a la configuración.", "night-light-forced": "Luz nocturna forzada.\nClic izquierdo para cambiar de modo.\nClic derecho para acceder a la configuración.", "night-light-not-installed": "Luz nocturna no disponible.\nwlsunset no está instalado.", + "noctalia-performance-disabled": "El modo de rendimiento de Noctalia está desactivado.\nClic izquierdo para activar.", + "noctalia-performance-enabled": "El modo de rendimiento de Noctalia está activado.\nClic izquierdo para desactivar.", "open-control-center": "Abrir el centro de control", "open-notification-history-disable-dnd": "Abrir historial de notificaciones\nClic derecho para desactivar \"No molestar\".", "open-notification-history-enable-dnd": "Abrir historial de notificaciones\nClic derecho para activar \"No molestar\".", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8409bd0f..1eff9cdc 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1798,6 +1798,8 @@ "night-light-enabled": "L'éclairage nocturne est activé.\nClic gauche pour changer de mode.\nClic droit pour accéder aux paramètres.", "night-light-forced": "L'éclairage nocturne est forcé.\nClic gauche pour changer de mode.\nClic droit pour accéder aux paramètres.", "night-light-not-installed": "L'éclairage nocturne n'est pas disponible.\nwlsunset n'est pas installé.", + "noctalia-performance-disabled": "Le mode performance Noctalia est désactivé.\nClic gauche pour activer.", + "noctalia-performance-enabled": "Le mode performance Noctalia est activé.\nClic gauche pour désactiver.", "open-control-center": "Ouvrir le centre de contrôle", "open-notification-history-disable-dnd": "Ouvrir l'historique des notifications\nClic droit pour désactiver \"Ne pas déranger\".", "open-notification-history-enable-dnd": "Ouvrir l'historique des notifications\nClic droit pour activer \"Ne pas déranger\".", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index dab199b6..5fbcd5c5 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1798,6 +1798,8 @@ "night-light-enabled": "Luz noturna ativada.\nClique esquerdo para alternar o modo.\nClique direito para acessar as configurações.", "night-light-forced": "Luz noturna forçada.\nClique esquerdo para alternar o modo.\nClique direito para acessar as configurações.", "night-light-not-installed": "Luz noturna não disponível.\nwlsunset não está instalado.", + "noctalia-performance-disabled": "O modo de desempenho Noctalia está desativado.\nClique esquerdo para ativar.", + "noctalia-performance-enabled": "O modo de desempenho Noctalia está ativado.\nClique esquerdo para desativar.", "open-control-center": "Abrir central de controle", "open-notification-history-disable-dnd": "Abrir histórico de notificações\nClique direito para desativar \"Não perturbe\".", "open-notification-history-enable-dnd": "Abrir histórico de notificações\nClique direito para ativar \"Não perturbe\".", diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 749063e9..7f3dd309 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -1798,6 +1798,8 @@ "night-light-enabled": "Gece ışığı etkin.\nMod arasında geçiş yapmak için sol tık.\nAyarlara erişmek için sağ tık.", "night-light-forced": "Gece ışığı zorla.\nMod arasında geçiş yapmak için sol tık.\nAyarlara erişmek için sağ tık.", "night-light-not-installed": "Gece ışığı mevcut değil.\nwlsunset yüklü değil.", + "noctalia-performance-disabled": "Noctalia performans modu devre dışı.\nEtkinleştirmek için sol tıklayın.", + "noctalia-performance-enabled": "Noctalia performans modu etkin.\nDevre dışı bırakmak için sol tıklayın.", "open-control-center": "Kontrol merkezini aç", "open-notification-history-disable-dnd": "Bildirim geçmişini aç\n\"Rahatsız etmeyi\" devre dışı bırakmak için sağ tık.", "open-notification-history-enable-dnd": "Bildirim geçmişini aç\n\"Rahatsız etmeyi\" etkinleştirmek için sağ tık.", diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index f32018c2..8f5e9163 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -1798,6 +1798,8 @@ "night-light-enabled": "Нічне світло увімкнено.\nЛівий клік для циклічного режиму.\nПравий клік для доступу до налаштувань.", "night-light-forced": "Нічне світло примусово.\nЛівий клік для циклічного режиму.\nПравий клік для доступу до налаштувань.", "night-light-not-installed": "Нічне світло недоступне.\nwlsunset не встановлено.", + "noctalia-performance-disabled": "Режим продуктивності Noctalia вимкнено.\nЛівий клік для увімкнення.", + "noctalia-performance-enabled": "Режим продуктивності Noctalia увімкнено.\nЛівий клік для вимкнення.", "open-control-center": "Відкрити центр керування", "open-notification-history-disable-dnd": "Відкрити історію сповіщень\nПравий клік для вимкнення \"Не турбувати\".", "open-notification-history-enable-dnd": "Відкрити історію сповіщень\nПравий клік для увімкнення \"Не турбувати\".", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 4c6c57d6..6ab2bbef 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1798,6 +1798,8 @@ "night-light-enabled": "夜间模式已启用。\n左键点击循环模式。\n右键点击访问设置。", "night-light-forced": "夜间模式已强制启用。\n左键点击循环模式。\n右键点击访问设置。", "night-light-not-installed": "夜间模式不可用。\nwlsunset 未安装。", + "noctalia-performance-disabled": "Noctalia 性能模式已禁用。\n左键点击以启用。", + "noctalia-performance-enabled": "Noctalia 性能模式已启用。\n左键点击以禁用。", "open-control-center": "打开控制中心", "open-notification-history-disable-dnd": "打开通知历史记录\n右键点击禁用\"勿扰模式\"。", "open-notification-history-enable-dnd": "打开通知历史记录\n右键点击启用\"勿扰模式\"。", diff --git a/Modules/Bar/Widgets/NoctaliaPerformance.qml b/Modules/Bar/Widgets/NoctaliaPerformance.qml new file mode 100644 index 00000000..443832fc --- /dev/null +++ b/Modules/Bar/Widgets/NoctaliaPerformance.qml @@ -0,0 +1,30 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Modules.Panels.Settings +import qs.Services +import qs.Widgets + +NIconButton { + id: root + + property ShellScreen screen + + density: Settings.data.bar.density + baseSize: Style.capsuleHeight + applyUiScale: false + colorBg: PowerProfileService.noctaliaPerformanceMode ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) + colorFg: PowerProfileService.noctaliaPerformanceMode ? Color.mOnPrimary : Color.mOnSurface + colorBorder: Color.transparent + colorBorderHover: Color.transparent + + icon: PowerProfileService.noctaliaPerformanceMode ? "rocket" : "rocket-off" + tooltipText: PowerProfileService.noctaliaPerformanceMode ? I18n.tr("tooltips.noctalia-performance-enabled") : I18n.tr("tooltips.noctalia-performance-disabled") + tooltipDirection: BarService.getTooltipDirection() + onClicked: { + PowerProfileService.toggleNoctaliaPerformance() + } +} diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index 69d30d39..30cf715b 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -25,6 +25,7 @@ Singleton { "MediaMini": mediaMiniComponent, "Microphone": microphoneComponent, "NightLight": nightLightComponent, + "NoctaliaPerformance": noctaliaPerformanceComponent, "NotificationHistory": notificationHistoryComponent, "PowerProfile": powerProfileComponent, "ScreenRecorder": screenRecorderComponent, @@ -218,6 +219,9 @@ Singleton { property Component nightLightComponent: Component { NightLight {} } + property Component noctaliaPerformanceComponent: Component { + NoctaliaPerformance {} + } property Component notificationHistoryComponent: Component { NotificationHistory {} } diff --git a/Services/PowerProfileService.qml b/Services/PowerProfileService.qml index 160e5213..cb778628 100644 --- a/Services/PowerProfileService.qml +++ b/Services/PowerProfileService.qml @@ -112,9 +112,9 @@ Singleton { onNoctaliaPerformanceModeChanged: { if (noctaliaPerformanceMode) { - ToastService.showNotice(I18n.tr("toast.noctalia-performance.label"), I18n.tr("toast.noctalia-performance.enabled"), "seedling") + ToastService.showNotice(I18n.tr("toast.noctalia-performance.label"), I18n.tr("toast.noctalia-performance.enabled"), "rocket") } else { - ToastService.showNotice(I18n.tr("toast.noctalia-performance.label"), I18n.tr("toast.noctalia-performance.disabled"), "seedling-off") + ToastService.showNotice(I18n.tr("toast.noctalia-performance.label"), I18n.tr("toast.noctalia-performance.disabled"), "rocket-off") } } } From 79c9ad5ccd0502829f33a9465c1fbdcd8ebd403e Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 09:59:00 +0100 Subject: [PATCH 009/107] Matugen/Discord: make textinput placeholder easier to read --- Assets/MatugenTemplates/vesktop.css | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Assets/MatugenTemplates/vesktop.css b/Assets/MatugenTemplates/vesktop.css index a4b7da11..22737948 100644 --- a/Assets/MatugenTemplates/vesktop.css +++ b/Assets/MatugenTemplates/vesktop.css @@ -170,6 +170,27 @@ h3[class*="postTitleText"]:hover { opacity: 0.85 !important; } +/* Improve message input/editor text readability */ +[class*="editor"] [class*="markup"], +[class*="slateTextArea"] [class*="markup"], +[class*="slateTextArea"], +[role="textbox"][class*="markup"], +[role="textbox"][class*="editor"], +[class*="editor"] [class*="slateTextArea"], +[data-slate-editor="true"] { + color: var(--text-3) !important; + opacity: 0.4 !important; + line-height: 1.4 !important; +} + +/* Empty/placeholder text in message input */ +[class*="emptyText"], +[class*="emptyText"] span, +[data-slate-zero-width] { + color: var(--text-4) !important; + opacity: 0.7 !important; +} + [class*="messageContent"][class*="deleted"], [class*="text-sm"][class*="messageContent"][class*="deleted"], [data-text-variant*="text-sm"][class*="messageContent"][class*="deleted"] { From 41f13f958b95f05e3414b34512a377eb9ee39c00 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 10:02:28 +0100 Subject: [PATCH 010/107] Matugen/Discord: make textinput placeholder easier to read (v2) --- Assets/MatugenTemplates/vesktop.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/MatugenTemplates/vesktop.css b/Assets/MatugenTemplates/vesktop.css index 22737948..82ea111f 100644 --- a/Assets/MatugenTemplates/vesktop.css +++ b/Assets/MatugenTemplates/vesktop.css @@ -179,7 +179,7 @@ h3[class*="postTitleText"]:hover { [class*="editor"] [class*="slateTextArea"], [data-slate-editor="true"] { color: var(--text-3) !important; - opacity: 0.4 !important; + opacity: 0.9 !important; line-height: 1.4 !important; } From 58d5f37b020e00bd36a7f1b171b3891208fc7b17 Mon Sep 17 00:00:00 2001 From: wxlyyy <1556588440@qq.com> Date: Fri, 7 Nov 2025 16:12:33 +0800 Subject: [PATCH 011/107] Nix: cleanup package --- nix/package.nix | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/nix/package.nix b/nix/package.nix index a5d07917..cacec580 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -1,25 +1,16 @@ { version ? "dirty", lib, - stdenv, + stdenvNoCC, # build - gcc, qt6, quickshell, - xkeyboard_config, # runtime deps - bash, - bluez, brightnessctl, cava, cliphist, - coreutils, ddcutil, - file, - findutils, - libnotify, matugen, - networkmanager, wlsunset, wl-clipboard, gpu-screen-recorder, # optional @@ -33,6 +24,7 @@ filter = path: type: !(builtins.any (prefix: lib.path.hasPrefix (../. + prefix) (/. + path)) [ /.github + /.gitignore /Assets/Screenshots /Assets/Wallpaper /Bin/dev @@ -41,60 +33,55 @@ /README.md /flake.nix /flake.lock + /shell.nix + /lefthook.yml + /CLAUDE.md ]); }; runtimeDeps = [ - bash - bluez brightnessctl cava cliphist - coreutils ddcutil - file - findutils - libnotify matugen - networkmanager wlsunset wl-clipboard ] - ++ lib.optionals (stdenv.hostPlatform.isx86_64) [gpu-screen-recorder]; + ++ lib.optionals (stdenvNoCC.hostPlatform.system == "x86_64-linux") [ + gpu-screen-recorder + ]; - fontconfig = makeFontsConf { + fontsConf = makeFontsConf { fontDirectories = [ roboto inter-nerdfont ]; }; in - stdenv.mkDerivation { + stdenvNoCC.mkDerivation { pname = "noctalia-shell"; inherit version src; nativeBuildInputs = [ - gcc qt6.wrapQtAppsHook ]; + buildInputs = [ - quickshell - xkeyboard_config qt6.qtbase ]; - propagatedBuildInputs = runtimeDeps; installPhase = '' mkdir -p $out/share/noctalia-shell $out/bin - cp -r ./* $out/share/noctalia-shell - cp ${quickshell}/bin/qs $out/bin/noctalia-shell + cp -r . $out/share/noctalia-shell + ln -s ${quickshell}/bin/qs $out/bin/noctalia-shell ''; preFixup = '' qtWrapperArgs+=( --prefix PATH : ${lib.makeBinPath runtimeDeps} - --set FONTCONFIG_FILE ${fontconfig} + --set FONTCONFIG_FILE ${fontsConf} --add-flags "-p $out/share/noctalia-shell" ) ''; From 2dd4c9ff6e81256335fc74eae3f067095fa9f8b1 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 12:21:11 +0100 Subject: [PATCH 012/107] README: add JustCurtis to the supporter list <3 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f096654d..1b7bec4c 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ While all donations are greatly appreciated, they are completely voluntary. * MrDowntempo * Tempus Thales * Raine +* JustCurtis --- From e09ed5b0602b6123c2e5adce51c1b650b6568a90 Mon Sep 17 00:00:00 2001 From: Ala Alkhafaji <3akevdev@gmail.com> Date: Sat, 8 Nov 2025 12:44:58 +0100 Subject: [PATCH 013/107] add IPC calls to enable/disable Do Not Disturb --- Services/IPCService.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Services/IPCService.qml b/Services/IPCService.qml index a6cac693..406cfb07 100644 --- a/Services/IPCService.qml +++ b/Services/IPCService.qml @@ -57,6 +57,12 @@ Item { function toggleDND() { Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb } + function enableDND() { + Settings.data.notifications.doNotDisturb = true + } + function disableDND() { + Settings.data.notifications.doNotDisturb = false + } function clear() { NotificationService.clearHistory() } From 2b04f8a9102702fe98d4acbe02421ea147ad2419 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 14:06:16 +0100 Subject: [PATCH 014/107] NotificationService: implement (#673) replacing functionality for notifications --- Bin/test-notification-replace.sh | 23 ++ Services/NotificationService.qml | 381 ++++++++++++++++++++----------- 2 files changed, 276 insertions(+), 128 deletions(-) create mode 100755 Bin/test-notification-replace.sh diff --git a/Bin/test-notification-replace.sh b/Bin/test-notification-replace.sh new file mode 100755 index 00000000..50fcf1ae --- /dev/null +++ b/Bin/test-notification-replace.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Test script for notification replacement functionality + +echo "Testing notification replacement..." +echo "" + +# Send initial notification and capture the ID +echo "Step 1: Sending initial notification 'asdf'..." +NOTIF_ID=$(notify-send -p "asdf") +echo "Notification ID: $NOTIF_ID" +echo "" + +# Wait a moment for the notification to appear +sleep 1 + +# Replace the notification +echo "Step 2: Replacing notification $NOTIF_ID with 'test'..." +notify-send -r "$NOTIF_ID" -p "test" +echo "" + +echo "The notification should now show 'test' instead of 'asdf'." +echo "If it still shows 'asdf', the replacement is not working." + diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index 46635e0c..706e55fd 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -26,12 +26,10 @@ Singleton { property ListModel historyList: ListModel {} // Internal state - property var activeMap: ({}) + property var activeNotifications: ({}) // Maps internal ID to {notification, watcher, metadata} + property var quickshellIdToInternalId: ({}) property var imageQueue: [] - // Performance optimization: Track notification metadata separately - property var notificationMetadata: ({}) // Stores timestamp and duration for each notification - PanelWindow { implicitHeight: 1 implicitWidth: 1 @@ -76,7 +74,7 @@ Singleton { } } - // Notification server - only created when notifications are enabled + // Notification server property var notificationServerLoader: null Component { @@ -89,14 +87,43 @@ Singleton { } } + Component { + id: notificationWatcherComponent + Connections { + property var targetNotification + property var targetDataId + target: targetNotification + + function onSummaryChanged() { + updateNotificationFromObject(targetDataId) + } + function onBodyChanged() { + updateNotificationFromObject(targetDataId) + } + function onAppNameChanged() { + updateNotificationFromObject(targetDataId) + } + function onUrgencyChanged() { + updateNotificationFromObject(targetDataId) + } + function onAppIconChanged() { + updateNotificationFromObject(targetDataId) + } + function onImageChanged() { + updateNotificationFromObject(targetDataId) + } + function onActionsChanged() { + updateNotificationFromObject(targetDataId) + } + } + } + function updateNotificationServer() { - // Destroy existing server if it exists if (notificationServerLoader) { notificationServerLoader.destroy() notificationServerLoader = null } - // Create server only if enabled if (Settings.isLoaded && Settings.data.notifications.enabled !== false) { notificationServerLoader = notificationServerComponent.createObject(root) } @@ -118,7 +145,7 @@ Singleton { } } - // Helper function to generate content-based ID for deduplication (without timestamp) + // Helper function to generate content-based ID for deduplication function getContentId(summary, body, appName) { return Checksum.sha256(JSON.stringify({ "summary": summary || "", @@ -129,69 +156,126 @@ Singleton { // Main handler function handleNotification(notification) { + const quickshellId = notification.id const data = createData(notification) addToHistory(data) if (Settings.data.notifications?.doNotDisturb || PowerProfileService.noctaliaPerformanceMode) return - // Check for duplicate notification (same content) - const normalizedAppName = getAppName(notification.appName || notification.desktopEntry || "") - const contentId = getContentId(notification.summary || "", notification.body || "", normalizedAppName) - let duplicateIndex = -1 - for (var i = 0; i < activeList.count; i++) { - const existing = activeList.get(i) - const existingContentId = getContentId(existing.summary || "", existing.body || "", existing.appName || "") - if (existingContentId === contentId) { - duplicateIndex = i - break - } + // Check if this is a replacement notification + const existingInternalId = quickshellIdToInternalId[quickshellId] + if (existingInternalId && activeNotifications[existingInternalId]) { + updateExistingNotification(existingInternalId, notification, data) + return } - // If duplicate found, remove the old one - if (duplicateIndex >= 0) { - const oldNotif = activeList.get(duplicateIndex) - activeMap[oldNotif.id]?.dismiss() - removeActive(oldNotif.id) + // Check for duplicate content + const duplicateId = findDuplicateNotification(data) + if (duplicateId) { + removeNotification(duplicateId) } - activeMap[data.id] = notification + // Add new notification + addNewNotification(quickshellId, notification, data) + } + + function updateExistingNotification(internalId, notification, data) { + const index = findNotificationIndex(internalId) + if (index < 0) + return + + const existing = activeList.get(index) + const oldTimestamp = existing.timestamp + const oldProgress = existing.progress + + // Update properties (keeping original timestamp and progress) + activeList.setProperty(index, "summary", data.summary) + activeList.setProperty(index, "body", data.body) + activeList.setProperty(index, "appName", data.appName) + activeList.setProperty(index, "urgency", data.urgency) + activeList.setProperty(index, "expireTimeout", data.expireTimeout) + activeList.setProperty(index, "originalImage", data.originalImage) + activeList.setProperty(index, "cachedImage", data.cachedImage) + activeList.setProperty(index, "actionsJson", data.actionsJson) + activeList.setProperty(index, "timestamp", oldTimestamp) + activeList.setProperty(index, "progress", oldProgress) + + // Update stored notification object + const notifData = activeNotifications[internalId] + notifData.notification = notification notification.tracked = true - notification.closed.connect(() => removeActive(data.id)) + notification.closed.connect(() => removeNotification(internalId)) - // Store metadata for efficient progress calculation - const durations = [Settings.data.notifications?.lowUrgencyDuration * 1000 || 3000, Settings.data.notifications?.normalUrgencyDuration * 1000 || 8000, Settings.data.notifications?.criticalUrgencyDuration * 1000 || 15000] + // Update metadata + notifData.metadata.urgency = data.urgency + notifData.metadata.duration = calculateDuration(data) + } - let expire = 0 - if (Settings.data.notifications?.respectExpireTimeout) { - if (data.expireTimeout === 0) { - expire = -1 // Never expire - } else if (data.expireTimeout > 0) { - expire = data.expireTimeout - } else { - expire = durations[data.urgency] + function addNewNotification(quickshellId, notification, data) { + // Map IDs + quickshellIdToInternalId[quickshellId] = data.id + + // Create watcher + const watcher = notificationWatcherComponent.createObject(root, { + "targetNotification": notification, + "targetDataId": data.id + }) + + // Store notification data + activeNotifications[data.id] = { + "notification": notification, + "watcher": watcher, + "metadata": { + "timestamp": data.timestamp.getTime(), + "duration": calculateDuration(data), + "urgency": data.urgency, + "paused": false, + "pauseTime": 0 } - } else { - expire = durations[data.urgency] } - notificationMetadata[data.id] = { - "timestamp": data.timestamp.getTime(), - "duration": expire, - "urgency": data.urgency, - "paused": false, - "pauseTime": 0 - } + notification.tracked = true + notification.closed.connect(() => removeNotification(data.id)) + // Add to list activeList.insert(0, data) + + // Remove overflow while (activeList.count > maxVisible) { const last = activeList.get(activeList.count - 1) - activeMap[last.id]?.dismiss() + activeNotifications[last.id]?.notification?.dismiss() activeList.remove(activeList.count - 1) - delete notificationMetadata[last.id] + cleanupNotification(last.id) } } + function findDuplicateNotification(data) { + const contentId = getContentId(data.summary, data.body, data.appName) + + for (var i = 0; i < activeList.count; i++) { + const existing = activeList.get(i) + const existingContentId = getContentId(existing.summary, existing.body, existing.appName) + if (existingContentId === contentId) { + return existing.id + } + } + return null + } + + function calculateDuration(data) { + const durations = [Settings.data.notifications?.lowUrgencyDuration * 1000 || 3000, Settings.data.notifications?.normalUrgencyDuration * 1000 || 8000, Settings.data.notifications?.criticalUrgencyDuration * 1000 || 15000] + + if (Settings.data.notifications?.respectExpireTimeout) { + if (data.expireTimeout === 0) + return -1 // Never expire + if (data.expireTimeout > 0) + return data.expireTimeout + } + + return durations[data.urgency] + } + function createData(n) { const time = new Date() const id = Checksum.sha256(JSON.stringify({ @@ -207,7 +291,7 @@ Singleton { return { "id": id, - "summary": (n.summary || ""), + "summary": n.summary || "", "body": stripTags(n.body || ""), "appName": getAppName(n.appName || n.desktopEntry || ""), "urgency": n.urgency < 0 || n.urgency > 2 ? 1 : n.urgency, @@ -223,6 +307,104 @@ Singleton { } } + function findNotificationIndex(internalId) { + for (var i = 0; i < activeList.count; i++) { + if (activeList.get(i).id === internalId) { + return i + } + } + return -1 + } + + function updateNotificationFromObject(internalId) { + const notifData = activeNotifications[internalId] + if (!notifData) + return + + const index = findNotificationIndex(internalId) + if (index < 0) + return + + const data = createData(notifData.notification) + const existing = activeList.get(index) + + // Update properties (keeping timestamp and progress) + activeList.setProperty(index, "summary", data.summary) + activeList.setProperty(index, "body", data.body) + activeList.setProperty(index, "appName", data.appName) + activeList.setProperty(index, "urgency", data.urgency) + activeList.setProperty(index, "expireTimeout", data.expireTimeout) + activeList.setProperty(index, "originalImage", data.originalImage) + activeList.setProperty(index, "cachedImage", data.cachedImage) + activeList.setProperty(index, "actionsJson", data.actionsJson) + + // Update metadata + notifData.metadata.urgency = data.urgency + notifData.metadata.duration = calculateDuration(data) + } + + function removeNotification(id) { + const index = findNotificationIndex(id) + if (index >= 0) { + activeList.remove(index) + } + cleanupNotification(id) + } + + function cleanupNotification(id) { + const notifData = activeNotifications[id] + if (notifData) { + notifData.watcher?.destroy() + delete activeNotifications[id] + } + + // Clean up quickshell ID mapping + for (const qsId in quickshellIdToInternalId) { + if (quickshellIdToInternalId[qsId] === id) { + delete quickshellIdToInternalId[qsId] + break + } + } + } + + // Progress updates + Timer { + interval: 50 + repeat: true + running: activeList.count > 0 + onTriggered: updateAllProgress() + } + + function updateAllProgress() { + const now = Date.now() + const toRemove = [] + + for (var i = 0; i < activeList.count; i++) { + const notif = activeList.get(i) + const notifData = activeNotifications[notif.id] + if (!notifData) + continue + + const meta = notifData.metadata + if (meta.duration === -1 || meta.paused) + continue + + const elapsed = now - meta.timestamp + const progress = Math.max(1.0 - (elapsed / meta.duration), 0.0) + + if (progress <= 0) { + toRemove.push(notif.id) + } else if (Math.abs(notif.progress - progress) > 0.005) { + activeList.setProperty(i, "progress", progress) + } + } + + if (toRemove.length > 0) { + animateAndRemove(toRemove[0]) + } + } + + // Image handling function queueImage(path, imageId) { if (!path || !path.startsWith("image://") || !imageId) return @@ -259,64 +441,6 @@ Singleton { } } - function removeActive(id) { - for (var i = 0; i < activeList.count; i++) { - if (activeList.get(i).id === id) { - activeList.remove(i) - delete activeMap[id] - delete notificationMetadata[id] - break - } - } - } - - // Optimized batch progress update - Timer { - interval: 50 // Reduced from 10ms to 50ms (20 updates/sec instead of 100) - repeat: true - running: activeList.count > 0 - onTriggered: updateAllProgress() - } - - function updateAllProgress() { - const now = Date.now() - const toRemove = [] - const updates = [] // Batch updates - - // Collect all updates first - for (var i = 0; i < activeList.count; i++) { - const notif = activeList.get(i) - const meta = notificationMetadata[notif.id] - - if (!meta || meta.duration === -1 || meta.paused) - continue - - // Skip infinite notifications - const elapsed = now - meta.timestamp - const progress = Math.max(1.0 - (elapsed / meta.duration), 0.0) - - if (progress <= 0) { - toRemove.push(notif.id) - } else if (Math.abs(notif.progress - progress) > 0.005) { - // Only update if change is significant - updates.push({ - "index": i, - "progress": progress - }) - } - } - - // Apply batch updates - for (const update of updates) { - activeList.setProperty(update.index, "progress", update.progress) - } - - // Remove expired notifications (one at a time to allow animation) - if (toRemove.length > 0) { - animateAndRemove(toRemove[0]) - } - } - // History management function addToHistory(data) { historyList.insert(0, data) @@ -348,7 +472,7 @@ Singleton { } } - // Persistence - State (lastSeenTs, etc.) + // Persistence - State FileView { id: stateFileView path: stateFile @@ -425,7 +549,6 @@ Singleton { try { root.lastSeenTs = stateAdapter.lastSeenTs || 0 - // Migration: if state file is empty but settings has lastSeenTs, migrate it if (root.lastSeenTs === 0 && Settings.data.notifications && Settings.data.notifications.lastSeenTs) { root.lastSeenTs = Settings.data.notifications.lastSeenTs saveState() @@ -450,10 +573,10 @@ Singleton { saveState() } + // Utility functions function getAppName(name) { if (!name || name.trim() === "") return "Unknown" - name = name.trim() if (name.includes(".") && (name.startsWith("com.") || name.startsWith("org.") || name.startsWith("io.") || name.startsWith("net."))) { @@ -464,9 +587,8 @@ Singleton { appPart = parts[parts.length - 2] || parts[0] } - if (appPart) { + if (appPart) name = appPart - } } if (name.includes(".")) { @@ -524,25 +646,25 @@ Singleton { } function pauseTimeout(id) { - const meta = notificationMetadata[id] - if (meta && !meta.paused) { - meta.paused = true - meta.pauseTime = Date.now() + const notifData = activeNotifications[id] + if (notifData && !notifData.metadata.paused) { + notifData.metadata.paused = true + notifData.metadata.pauseTime = Date.now() } } function resumeTimeout(id) { - const meta = notificationMetadata[id] - if (meta && meta.paused) { - meta.timestamp += Date.now() - meta.pauseTime - meta.paused = false + const notifData = activeNotifications[id] + if (notifData && notifData.metadata.paused) { + notifData.metadata.timestamp += Date.now() - notifData.metadata.pauseTime + notifData.metadata.paused = false } } // Public API function dismissActiveNotification(id) { - activeMap[id]?.dismiss() - removeActive(id) + activeNotifications[id]?.notification?.dismiss() + removeNotification(id) } function dismissOldestActive() { @@ -553,18 +675,21 @@ Singleton { } function dismissAllActive() { - Object.values(activeMap).forEach(n => n.dismiss()) + for (const id in activeNotifications) { + activeNotifications[id].notification?.dismiss() + activeNotifications[id].watcher?.destroy() + } activeList.clear() - activeMap = {} - notificationMetadata = {} + activeNotifications = {} + quickshellIdToInternalId = {} } function invokeAction(id, actionId) { - const n = activeMap[id] - if (!n?.actions) + const notifData = activeNotifications[id] + if (!notifData?.notification?.actions) return false - for (const action of n.actions) { + for (const action of notifData.notification.actions) { if (action.identifier === actionId && action.invoke) { action.invoke() return true @@ -599,7 +724,7 @@ Singleton { saveHistory() } - // Signals & connections + // Signals signal animateAndRemove(string notificationId) Connections { From 1755e9157c8ae1dd3f07f5b4dfe5a0fdd7ad18aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Atoch?= Date: Sat, 8 Nov 2025 08:47:00 -0500 Subject: [PATCH 015/107] NotificationHistory: Panel rebuilt using scrollview instead of listview to avoid polish loop. --- .../NotificationHistoryPanel.qml | 380 ++++++++---------- 1 file changed, 170 insertions(+), 210 deletions(-) diff --git a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml index 78c6fbbf..3c90e942 100644 --- a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml +++ b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml @@ -124,237 +124,197 @@ SmartPanel { } // Notification list - NListView { - id: notificationList + NScrollView { + id: scrollView Layout.fillWidth: true Layout.fillHeight: true - horizontalPolicy: ScrollBar.AlwaysOff - verticalPolicy: ScrollBar.AsNeeded - - model: NotificationService.historyList - spacing: Style.marginM + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded clip: true - boundsBehavior: Flickable.StopAtBounds visible: NotificationService.historyList.count > 0 // Track which notification is expanded property string expandedId: "" - delegate: Item { - property string notificationId: model.id - property bool isExpanded: notificationList.expandedId === notificationId + contentWidth: availableWidth - // Cache the content height to break binding loops - property real contentHeight: notificationLayout.implicitHeight + Column { + width: scrollView.width + spacing: Style.marginM - // Cache truncation state to avoid binding to truncated property during polish - property bool hasTextTruncated: false + Repeater { + model: NotificationService.historyList - width: notificationList.width - height: contentHeight + (Style.marginM * 2) - - Rectangle { - anchors.fill: parent - radius: Style.radiusM - color: Color.mSurfaceVariant - border.color: Qt.alpha(Color.mOutline, Style.opacityMedium) - border.width: Style.borderS - - // Smooth color transition on hover - Behavior on color { - enabled: !Settings.data.general.animationDisabled - ColorAnimation { - duration: Style.animationFast - } - } - } - - // Click to expand/collapse - MouseArea { - anchors.fill: parent - // Don't capture clicks on the delete button - anchors.rightMargin: 48 - enabled: hasTextTruncated - onClicked: { - if (notificationList.expandedId === notificationId) { - notificationList.expandedId = "" - } else { - notificationList.expandedId = notificationId - } - } - cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - } - - // Update truncation state asynchronously - Connections { - target: summaryText - function onTruncatedChanged() { - hasTextTruncated = summaryText.truncated || bodyText.truncated - } - } - - Connections { - target: bodyText - function onTruncatedChanged() { - hasTextTruncated = summaryText.truncated || bodyText.truncated - } - } - - Component.onCompleted: { - hasTextTruncated = summaryText.truncated || bodyText.truncated - } - - Item { - id: notificationLayoutWrapper - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: Style.marginM - height: notificationLayout.implicitHeight - - RowLayout { - id: notificationLayout + delegate: Item { + id: notificationDelegate width: parent.width - spacing: Style.marginM + height: contentColumn.height + (Style.marginM * 2) - // Icon - properly centered vertically - NImageCircled { - Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio) - Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio) - Layout.alignment: Qt.AlignVCenter - imagePath: model.cachedImage || model.originalImage || "" - borderColor: Color.transparent - borderWidth: 0 - fallbackIcon: "bell" - fallbackIconSize: 24 + property string notificationId: model.id + property bool isExpanded: scrollView.expandedId === notificationId + property bool canExpand: summaryText.truncated || bodyText.truncated + + Rectangle { + anchors.fill: parent + radius: Style.radiusM + color: Color.mSurfaceVariant + border.color: Qt.alpha(Color.mOutline, Style.opacityMedium) + border.width: Style.borderS + + Behavior on color { + enabled: !Settings.data.general.animationDisabled + ColorAnimation { + duration: Style.animationFast + } + } } - // Notification content column - ColumnLayout { - Layout.fillWidth: true - Layout.alignment: Qt.AlignTop - spacing: Style.marginXS - Layout.rightMargin: -(Style.marginM + Style.baseWidgetSize * 0.6) + // Click to expand/collapse + MouseArea { + anchors.fill: parent + anchors.rightMargin: Style.baseWidgetSize + enabled: notificationDelegate.canExpand + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + if (scrollView.expandedId === notificationId) { + scrollView.expandedId = "" + } else { + scrollView.expandedId = notificationId + } + } + } - // Header row with app name and timestamp - RowLayout { - Layout.fillWidth: true - spacing: Style.marginS + Column { + id: contentColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginM - // Urgency indicator - Rectangle { - Layout.preferredWidth: 6 - Layout.preferredHeight: 6 - Layout.alignment: Qt.AlignVCenter - radius: 3 - visible: model.urgency !== 1 - color: { - if (model.urgency === 2) - return Color.mError - else if (model.urgency === 0) - return Color.mOnSurfaceVariant - else - return Color.transparent + Row { + width: parent.width + spacing: Style.marginM + + // Icon + NImageCircled { + width: Math.round(40 * Style.uiScaleRatio) + height: Math.round(40 * Style.uiScaleRatio) + anchors.verticalCenter: parent.verticalCenter + imagePath: model.cachedImage || model.originalImage || "" + borderColor: Color.transparent + borderWidth: 0 + fallbackIcon: "bell" + fallbackIconSize: 24 + } + + // Content + Column { + width: parent.width - Math.round(40 * Style.uiScaleRatio) - Style.marginM - Style.baseWidgetSize + spacing: Style.marginXS + + // Header row with app name and timestamp + Row { + width: parent.width + spacing: Style.marginS + + // Urgency indicator + Rectangle { + width: 6 + height: 6 + anchors.verticalCenter: parent.verticalCenter + radius: 3 + visible: model.urgency !== 1 + color: { + if (model.urgency === 2) + return Color.mError + else if (model.urgency === 0) + return Color.mOnSurfaceVariant + else + return Color.transparent + } + } + + NText { + text: model.appName || "Unknown App" + pointSize: Style.fontSizeXS + color: Color.mSecondary + } + + NText { + text: Time.formatRelativeTime(model.timestamp) + pointSize: Style.fontSizeXS + color: Color.mSecondary + } + } + + // Summary + NText { + id: summaryText + width: parent.width + text: model.summary || I18n.tr("general.no-summary") + pointSize: Style.fontSizeM + font.weight: Font.Medium + color: Color.mOnSurface + textFormat: Text.PlainText + wrapMode: Text.Wrap + maximumLineCount: notificationDelegate.isExpanded ? 999 : 2 + elide: Text.ElideRight + } + + // Body + NText { + id: bodyText + width: parent.width + text: model.body || "" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + textFormat: Text.PlainText + wrapMode: Text.Wrap + maximumLineCount: notificationDelegate.isExpanded ? 999 : 3 + elide: Text.ElideRight + visible: text.length > 0 + } + + // Expand indicator + Row { + width: parent.width + visible: !notificationDelegate.isExpanded && notificationDelegate.canExpand + spacing: Style.marginXS + + Item { + width: parent.width - expandText.width - expandIcon.width - Style.marginXS + height: 1 + } + + NText { + id: expandText + text: I18n.tr("notifications.panel.click-to-expand") || "Click to expand" + pointSize: Style.fontSizeXS + color: Color.mPrimary + font.weight: Font.Medium + } + + NIcon { + id: expandIcon + icon: "chevron-down" + pointSize: Style.fontSizeS + color: Color.mPrimary + } } } - NText { - text: model.appName || "Unknown App" - pointSize: Style.fontSizeXS - color: Color.mSecondary + // Delete button + NIconButton { + icon: "trash" + tooltipText: I18n.tr("tooltips.delete-notification") + baseSize: Style.baseWidgetSize * 0.7 + anchors.verticalCenter: parent.verticalCenter + + onClicked: { + NotificationService.removeFromHistory(notificationId) + } } - - NText { - text: Time.formatRelativeTime(model.timestamp) - pointSize: Style.fontSizeXS - color: Color.mSecondary - } - - Item { - Layout.fillWidth: true - } - } - - // Summary - NText { - id: summaryText - text: model.summary || I18n.tr("general.no-summary") - pointSize: Style.fontSizeM - font.weight: Font.Medium - color: Color.mOnSurface - textFormat: Text.PlainText - wrapMode: Text.Wrap - Layout.fillWidth: true - maximumLineCount: isExpanded ? 999 : 2 - elide: Text.ElideRight - - // Smooth transition without triggering layout recalculation - Behavior on maximumLineCount { - enabled: false // Disable animation on this to avoid polish loops - } - } - - // Body - NText { - id: bodyText - text: model.body || "" - pointSize: Style.fontSizeS - color: Color.mOnSurfaceVariant - textFormat: Text.PlainText - wrapMode: Text.Wrap - Layout.fillWidth: true - maximumLineCount: isExpanded ? 999 : 3 - elide: Text.ElideRight - visible: text.length > 0 - - // Smooth transition without triggering layout recalculation - Behavior on maximumLineCount { - enabled: false // Disable animation on this to avoid polish loops - } - } - - // Spacer for expand indicator - Item { - Layout.fillWidth: true - Layout.preferredHeight: Style.marginS - visible: !isExpanded && hasTextTruncated - } - - // Expand indicator - RowLayout { - Layout.fillWidth: true - visible: !isExpanded && hasTextTruncated - spacing: Style.marginXS - - Item { - Layout.fillWidth: true - } - - NText { - text: I18n.tr("notifications.panel.click-to-expand") || "Click to expand" - pointSize: Style.fontSizeXS - color: Color.mPrimary - font.weight: Font.Medium - } - - NIcon { - icon: "chevron-down" - pointSize: Style.fontSizeS - color: Color.mPrimary - } - } - } - - // Delete button - NIconButton { - icon: "trash" - tooltipText: I18n.tr("tooltips.delete-notification") - baseSize: Style.baseWidgetSize * 0.7 - Layout.alignment: Qt.AlignTop - - onClicked: { - // Remove from history using the service API - NotificationService.removeFromHistory(notificationId) } } } From 5c8801ba764fffb1eeeb9242efaa68a7f3c2cce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Atoch?= Date: Sat, 8 Nov 2025 08:51:08 -0500 Subject: [PATCH 016/107] SmartPanel: Improved rolldown direction when the bar is not floating --- Modules/MainScreen/SmartPanel.qml | 50 ++++++++++++++++++------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/Modules/MainScreen/SmartPanel.qml b/Modules/MainScreen/SmartPanel.qml index e4bda7a2..3d21b68d 100644 --- a/Modules/MainScreen/SmartPanel.qml +++ b/Modules/MainScreen/SmartPanel.qml @@ -744,12 +744,14 @@ Item { if (!isPanelVisible) { return true } + // PRIORITY 1: Bar attachment (always takes precedence) // Attached to bar at top if (willTouchTopBar) { return true } + // PRIORITY 2: Screen edge attachment (only if not touching bar) // Attached to screen top edge (not bar) - if (willTouchTopEdge && !willTouchTopBar) { + if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) { return true } // If panel is not attached to any edge, animate from top by default @@ -762,12 +764,14 @@ Item { if (!isPanelVisible) { return false } + // PRIORITY 1: Bar attachment (always takes precedence) // Attached to bar at bottom if (willTouchBottomBar) { return true } + // PRIORITY 2: Screen edge attachment (only if not touching bar) // Attached to screen bottom edge (not bar) - if (willTouchBottomEdge && !willTouchBottomBar) { + if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) { return true } return false @@ -776,22 +780,25 @@ Item { if (!isPanelVisible) { return false } - // Don't animate from left if also touching top/bottom edge (priority: vertical over horizontal) - // Check directly instead of relying on other computed properties to avoid race conditions - var touchingTopBar = isPanelVisible && panelContent.allowAttachToBar && root.barPosition === "top" && !root.barIsVertical && Math.abs(panelBackground.targetY - (root.barMarginV + Style.barHeight)) <= 1 - var touchingBottomBar = isPanelVisible && panelContent.allowAttachToBar && root.barPosition === "bottom" && !root.barIsVertical && Math.abs(panelBackground.targetY - (root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight)) <= 1 - var touchingTopEdge = isPanelVisible && panelContent.allowAttach && panelBackground.targetY <= 1 - var touchingBottomEdge = isPanelVisible && panelContent.allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1) - - if (touchingTopEdge || touchingBottomEdge || touchingTopBar || touchingBottomBar) { + // PRIORITY 1: Bar attachment (always takes precedence) + // If touching any horizontal bar, don't animate from left + if (willTouchTopBar || willTouchBottomBar) { return false } // Attached to bar at left if (willTouchLeftBar) { return true } + // PRIORITY 2: Screen edge attachment (only if not touching any bar) + // Don't animate from left if also touching top/bottom edge (priority: vertical over horizontal) + var touchingTopEdge = isPanelVisible && panelContent.allowAttach && panelBackground.targetY <= 1 + var touchingBottomEdge = isPanelVisible && panelContent.allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1) + + if (touchingTopEdge || touchingBottomEdge) { + return false + } // Attached to screen left edge (not bar) - if (willTouchLeftEdge && !willTouchLeftBar) { + if (willTouchLeftEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar) { return true } return false @@ -800,22 +807,25 @@ Item { if (!isPanelVisible) { return false } - // Don't animate from right if also touching top/bottom edge (priority: vertical over horizontal) - // Check directly instead of relying on other computed properties to avoid race conditions - var touchingTopBar = isPanelVisible && panelContent.allowAttachToBar && root.barPosition === "top" && !root.barIsVertical && Math.abs(panelBackground.targetY - (root.barMarginV + Style.barHeight)) <= 1 - var touchingBottomBar = isPanelVisible && panelContent.allowAttachToBar && root.barPosition === "bottom" && !root.barIsVertical && Math.abs(panelBackground.targetY - (root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight)) <= 1 - var touchingTopEdge = isPanelVisible && panelContent.allowAttach && panelBackground.targetY <= 1 - var touchingBottomEdge = isPanelVisible && panelContent.allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1) - - if (touchingTopEdge || touchingBottomEdge || touchingTopBar || touchingBottomBar) { + // PRIORITY 1: Bar attachment (always takes precedence) + // If touching any horizontal bar, don't animate from right + if (willTouchTopBar || willTouchBottomBar) { return false } // Attached to bar at right if (willTouchRightBar) { return true } + // PRIORITY 2: Screen edge attachment (only if not touching any bar) + // Don't animate from right if also touching top/bottom edge (priority: vertical over horizontal) + var touchingTopEdge = isPanelVisible && panelContent.allowAttach && panelBackground.targetY <= 1 + var touchingBottomEdge = isPanelVisible && panelContent.allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1) + + if (touchingTopEdge || touchingBottomEdge) { + return false + } // Attached to screen right edge (not bar) - if (willTouchRightEdge && !willTouchRightBar) { + if (willTouchRightEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar) { return true } return false From 20c0589ce47a6e39c0f9bb98b55ae9092bea5c6f Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 10:52:42 -0500 Subject: [PATCH 017/107] Refactor AppTheming in smaller services --- CLAUDE.md | 70 ++- Commons/Settings.qml | 24 +- .../Panels/Settings/Tabs/ColorSchemeTab.qml | 2 +- Services/AppThemeService.qml | 464 +----------------- Services/ColorPaletteGenerator.qml | 100 ++++ Services/MatugenTemplates.qml | 306 ------------ Services/ProgramCheckerService.qml | 12 +- Services/TemplateProcessor.qml | 360 ++++++++++++++ Services/TemplateRegistry.qml | 234 +++++++++ 9 files changed, 799 insertions(+), 773 deletions(-) create mode 100644 Services/ColorPaletteGenerator.qml delete mode 100644 Services/MatugenTemplates.qml create mode 100644 Services/TemplateProcessor.qml create mode 100644 Services/TemplateRegistry.qml diff --git a/CLAUDE.md b/CLAUDE.md index 7f36f0aa..82ccb76b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,12 +82,14 @@ Core services that power the shell (40+ services): - `SystemStatService.qml` - System resource monitoring **UI & Theming:** -- `AppThemeService.qml` - Application theming engine +- `AppThemeService.qml` - Main theming facade/orchestrator +- `ColorPaletteGenerator.qml` - Material Design 3 palette generation +- `TemplateRegistry.qml` - Template configurations (apps, terminals, Discord) +- `TemplateProcessor.qml` - Matugen CLI + sed script execution - `ColorSchemeService.qml` - Color scheme management - `DarkModeService.qml` - Dark/light mode switching - `FontService.qml` - Font management - `WallpaperService.qml` - Wallpaper handling (with Matugen integration) -- `MatugenTemplates.qml` - Material You color generation templates - `NightLightService.qml` - Blue light filter **Features:** @@ -321,6 +323,66 @@ BarWidgetRegistry and ControlCenterWidgetRegistry: - Dynamic widget loading - Easy extension point for custom widgets +### Singleton Pattern (Quickshell) +Services in Quickshell use the `pragma Singleton` directive: +- Add `pragma Singleton` at the top of the QML file (before imports) +- No qmldir needed - Quickshell handles singleton resolution automatically +- Import with standard path: `import qs.Services` or `import qs.Services.DynamicTheming` +- Access via singleton name: `AppThemeService.generate()` + +Example: +```qml +pragma Singleton + +import QtQuick +import qs.Commons + +Singleton { + id: root + + function doSomething() { + // singleton logic + } +} +``` + +### DynamicTheming Architecture +The theming system was refactored (Jan 2025) into 4 focused services in `Services/`: + +**AppThemeService.qml** (~60 lines) - Main facade +- Public API: `init()`, `generate()`, `generateFromWallpaper()`, `generateFromPredefinedScheme()` +- Orchestrates theme generation based on wallpaper vs predefined schemes +- Maintains backward compatibility with existing code + +**ColorPaletteGenerator.qml** (~100 lines) - Pure color logic +- Single function: `generatePalette(colors, isDarkMode, isStrict)` +- Generates complete Material Design 3 palette from base colors +- No dependencies on other services (pure computation) + +**TemplateRegistry.qml** (~230 lines) - Configuration source of truth +- Defines all application templates (`applications` array) +- Defines terminal templates (`terminals` array) +- Consolidated Discord clients (7 variants in single config) +- Provides `discordClients` array for ProgramCheckerService +- User template TOML generation + +**TemplateProcessor.qml** (~370 lines) - Template execution +- Dual-path architecture: + - Wallpaper colors: Uses matugen CLI for color extraction + template processing + - Predefined schemes: Uses sed scripts to apply colors to templates +- Handles terminal themes (copies pre-rendered files for predefined schemes) +- User template support (matugen json/image subcommands) + +**Adding new application templates:** +1. Add entry to `TemplateRegistry.applications` array in `Services/TemplateRegistry.qml` +2. Create template file in `/Assets/MatugenTemplates/` +3. Add Settings toggle in `Commons/Settings.qml` (templates.yourapp) +4. Template will automatically work for both wallpaper and predefined paths + +**Discord client handling:** +All Discord clients use the same template (`vesktop.css`) but different output paths. +Auto-detected by ProgramCheckerService, enabled via single `Settings.data.templates.discord` toggle. + ## Git Hooks Uses `lefthook` for git hooks (see lefthook.yml) @@ -352,7 +414,9 @@ See [development guidelines](https://docs.noctalia.dev/development/guideline) - Service initialization: `shell.qml` (Component.onCompleted) - Panel registration: `shell.qml` (panelComponents array) - Theme system: `Services/AppThemeService.qml` -- Color generation: `Services/MatugenTemplates.qml` +- Template configurations: `Services/TemplateRegistry.qml` +- Palette generation: `Services/ColorPaletteGenerator.qml` +- Template processing: `Services/TemplateProcessor.qml` ### Testing - Test on target compositors: Niri, Hyprland, Sway diff --git a/Commons/Settings.qml b/Commons/Settings.qml index e720a97f..a38f5082 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -14,7 +14,7 @@ Singleton { readonly property alias data: adapter property bool isLoaded: false property bool directoriesCreated: false - property int settingsVersion: 20 + property int settingsVersion: 21 property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1" // Define our app directories @@ -617,6 +617,28 @@ Singleton { Logger.w("Settings", "Added a ControlCenter widget to the right section") } } + + // ----------------- + // 5th. Migrate Discord templates (version 20 → 21) + // Consolidate individual discord_* properties into unified discord property + if (adapter.settingsVersion < 21) { + var anyDiscordEnabled = false + + // Check if any Discord client was enabled + const discordClients = ["discord_vesktop", "discord_webcord", "discord_armcord", "discord_equibop", "discord_lightcord", "discord_dorion"] + + for (var i = 0; i < discordClients.length; i++) { + if (adapter.templates[discordClients[i]]) { + anyDiscordEnabled = true + break + } + } + + // Set unified discord property + adapter.templates.discord = anyDiscordEnabled + + Logger.i("Settings", "Migrated Discord templates to unified 'discord' property (enabled:", anyDiscordEnabled + ")") + } } // ----------------------------------------------------- diff --git a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml index f12decfa..80f0bf80 100644 --- a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml @@ -771,7 +771,7 @@ ColumnLayout { onToggled: checked => { Settings.data.templates.enableUserTemplates = checked if (checked) { - MatugenTemplates.writeUserTemplatesToml() + TemplateRegistry.writeUserTemplatesToml() } AppThemeService.generate() } diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index a5460bcb..f694d235 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -2,137 +2,20 @@ pragma Singleton import QtQuick import Quickshell -import Quickshell.Io import qs.Commons -import qs.Services -import "../Helpers/ColorsConvert.js" as ColorsConvert Singleton { id: root + // PUBLIC API - maintain compatibility with existing code readonly property string colorsApplyScript: Quickshell.shellDir + '/Bin/colors-apply.sh' - readonly property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml" - readonly property var terminalPaths: ({ - "foot": "~/.config/foot/themes/noctalia", - "ghostty": "~/.config/ghostty/themes/noctalia", - "kitty": "~/.config/kitty/themes/noctalia.conf", - "alacritty": "~/.config/alacritty/themes/noctalia.toml", - "wezterm": "~/.config/wezterm/colors/Noctalia.toml" - }) - - readonly property var schemeNameMap: ({ - "Noctalia (default)": "Noctalia-default", - "Noctalia (legacy)": "Noctalia-legacy", - "Tokyo Night": "Tokyo-Night" - }) - readonly property var predefinedTemplateConfigs: ({ - "gtk": { - "input": "gtk.css", - "outputs": [{ - "path": "~/.config/gtk-3.0/gtk.css" - }, { - "path": "~/.config/gtk-4.0/gtk.css" - }], - "postProcess": mode => `gsettings set org.gnome.desktop.interface color-scheme prefer-${mode}\n` - }, - "qt": { - "input": "qtct.conf", - "outputs": [{ - "path": "~/.config/qt5ct/colors/noctalia.conf" - }, { - "path": "~/.config/qt6ct/colors/noctalia.conf" - }] - }, - "kcolorscheme": { - "input": "kcolorscheme.colors", - "outputs": [{ - "path": "~/.local/share/color-schemes/noctalia.colors" - }] - }, - "fuzzel": { - "input": "fuzzel.conf", - "outputs": [{ - "path": "~/.config/fuzzel/themes/noctalia" - }], - "postProcess": () => `${colorsApplyScript} fuzzel\n` - }, - "pywalfox": { - "input": "pywalfox.json", - "outputs": [{ - "path": "~/.cache/wal/colors.json" - }], - "postProcess": () => `${colorsApplyScript} pywalfox\n` - }, - "discord_vesktop": { - "input": "vesktop.css", - "outputs": [{ - "path": "~/.config/vesktop/themes/noctalia.theme.css" - }] - }, - "discord_webcord": { - "input": "vesktop.css", - "outputs": [{ - "path": "~/.config/webcord/themes/noctalia.theme.css" - }] - }, - "discord_armcord": { - "input": "vesktop.css", - "outputs": [{ - "path": "~/.config/armcord/themes/noctalia.theme.css" - }] - }, - "discord_equibop": { - "input": "vesktop.css", - "outputs": [{ - "path": "~/.config/equibop/themes/noctalia.theme.css" - }] - }, - "discord_lightcord": { - "input": "vesktop.css", - "outputs": [{ - "path": "~/.config/lightcord/themes/noctalia.theme.css" - }] - }, - "discord_dorion": { - "input": "vesktop.css", - "outputs": [{ - "path": "~/.config/dorion/themes/noctalia.theme.css" - }] - }, - "discord_vencord": { - "input": "vesktop.css", - "outputs": [{ - "path": "~/.config/discord/themes/noctalia.theme.css" - }] - }, - "vicinae": { - "input": "vicinae.toml", - "outputs": [{ - "path": "~/.local/share/vicinae/themes/matugen.toml" - }], - "postProcess": () => `cp -n ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae\n` - }, - "walker": { - "input": "walker.css", - "outputs": [{ - "path": "~/.config/walker/themes/noctalia/style.css" - }], - "postProcess": () => `${colorsApplyScript} walker\n`, - "strict": true - }, - "code": { - "input": "code.json", - "outputs": [{ - "path": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" - }] - } - }) + // Connections to trigger regeneration Connections { target: WallpaperService function onWallpaperChanged(screenName, path) { if (screenName === Screen.name && Settings.data.colorSchemes.useWallpaperColors) { - generateFromWallpaper() + generate() } } } @@ -141,367 +24,36 @@ Singleton { target: Settings.data.colorSchemes function onDarkModeChanged() { Logger.i("AppThemeService", "Detected dark mode change") - AppThemeService.generate() + generate() } } - // -------------------------------------------------------------------------------- + // PUBLIC FUNCTIONS function init() { Logger.i("AppThemeService", "Service started") } - // -------------------------------------------------------------------------------- function generate() { if (Settings.data.colorSchemes.useWallpaperColors) { generateFromWallpaper() } else { - // Re-apply the scheme, this is the best way to regenerate all templates too. ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) } } - // -------------------------------------------------------------------------------- - // Wallpaper Colors Generation - // -------------------------------------------------------------------------------- function generateFromWallpaper() { - - // Logger.i("AppThemeService", "Generating from wallpaper on screen:", Screen.name) - const wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''") + const wp = WallpaperService.getWallpaper(Screen.name) if (!wp) { Logger.e("AppThemeService", "No wallpaper found") return } - - const content = MatugenTemplates.buildConfigToml() - if (!content) - return - const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - const script = buildMatugenScript(content, wp, mode) - - generateProcess.command = ["bash", "-lc", script] - generateProcess.running = true + TemplateProcessor.processWallpaperColors(wp, mode) } - function buildMatugenScript(content, wallpaper, mode) { - const delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9) - const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''") - - let script = `cat > '${pathEsc}' << '${delimiter}'\n${content}\n${delimiter}\n` - script += `matugen image '${wallpaper}' --config '${pathEsc}' --mode ${mode} --type ${Settings.data.colorSchemes.matugenSchemeType}` - script += buildUserTemplateCommand(wallpaper, mode) - - return script + "\n" - } - - // -------------------------------------------------------------------------------- - // Predefined Scheme Generation - // For predefined color schemes, we bypass matugen's generation which do not gives good results. - // Instead, we use 'sed' to apply a custom palette to the existing matugen templates. - // -------------------------------------------------------------------------------- function generateFromPredefinedScheme(schemeData) { Logger.i("AppThemeService", "Generating templates from predefined color scheme") - - handleTerminalThemes() - const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - const colors = schemeData[mode] - let script = processAllTemplates(colors, mode) - - // Add user templates if enabled - script += buildUserTemplateCommandForPredefined(schemeData, mode) - - generateProcess.command = ["bash", "-lc", script] - generateProcess.running = true - } - - function generatePalette(colors, isDarkMode, isStrict) { - const c = hex => ({ - "default": { - "hex": hex, - "hex_stripped": hex.replace(/^#/, "") - } - }) - - // Generate container colors - const primaryContainer = ColorsConvert.generateContainerColor(colors.mPrimary, isDarkMode) - const secondaryContainer = ColorsConvert.generateContainerColor(colors.mSecondary, isDarkMode) - const tertiaryContainer = ColorsConvert.generateContainerColor(colors.mTertiary, isDarkMode) - - // Generate "on" colors - const onPrimary = ColorsConvert.generateOnColor(colors.mPrimary, isDarkMode) - const onSecondary = ColorsConvert.generateOnColor(colors.mSecondary, isDarkMode) - const onTertiary = ColorsConvert.generateOnColor(colors.mTertiary, isDarkMode) - - const onPrimaryContainer = ColorsConvert.generateOnColor(primaryContainer, isDarkMode) - const onSecondaryContainer = ColorsConvert.generateOnColor(secondaryContainer, isDarkMode) - const onTertiaryContainer = ColorsConvert.generateOnColor(tertiaryContainer, isDarkMode) - - // Generate error colors (standard red-based) - const errorContainer = ColorsConvert.generateContainerColor(colors.mError, isDarkMode) - const onError = ColorsConvert.generateOnColor(colors.mError, isDarkMode) - const onErrorContainer = ColorsConvert.generateOnColor(errorContainer, isDarkMode) - - // Surface is same as background in Material Design 3 - const surface = colors.mSurface - const onSurface = isStrict ? colors.mOnSurface : ColorsConvert.generateOnColor(colors.mSurface, isDarkMode) - - // Generate surface variant (slightly different tone) - const surfaceVariant = isStrict ? colors.mSurfaceVariant : ColorsConvert.adjustLightness(colors.mSurface, isDarkMode ? 5 : -3) - const onSurfaceVariant = isStrict ? colors.mOnSurfaceVariant : ColorsConvert.generateOnColor(surfaceVariant, isDarkMode) - - // Generate surface containers (progressive elevation) - const surfaceContainerLowest = ColorsConvert.generateSurfaceVariant(surface, 0, isDarkMode) - const surfaceContainerLow = ColorsConvert.generateSurfaceVariant(surface, 1, isDarkMode) - const surfaceContainer = ColorsConvert.generateSurfaceVariant(surface, 2, isDarkMode) - const surfaceContainerHigh = ColorsConvert.generateSurfaceVariant(surface, 3, isDarkMode) - const surfaceContainerHighest = ColorsConvert.generateSurfaceVariant(surface, 4, isDarkMode) - - // Generate outline colors (for borders/dividers) - const outline = isStrict ? colors.mOutline : ColorsConvert.adjustLightnessAndSaturation(colors.mOnSurface, isDarkMode ? -30 : 30, isDarkMode ? -30 : 30) - const outlineVariant = ColorsConvert.adjustLightness(outline, isDarkMode ? -20 : 20) - - // Shadow is always pitch black - const shadow = "#000000" - - return { - "primary": c(colors.mPrimary), - "on_primary": c(onPrimary), - "primary_container": c(primaryContainer), - "on_primary_container": c(onPrimaryContainer), - "secondary": c(colors.mSecondary), - "on_secondary": c(onSecondary), - "secondary_container": c(secondaryContainer), - "on_secondary_container": c(onSecondaryContainer), - "tertiary": c(colors.mTertiary), - "on_tertiary": c(onTertiary), - "tertiary_container": c(tertiaryContainer), - "on_tertiary_container": c(onTertiaryContainer), - "error": c(colors.mError), - "on_error": c(onError), - "error_container": c(errorContainer), - "on_error_container": c(onErrorContainer), - "background": c(surface), - "on_background": c(onSurface), - "surface": c(surface), - "on_surface": c(onSurface), - "surface_variant": c(surfaceVariant), - "on_surface_variant": c(onSurfaceVariant), - "surface_container_lowest": c(surfaceContainerLowest), - "surface_container_low": c(surfaceContainerLow), - "surface_container": c(surfaceContainer), - "surface_container_high": c(surfaceContainerHigh), - "surface_container_highest": c(surfaceContainerHighest), - "outline": c(outline), - "outline_variant": c(outlineVariant), - "shadow": c(shadow) - } - } - - function processAllTemplates(colors, mode) { - let script = "" - const homeDir = Quickshell.env("HOME") - - Object.keys(predefinedTemplateConfigs).forEach(appName => { - if (Settings.data.templates[appName]) { - script += processTemplate(appName, colors, mode, homeDir) - } - }) - - return script - } - - function processTemplate(appName, colors, mode, homeDir) { - const config = predefinedTemplateConfigs[appName] - const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${config.input}` - let script = "" - - const palette = generatePalette(colors, Settings.data.colorSchemes.darkMode, config.strict || false) - - config.outputs.forEach(output => { - - const outputPath = output.path.replace("~", homeDir) - const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) - - // For Discord clients, check if the base config directory exists - if (appName.startsWith("discord_")) { - const baseConfigDir = outputDir.replace("/themes", "") - script += `if [ -d "${baseConfigDir}" ]; then\n` - script += ` mkdir -p ${outputDir}\n` - script += ` cp '${templatePath}' '${outputPath}'\n` - script += ` ${replaceColorsInFile(outputPath, palette)}\n` - script += `else\n` - script += ` echo "Discord client ${appName} not found at ${baseConfigDir}, skipping theme generation"\n` - script += `fi\n` - } else { - script += `mkdir -p ${outputDir}\n` - script += `cp '${templatePath}' '${outputPath}'\n` - script += replaceColorsInFile(outputPath, palette) - } - }) - - if (config.postProcess) { - script += config.postProcess(mode) - } - - return script - } - - function replaceColorsInFile(filePath, colors) { - // This handle both ".hex" and ".hex_stripped" the EXACT same way. Our predefined color schemes are - // always RRGGBB without alpha so this is fine and keeps compatibility with matugen. - let script = "" - Object.keys(colors).forEach(colorKey => { - const colorValue = colors[colorKey].default.hex - const escapedColor = colorValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex\\(_stripped\\)\\?}}/${escapedColor}/g' '${filePath}'\n` - }) - return script - } - - // -------------------------------------------------------------------------------- - // Terminal Themes - // -------------------------------------------------------------------------------- - function handleTerminalThemes() { - const commands = [] - - Object.keys(terminalPaths).forEach(terminal => { - if (Settings.data.templates[terminal]) { - const outputPath = terminalPaths[terminal] - const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) - const templatePath = getTerminalColorsTemplate(terminal) - - commands.push(`mkdir -p ${outputDir}`) - commands.push(`cp -f ${templatePath} ${outputPath}`) - commands.push(`${colorsApplyScript} ${terminal}`) - } - }) - - if (commands.length > 0) { - copyProcess.command = ["bash", "-lc", commands.join('; ')] - copyProcess.running = true - } - } - - function getTerminalColorsTemplate(terminal) { - let colorScheme = Settings.data.colorSchemes.predefinedScheme - const mode = Settings.data.colorSchemes.darkMode ? 'dark' : 'light' - - colorScheme = schemeNameMap[colorScheme] || colorScheme - let extension = "" - if (terminal === 'kitty') { - extension = ".conf" - } else if (terminal === 'wezterm') { - extension = ".toml" - } - - return `${Quickshell.shellDir}/Assets/ColorScheme/${colorScheme}/terminal/${terminal}/${colorScheme}-${mode}${extension}` - } - - // -------------------------------------------------------------------------------- - // User Templates - // -------------------------------------------------------------------------------- - function buildUserTemplateCommand(input, mode) { - if (!Settings.data.templates.enableUserTemplates) { - return "" - } - - const userConfigPath = getUserConfigPath() - let script = "\n# Execute user config if it exists\n" - script += `if [ -f '${userConfigPath}' ]; then\n` - script += ` matugen image '${input}' --config '${userConfigPath}' --mode ${mode} --type ${Settings.data.colorSchemes.matugenSchemeType}\n` - script += "fi" - - return script - } - - function buildUserTemplateCommandForPredefined(schemeData, mode) { - if (!Settings.data.templates.enableUserTemplates) { - return "" - } - - const userConfigPath = getUserConfigPath() - const isDarkMode = Settings.data.colorSchemes.darkMode - const colors = schemeData[mode] - - // Generate the matugen palette JSON - const palette = generatePalette(colors, isDarkMode, false) - - // Create a temporary JSON file with the color palette - const tempJsonPath = Settings.cacheDir + "predefined-colors.json" - const homeDir = Quickshell.env("HOME") - const tempJsonPathEsc = tempJsonPath.replace(/'/g, "'\\''") - - let script = "\n# Execute user templates with predefined scheme colors\n" - script += `if [ -f '${userConfigPath}' ]; then\n` - - // Write the color palette to a temp JSON file - script += ` cat > '${tempJsonPathEsc}' << 'EOF'\n` - script += JSON.stringify({ - "colors": palette - }, null, 2) + "\n" - script += "EOF\n" - - // Use matugen json subcommand with the color palette - script += ` matugen json '${tempJsonPathEsc}' --config '${userConfigPath}' --mode ${mode}\n` - script += "fi" - - return script - } - - function getUserConfigPath() { - return (Settings.configDir + "user-templates.toml").replace(/'/g, "'\\''") - } - - // -------------------------------------------------------------------------------- - // Processes - // -------------------------------------------------------------------------------- - Process { - id: generateProcess - workingDirectory: Quickshell.shellDir - running: false - - onExited: function (exitCode) { - if (exitCode !== 0) { - // Capture error output from stderr first, then stdout, or use fallback message - const errorMsg = (stderr.text && stderr.text.trim() !== "") ? stderr.text.trim() : ((stdout.text && stdout.text.trim() !== "") ? stdout.text.trim() : I18n.tr("toast.matugen.failed-general")) - Logger.e("AppThemeService", "Matugen process failed with exit code:", exitCode) - if (stderr.text && stderr.text.trim() !== "") { - Logger.e("AppThemeService", "Matugen stderr:", stderr.text) - } - if (stdout.text && stdout.text.trim() !== "") { - Logger.e("AppThemeService", "Matugen stdout:", stdout.text) - } - ToastService.showError(I18n.tr("toast.matugen.failed"), errorMsg, 6000) - } - } - - stdout: StdioCollector { - onStreamFinished: { - if (this.text) { - Logger.d("AppThemeService", "GenerateProcess stdout:", this.text) - } - } - } - stderr: StdioCollector { - onStreamFinished: { - if (this.text) { - Logger.d("AppThemeService", "GenerateProcess stderr:", this.text) - } - } - } - } - - Process { - id: copyProcess - workingDirectory: Quickshell.shellDir - running: false - stderr: StdioCollector { - onStreamFinished: { - if (this.text) { - Logger.d("AppThemeService", "CopyProcess stderr:", this.text) - } - } - } + TemplateProcessor.processPredefinedScheme(schemeData, mode) } } diff --git a/Services/ColorPaletteGenerator.qml b/Services/ColorPaletteGenerator.qml new file mode 100644 index 00000000..05b9d7c2 --- /dev/null +++ b/Services/ColorPaletteGenerator.qml @@ -0,0 +1,100 @@ +pragma Singleton + +import QtQuick +import Quickshell +import "../Helpers/ColorsConvert.js" as ColorsConvert + +Singleton { + id: root + + + /** + * Generate Material Design 3 color palette from base colors + * @param colors - Object with mPrimary, mSecondary, mTertiary, mError, mSurface, etc. + * @param isDarkMode - Boolean indicating dark or light mode + * @param isStrict - Boolean; if true, use mSurfaceVariant/mOnSurfaceVariant/mOutline directly + * @returns Object with all MD3 color roles in matugen format + */ + function generatePalette(colors, isDarkMode, isStrict) { + const c = hex => ({ + "default": { + "hex": hex, + "hex_stripped": hex.replace(/^#/, "") + } + }) + + // Generate container colors + const primaryContainer = ColorsConvert.generateContainerColor(colors.mPrimary, isDarkMode) + const secondaryContainer = ColorsConvert.generateContainerColor(colors.mSecondary, isDarkMode) + const tertiaryContainer = ColorsConvert.generateContainerColor(colors.mTertiary, isDarkMode) + + // Generate "on" colors + const onPrimary = ColorsConvert.generateOnColor(colors.mPrimary, isDarkMode) + const onSecondary = ColorsConvert.generateOnColor(colors.mSecondary, isDarkMode) + const onTertiary = ColorsConvert.generateOnColor(colors.mTertiary, isDarkMode) + + const onPrimaryContainer = ColorsConvert.generateOnColor(primaryContainer, isDarkMode) + const onSecondaryContainer = ColorsConvert.generateOnColor(secondaryContainer, isDarkMode) + const onTertiaryContainer = ColorsConvert.generateOnColor(tertiaryContainer, isDarkMode) + + // Generate error colors (standard red-based) + const errorContainer = ColorsConvert.generateContainerColor(colors.mError, isDarkMode) + const onError = ColorsConvert.generateOnColor(colors.mError, isDarkMode) + const onErrorContainer = ColorsConvert.generateOnColor(errorContainer, isDarkMode) + + // Surface is same as background in Material Design 3 + const surface = colors.mSurface + const onSurface = isStrict ? colors.mOnSurface : ColorsConvert.generateOnColor(colors.mSurface, isDarkMode) + + // Generate surface variant (slightly different tone) + const surfaceVariant = isStrict ? colors.mSurfaceVariant : ColorsConvert.adjustLightness(colors.mSurface, isDarkMode ? 5 : -3) + const onSurfaceVariant = isStrict ? colors.mOnSurfaceVariant : ColorsConvert.generateOnColor(surfaceVariant, isDarkMode) + + // Generate surface containers (progressive elevation) + const surfaceContainerLowest = ColorsConvert.generateSurfaceVariant(surface, 0, isDarkMode) + const surfaceContainerLow = ColorsConvert.generateSurfaceVariant(surface, 1, isDarkMode) + const surfaceContainer = ColorsConvert.generateSurfaceVariant(surface, 2, isDarkMode) + const surfaceContainerHigh = ColorsConvert.generateSurfaceVariant(surface, 3, isDarkMode) + const surfaceContainerHighest = ColorsConvert.generateSurfaceVariant(surface, 4, isDarkMode) + + // Generate outline colors (for borders/dividers) + const outline = isStrict ? colors.mOutline : ColorsConvert.adjustLightnessAndSaturation(colors.mOnSurface, isDarkMode ? -30 : 30, isDarkMode ? -30 : 30) + const outlineVariant = ColorsConvert.adjustLightness(outline, isDarkMode ? -20 : 20) + + // Shadow is always pitch black + const shadow = "#000000" + + return { + "primary": c(colors.mPrimary), + "on_primary": c(onPrimary), + "primary_container": c(primaryContainer), + "on_primary_container": c(onPrimaryContainer), + "secondary": c(colors.mSecondary), + "on_secondary": c(onSecondary), + "secondary_container": c(secondaryContainer), + "on_secondary_container": c(onSecondaryContainer), + "tertiary": c(colors.mTertiary), + "on_tertiary": c(onTertiary), + "tertiary_container": c(tertiaryContainer), + "on_tertiary_container": c(onTertiaryContainer), + "error": c(colors.mError), + "on_error": c(onError), + "error_container": c(errorContainer), + "on_error_container": c(onErrorContainer), + "background": c(surface), + "on_background": c(onSurface), + "surface": c(surface), + "on_surface": c(onSurface), + "surface_variant": c(surfaceVariant), + "on_surface_variant": c(onSurfaceVariant), + "surface_container_lowest": c(surfaceContainerLowest), + "surface_container_low": c(surfaceContainerLow), + "surface_container": c(surfaceContainer), + "surface_container_high": c(surfaceContainerHigh), + "surface_container_highest": c(surfaceContainerHighest), + "outline": c(outline), + "outline_variant": c(outlineVariant), + "shadow": c(shadow) + } + } +} diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml deleted file mode 100644 index ad9bf6a6..00000000 --- a/Services/MatugenTemplates.qml +++ /dev/null @@ -1,306 +0,0 @@ -pragma Singleton - -import QtQuick -import Quickshell -import Quickshell.Io -import qs.Commons - -// Central place to define which templates we generate and where they write. -// Users can extend it by dropping additional templates into: -// - Assets/MatugenTemplates/ (built-in templates) -// - ~/.config/matugen/ (user-defined templates when enableUserTemplates is true) -// User templates are automatically executed after the main matugen command -// if enableUserTemplates is enabled in settings. -Singleton { - id: root - - // Build the base TOML using current settings - function buildConfigToml() { - var lines = [] - var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - - if (Settings.data.colorSchemes.useWallpaperColors) { - addWallpaperBasedTemplates(lines, mode) - } - - addApplicationTemplates(lines, mode) - - if (lines.length > 0) { - const config = ["[config]"].concat(lines) - return config.join("\n") + "\n" - } - - return "" - } - - // Build user templates TOML for ~/.config/noctalia/user-templates.toml - function buildUserTemplatesToml() { - var lines = [] - lines.push("[config]") - lines.push("") - lines.push("[templates]") - lines.push("") - lines.push("# User-defined templates") - lines.push("# Add your custom templates below") - lines.push("# Example:") - lines.push("# [templates.myapp]") - lines.push("# input_path = \"~/.config/noctalia/templates/myapp.css\"") - lines.push("# output_path = \"~/.config/myapp/theme.css\"") - lines.push("# post_hook = \"myapp --reload-theme\"") - lines.push("") - lines.push("# Remove this section and add your own templates") - lines.push("#[templates.placeholder]") - lines.push("#input_path = \"" + Quickshell.shellDir + "/Assets/MatugenTemplates/noctalia.json\"") - lines.push("#output_path = \"" + Settings.cacheDir + "placeholder.json\"") - lines.push("") - - return lines.join("\n") + "\n" - } - - // Write user templates TOML to ~/.config/noctalia/user-templates.toml - function writeUserTemplatesToml() { - var userConfigPath = Settings.configDir + "user-templates.toml" - - // Check if file already exists - fileCheckProcess.command = ["test", "-f", userConfigPath] - fileCheckProcess.running = true - } - - function doWriteUserTemplatesToml() { - var userConfigPath = Settings.configDir + "user-templates.toml" - var configContent = buildUserTemplatesToml() - - // Ensure directory exists (should already exist but just in case) - Quickshell.execDetached(["mkdir", "-p", Settings.configDir]) - - // Write the config file - Quickshell.execDetached(["sh", "-c", `echo '${configContent.replace(/'/g, "'\\''")}' > '${userConfigPath}'`]) - - Logger.d("MatugenTemplates", "User templates config written to:", userConfigPath) - } - - // -------------------------------- - function addWallpaperBasedTemplates(lines, mode) { - // Noctalia colors - lines.push("[templates.noctalia]") - lines.push('input_path = "' + Quickshell.shellDir + '/Assets/MatugenTemplates/noctalia.json"') - lines.push('output_path = "' + Settings.configDir + 'colors.json"') - - // Terminal templates (only for wallpaper-based colors) - addTerminalTemplates(lines) - } - - // -------------------------------- - function addTerminalTemplates(lines) { - var terminals = [{ - "name": "foot", - "path": "Terminal/foot", - "output": "~/.config/foot/themes/noctalia" - }, { - "name": "ghostty", - "path": "Terminal/ghostty", - "output": "~/.config/ghostty/themes/noctalia", - "post_hook": "pkill -SIGUSR2 ghostty" - }, { - "name": "kitty", - "path": "Terminal/kitty.conf", - "output": "~/.config/kitty/themes/noctalia.conf" - }, { - "name": "alacritty", - "path": "Terminal/alacritty.toml", - "output": "~/.config/alacritty/themes/noctalia.toml" - }, { - "name": "wezterm", - "path": "Terminal/wezterm.toml", - "output": "~/.config/wezterm/colors/Noctalia.toml", - "post_hook": "touch ~/.config/wezterm/wezterm.lua" - }] - - terminals.forEach(function (terminal) { - if (Settings.data.templates[terminal.name]) { - lines.push("\n[templates." + terminal.name + "]") - lines.push('input_path = "' + Quickshell.shellDir + '/Assets/MatugenTemplates/' + terminal.path + '"') - lines.push('output_path = "' + terminal.output + '"') - lines.push('post_hook = "' + AppThemeService.colorsApplyScript + " " + terminal.name + '"') - } - }) - } - - // Applications configuration - readonly property var applications: [{ - "name": "gtk", - "templates": [{ - "version": "gtk3", - "output": "~/.config/gtk-3.0/gtk.css" - }, { - "version": "gtk4", - "output": "~/.config/gtk-4.0/gtk.css" - }], - "input": "gtk.css", - "postHook": "gsettings set org.gnome.desktop.interface color-scheme prefer-{mode}" - }, { - "name": "qt", - "templates": [{ - "version": "qt5", - "output": "~/.config/qt5ct/colors/noctalia.conf" - }, { - "version": "qt6", - "output": "~/.config/qt6ct/colors/noctalia.conf" - }], - "input": "qtct.conf" - }, { - "name": "kcolorscheme", - "templates": [{ - "version": "kcolorscheme", - "output": "~/.local/share/color-schemes/noctalia.colors" - }], - "input": "kcolorscheme.colors" - }, { - "name": "fuzzel", - "templates": [{ - "version": "fuzzel", - "output": "~/.config/fuzzel/themes/noctalia" - }], - "input": "fuzzel.conf", - "postHook": AppThemeService.colorsApplyScript + " fuzzel" - }, { - "name": "vicinae", - "templates": [{ - "version": "vicinae", - "output": "~/.local/share/vicinae/themes/matugen.toml" - }], - "input": "vicinae.toml", - "postHook": "cp -n " + Quickshell.shellDir + "/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && " + AppThemeService.colorsApplyScript + " vicinae" - }, { - "name": "walker", - "templates": [{ - "version": "walker", - "output": "~/.config/walker/themes/noctalia/style.css" - }], - "input": "walker.css" - }, { - "name": "pywalfox", - "templates": [{ - "version": "pywalfox", - "output": "~/.cache/wal/colors.json" - }], - "input": "pywalfox.json", - "postHook": AppThemeService.colorsApplyScript + " pywalfox" - }, { - "name": "discord_vesktop", - "templates": [{ - "version": "discord_vesktop", - "output": "~/.config/vesktop/themes/noctalia.theme.css" - }], - "input": "vesktop.css" - }, { - "name": "discord_webcord", - "templates": [{ - "version": "discord_webcord", - "output": "~/.config/webcord/themes/noctalia.theme.css" - }], - "input": "vesktop.css" - }, { - "name": "discord_armcord", - "templates": [{ - "version": "discord_armcord", - "output": "~/.config/armcord/themes/noctalia.theme.css" - }], - "input": "vesktop.css" - }, { - "name": "discord_equibop", - "templates": [{ - "version": "discord_equibop", - "output": "~/.config/equibop/themes/noctalia.theme.css" - }], - "input": "vesktop.css" - }, { - "name": "discord_lightcord", - "templates": [{ - "version": "discord_lightcord", - "output": "~/.config/lightcord/themes/noctalia.theme.css" - }], - "input": "vesktop.css" - }, { - "name": "discord_dorion", - "templates": [{ - "version": "discord_dorion", - "output": "~/.config/dorion/themes/noctalia.theme.css" - }], - "input": "vesktop.css" - }, { - "name": "discord_vencord", - "templates": [{ - "version": "discord_vencord", - "output": "~/.config/discord/themes/noctalia.theme.css" - }], - "input": "vesktop.css", - "requiresThemesFolder": true - }, { - "name": "code", - "templates": [{ - "version": "code", - "output": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" - }], - "input": "code.json" - }] - - // -------------------------------- - function addApplicationTemplates(lines, mode) { - applications.forEach(function (app) { - // Check if app has a condition and if it's met - var shouldInclude = true - if (app.condition !== undefined) { - shouldInclude = app.condition - } - - if (Settings.data.templates[app.name] && shouldInclude) { - app.templates.forEach(function (template) { - lines.push("\n[templates." + template.version + "]") - lines.push('input_path = "' + Quickshell.shellDir + '/Assets/MatugenTemplates/' + app.input + '"') - lines.push('output_path = "' + template.output + '"') - if (app.postHook) { - var postHook = app.postHook.replace("{mode}", mode) - lines.push('post_hook = "' + postHook + '"') - } - }) - } - }) - } - - // Extract Discord clients from applications array - readonly property var discordClients: { - var clients = [] - for (var i = 0; i < applications.length; i++) { - var app = applications[i] - if (app.name && app.name.startsWith("discord_")) { - var clientName = app.name.replace("discord_", "") - var themePath = app.templates[0].output - var configPath = themePath.replace("/themes/noctalia.theme.css", "") - clients.push({ - "name": clientName, - "configPath": configPath, - "themePath": themePath, - "requiresThemesFolder": app.requiresThemesFolder || false - }) - } - } - return clients - } - - // Process for checking if user templates file exists - Process { - id: fileCheckProcess - running: false - - onExited: function (exitCode) { - if (exitCode === 0) { - // File exists, skip creation - Logger.d("MatugenTemplates", "User templates config already exists, skipping creation") - } else { - // File doesn't exist, create it - doWriteUserTemplatesToml() - } - } - } -} diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml index b18ef1ea..fa3cb406 100644 --- a/Services/ProgramCheckerService.qml +++ b/Services/ProgramCheckerService.qml @@ -37,8 +37,8 @@ Singleton { // Build shell script to check each client var scriptParts = ["available_clients=\"\";"] - for (var i = 0; i < MatugenTemplates.discordClients.length; i++) { - var client = MatugenTemplates.discordClients[i] + for (var i = 0; i < TemplateRegistry.discordClients.length; i++) { + var client = TemplateRegistry.discordClients[i] var clientName = client.name // Check if this client requires themes folder to exist @@ -73,8 +73,8 @@ Singleton { // Build list of available clients for (var i = 0; i < detectedClients.length; i++) { var clientName = detectedClients[i] - for (var j = 0; j < MatugenTemplates.discordClients.length; j++) { - var client = MatugenTemplates.discordClients[j] + for (var j = 0; j < TemplateRegistry.discordClients.length; j++) { + var client = TemplateRegistry.discordClients[j] if (client.name === clientName) { availableDiscordClients.push(client) break @@ -199,8 +199,8 @@ Singleton { Logger.d("ProgramChecker", "HOME:", Quickshell.env("HOME")) // Test each client directory - for (var i = 0; i < MatugenTemplates.discordClients.length; i++) { - var client = MatugenTemplates.discordClients[i] + for (var i = 0; i < TemplateRegistry.discordClients.length; i++) { + var client = TemplateRegistry.discordClients[i] var configDir = client.configPath.replace("~", Quickshell.env("HOME")) Logger.d("ProgramChecker", "Checking:", configDir) } diff --git a/Services/TemplateProcessor.qml b/Services/TemplateProcessor.qml new file mode 100644 index 00000000..d5d0d0d8 --- /dev/null +++ b/Services/TemplateProcessor.qml @@ -0,0 +1,360 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Singleton { + id: root + + readonly property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml" + + readonly property var schemeNameMap: ({ + "Noctalia (default)": "Noctalia-default", + "Noctalia (legacy)": "Noctalia-legacy", + "Tokyo Night": "Tokyo-Night" + }) + + readonly property var terminalPaths: ({ + "foot": "~/.config/foot/themes/noctalia", + "ghostty": "~/.config/ghostty/themes/noctalia", + "kitty": "~/.config/kitty/themes/noctalia.conf", + "alacritty": "~/.config/alacritty/themes/noctalia.toml", + "wezterm": "~/.config/wezterm/colors/Noctalia.toml" + }) + + + /** + * Process wallpaper colors using matugen CLI + * Requirement #4: Dual-path architecture (wallpaper uses matugen CLI) + */ + function processWallpaperColors(wallpaperPath, mode) { + const content = buildMatugenConfig() + if (!content) + return + + const wp = wallpaperPath.replace(/'/g, "'\\''") + const script = buildMatugenScript(content, wp, mode) + + generateProcess.command = ["bash", "-lc", script] + generateProcess.running = true + } + + + /** + * Process predefined color scheme using sed scripts + * Requirement #4: Dual-path architecture (predefined uses sed scripts) + */ + function processPredefinedScheme(schemeData, mode) { + handleTerminalThemes(mode) + + const colors = schemeData[mode] + let script = processAllTemplates(colors, mode) + + // Add user templates if enabled (requirement #1) + script += buildUserTemplateCommandForPredefined(schemeData, mode) + + generateProcess.command = ["bash", "-lc", script] + generateProcess.running = true + } + + // ================================================================================ + // WALLPAPER-BASED GENERATION (matugen CLI) + // ================================================================================ + function buildMatugenConfig() { + var lines = [] + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + + if (Settings.data.colorSchemes.useWallpaperColors) { + addWallpaperTemplates(lines, mode) + } + + addApplicationTemplates(lines, mode) + + if (lines.length > 0) { + return ["[config]"].concat(lines).join("\n") + "\n" + } + return "" + } + + function addWallpaperTemplates(lines, mode) { + // Noctalia colors JSON + lines.push("[templates.noctalia]") + lines.push('input_path = "' + Quickshell.shellDir + '/Assets/MatugenTemplates/noctalia.json"') + lines.push('output_path = "' + Settings.configDir + 'colors.json"') + + // Terminal templates + TemplateRegistry.terminals.forEach(terminal => { + if (Settings.data.templates[terminal.id]) { + lines.push(`\n[templates.${terminal.id}]`) + lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${terminal.matugenPath}"`) + lines.push(`output_path = "${terminal.outputPath}"`) + const postHook = terminal.postHook || `${TemplateRegistry.colorsApplyScript} ${terminal.id}` + lines.push(`post_hook = "${postHook}"`) + } + }) + } + + function addApplicationTemplates(lines, mode) { + TemplateRegistry.applications.forEach(app => { + if (app.id === "discord") { + // Handle Discord clients specially + if (Settings.data.templates.discord) { + app.clients.forEach(client => { + // Check if this specific client is detected + if (isDiscordClientEnabled(client.name)) { + lines.push(`\n[templates.discord_${client.name}]`) + lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`) + lines.push(`output_path = "${client.path}/themes/noctalia.theme.css"`) + } + }) + } + } else { + // Handle regular apps + if (Settings.data.templates[app.id]) { + app.outputs.forEach((output, idx) => { + lines.push(`\n[templates.${app.id}_${idx}]`) + lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`) + lines.push(`output_path = "${output.path}"`) + if (app.postProcess) { + lines.push(`post_hook = "${app.postProcess(mode)}"`) + } + }) + } + } + }) + } + + function isDiscordClientEnabled(clientName) { + // Check ProgramCheckerService to see if client is detected + for (var i = 0; i < ProgramCheckerService.availableDiscordClients.length; i++) { + if (ProgramCheckerService.availableDiscordClients[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, "'\\''") + + let script = `cat > '${pathEsc}' << '${delimiter}'\n${content}\n${delimiter}\n` + script += `matugen image '${wallpaper}' --config '${pathEsc}' --mode ${mode} --type ${Settings.data.colorSchemes.matugenSchemeType}` + script += buildUserTemplateCommand(wallpaper, mode) + + return script + "\n" + } + + // ================================================================================ + // PREDEFINED SCHEME GENERATION (sed scripts) + // ================================================================================ + function processAllTemplates(colors, mode) { + let script = "" + const homeDir = Quickshell.env("HOME") + + TemplateRegistry.applications.forEach(app => { + if (app.id === "discord") { + if (Settings.data.templates.discord) { + script += processDiscordClients(app, colors, mode, homeDir) + } + } else { + if (Settings.data.templates[app.id]) { + script += processTemplate(app, colors, mode, homeDir) + } + } + }) + + return script + } + + function processDiscordClients(discordApp, colors, mode, homeDir) { + let script = "" + const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false) + + discordApp.clients.forEach(client => { + if (!isDiscordClientEnabled(client.name)) + return + + const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${discordApp.input}` + const outputPath = `${client.path}/themes/noctalia.theme.css`.replace("~", homeDir) + const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) + const baseConfigDir = outputDir.replace("/themes", "") + + 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 "Discord 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 = "" + + app.outputs.forEach(output => { + const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}` + const outputPath = output.path.replace("~", homeDir) + const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) + + script += `mkdir -p ${outputDir}\n` + script += `cp '${templatePath}' '${outputPath}'\n` + script += replaceColorsInFile(outputPath, palette) + }) + + if (app.postProcess) { + script += app.postProcess(mode) + } + + return script + } + + function replaceColorsInFile(filePath, colors) { + // This handles both ".hex" and ".hex_stripped" the EXACT same way. Our predefined color schemes are + // always RRGGBB without alpha so this is fine and keeps compatibility with matugen. + let script = "" + Object.keys(colors).forEach(colorKey => { + const colorValue = colors[colorKey].default.hex + const escapedColor = colorValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex\\(_stripped\\)\\?}}/${escapedColor}/g' '${filePath}'\n` + }) + return script + } + + // ================================================================================ + // TERMINAL THEMES (predefined schemes use pre-rendered files) + // ================================================================================ + function handleTerminalThemes(mode) { + const commands = [] + const homeDir = Quickshell.env("HOME") + + Object.keys(terminalPaths).forEach(terminal => { + if (Settings.data.templates[terminal]) { + const outputPath = terminalPaths[terminal].replace("~", homeDir) + const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) + const templatePath = getTerminalColorsTemplate(terminal, mode) + + commands.push(`mkdir -p ${outputDir}`) + commands.push(`cp -f ${templatePath} ${outputPath}`) + commands.push(`${TemplateRegistry.colorsApplyScript} ${terminal}`) + } + }) + + if (commands.length > 0) { + copyProcess.command = ["bash", "-lc", commands.join('; ')] + copyProcess.running = true + } + } + + function getTerminalColorsTemplate(terminal, mode) { + let colorScheme = Settings.data.colorSchemes.predefinedScheme + colorScheme = schemeNameMap[colorScheme] || colorScheme + + let extension = "" + if (terminal === 'kitty') { + extension = ".conf" + } else if (terminal === 'wezterm') { + extension = ".toml" + } + + return `${Quickshell.shellDir}/Assets/ColorScheme/${colorScheme}/terminal/${terminal}/${colorScheme}-${mode}${extension}` + } + + // ================================================================================ + // USER TEMPLATES + // ================================================================================ + function buildUserTemplateCommand(input, mode) { + if (!Settings.data.templates.enableUserTemplates) + return "" + + const userConfigPath = getUserConfigPath() + let script = "\n# Execute user config if it exists\n" + script += `if [ -f '${userConfigPath}' ]; then\n` + script += ` matugen image '${input}' --config '${userConfigPath}' --mode ${mode} --type ${Settings.data.colorSchemes.matugenSchemeType}\n` + script += "fi" + + return script + } + + function buildUserTemplateCommandForPredefined(schemeData, mode) { + if (!Settings.data.templates.enableUserTemplates) + return "" + + const userConfigPath = getUserConfigPath() + const colors = schemeData[mode] + const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false) + + const tempJsonPath = Settings.cacheDir + "predefined-colors.json" + const tempJsonPathEsc = tempJsonPath.replace(/'/g, "'\\''") + + let script = "\n# Execute user templates with predefined scheme colors\n" + script += `if [ -f '${userConfigPath}' ]; then\n` + script += ` cat > '${tempJsonPathEsc}' << 'EOF'\n` + script += JSON.stringify({ + "colors": palette + }, null, 2) + "\n" + script += "EOF\n" + script += ` matugen json '${tempJsonPathEsc}' --config '${userConfigPath}' --mode ${mode}\n` + script += "fi" + + return script + } + + function getUserConfigPath() { + return (Settings.configDir + "user-templates.toml").replace(/'/g, "'\\''") + } + + // ================================================================================ + // PROCESSES + // ================================================================================ + Process { + id: generateProcess + workingDirectory: Quickshell.shellDir + running: false + + onExited: function (exitCode) { + if (exitCode !== 0) { + const errorMsg = (stderr.text && stderr.text.trim() !== "") ? stderr.text.trim() : ((stdout.text && stdout.text.trim() !== "") ? stdout.text.trim() : I18n.tr("toast.matugen.failed-general")) + Logger.e("TemplateProcessor", "Process failed with exit code:", exitCode) + if (stderr.text && stderr.text.trim() !== "") { + Logger.e("TemplateProcessor", "stderr:", stderr.text) + } + if (stdout.text && stdout.text.trim() !== "") { + Logger.e("TemplateProcessor", "stdout:", stdout.text) + } + ToastService.showError(I18n.tr("toast.matugen.failed"), errorMsg, 6000) + } + } + + stdout: StdioCollector { + onStreamFinished: { + if (this.text) + Logger.d("TemplateProcessor", "stdout:", this.text) + } + } + stderr: StdioCollector { + onStreamFinished: { + if (this.text) + Logger.d("TemplateProcessor", "stderr:", this.text) + } + } + } + + Process { + id: copyProcess + workingDirectory: Quickshell.shellDir + running: false + stderr: StdioCollector { + onStreamFinished: { + if (this.text) + Logger.d("TemplateProcessor", "copyProcess stderr:", this.text) + } + } + } +} diff --git a/Services/TemplateRegistry.qml b/Services/TemplateRegistry.qml new file mode 100644 index 00000000..a4157f48 --- /dev/null +++ b/Services/TemplateRegistry.qml @@ -0,0 +1,234 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Singleton { + id: root + + readonly property string colorsApplyScript: Quickshell.shellDir + '/Bin/colors-apply.sh' + + // Terminal configurations (for wallpaper-based matugen templates) + readonly property var terminals: [{ + "id": "foot", + "name": "Foot", + "matugenPath": "Terminal/foot", + "outputPath": "~/.config/foot/themes/noctalia" + }, { + "id": "ghostty", + "name": "Ghostty", + "matugenPath": "Terminal/ghostty", + "outputPath": "~/.config/ghostty/themes/noctalia", + "postHook": "pkill -SIGUSR2 ghostty" + }, { + "id": "kitty", + "name": "Kitty", + "matugenPath": "Terminal/kitty.conf", + "outputPath": "~/.config/kitty/themes/noctalia.conf" + }, { + "id": "alacritty", + "name": "Alacritty", + "matugenPath": "Terminal/alacritty.toml", + "outputPath": "~/.config/alacritty/themes/noctalia.toml" + }, { + "id": "wezterm", + "name": "Wezterm", + "matugenPath": "Terminal/wezterm.toml", + "outputPath": "~/.config/wezterm/colors/Noctalia.toml", + "postHook": "touch ~/.config/wezterm/wezterm.lua" + }] + + // Application configurations - consolidated from MatugenTemplates + AppThemeService + readonly property var applications: [{ + "id": "gtk", + "name": "GTK", + "category": "ui", + "input": "gtk.css", + "outputs": [{ + "path": "~/.config/gtk-3.0/gtk.css" + }, { + "path": "~/.config/gtk-4.0/gtk.css" + }], + "postProcess": mode => `gsettings set org.gnome.desktop.interface color-scheme prefer-${mode}\n` + }, { + "id": "qt", + "name": "Qt", + "category": "ui", + "input": "qtct.conf", + "outputs": [{ + "path": "~/.config/qt5ct/colors/noctalia.conf" + }, { + "path": "~/.config/qt6ct/colors/noctalia.conf" + }] + }, { + "id": "kcolorscheme", + "name": "KColorScheme", + "category": "ui", + "input": "kcolorscheme.colors", + "outputs": [{ + "path": "~/.local/share/color-schemes/noctalia.colors" + }] + }, { + "id": "fuzzel", + "name": "Fuzzel", + "category": "launchers", + "input": "fuzzel.conf", + "outputs": [{ + "path": "~/.config/fuzzel/themes/noctalia" + }], + "postProcess": () => `${colorsApplyScript} fuzzel\n` + }, { + "id": "vicinae", + "name": "Vicinae", + "category": "launchers", + "input": "vicinae.toml", + "outputs": [{ + "path": "~/.local/share/vicinae/themes/matugen.toml" + }], + "postProcess": () => `cp -n ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae\n` + }, { + "id": "walker", + "name": "Walker", + "category": "launchers", + "input": "walker.css", + "outputs": [{ + "path": "~/.config/walker/themes/noctalia/style.css" + }], + "postProcess": () => `${colorsApplyScript} walker\n`, + "strict": true // Use strict mode for palette generation (preserves custom surface/outline values) + }, { + "id": "pywalfox", + "name": "Pywalfox", + "category": "applications", + "input": "pywalfox.json", + "outputs": [{ + "path": "~/.cache/wal/colors.json" + }], + "postProcess": () => `${colorsApplyScript} pywalfox\n` + }, // CONSOLIDATED DISCORD CLIENTS (requirement #2) + { + "id": "discord", + "name": "Discord", + "category": "applications", + "input": "vesktop.css", + "clients": [{ + "name": "vesktop", + "path": "~/.config/vesktop", + "requiresThemesFolder": false + }, { + "name": "webcord", + "path": "~/.config/webcord", + "requiresThemesFolder": false + }, { + "name": "armcord", + "path": "~/.config/armcord", + "requiresThemesFolder": false + }, { + "name": "equibop", + "path": "~/.config/equibop", + "requiresThemesFolder": false + }, { + "name": "lightcord", + "path": "~/.config/lightcord", + "requiresThemesFolder": false + }, { + "name": "dorion", + "path": "~/.config/dorion", + "requiresThemesFolder": false + }, { + "name": "vencord", + "path": "~/.config/discord", + "requiresThemesFolder": true + }] + }, // VSCode with hardcoded path (requirement #5) + { + "id": "code", + "name": "VSCode", + "category": "applications", + "input": "code.json", + "outputs": [{ + "path": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" + }] + }] + + // Extract Discord clients for ProgramCheckerService compatibility + readonly property var discordClients: { + var clients = [] + var discordApp = applications.find(app => app.id === "discord") + if (discordApp && discordApp.clients) { + discordApp.clients.forEach(client => { + clients.push({ + "name": client.name, + "configPath": client.path, + "themePath": `${client.path}/themes/noctalia.theme.css`, + "requiresThemesFolder": client.requiresThemesFolder || false + }) + }) + } + return clients + } + + // Build user templates TOML content + function buildUserTemplatesToml() { + var lines = [] + lines.push("[config]") + lines.push("") + lines.push("[templates]") + lines.push("") + lines.push("# User-defined templates") + lines.push("# Add your custom templates below") + lines.push("# Example:") + lines.push("# [templates.myapp]") + lines.push("# input_path = \"~/.config/noctalia/templates/myapp.css\"") + lines.push("# output_path = \"~/.config/myapp/theme.css\"") + lines.push("# post_hook = \"myapp --reload-theme\"") + lines.push("") + lines.push("# Remove this section and add your own templates") + lines.push("#[templates.placeholder]") + lines.push("#input_path = \"" + Quickshell.shellDir + "/Assets/MatugenTemplates/noctalia.json\"") + lines.push("#output_path = \"" + Settings.cacheDir + "placeholder.json\"") + lines.push("") + + return lines.join("\n") + "\n" + } + + // Write user templates TOML file (moved from MatugenTemplates) + function writeUserTemplatesToml() { + var userConfigPath = Settings.configDir + "user-templates.toml" + + // Check if file already exists + fileCheckProcess.command = ["test", "-f", userConfigPath] + fileCheckProcess.running = true + } + + function doWriteUserTemplatesToml() { + var userConfigPath = Settings.configDir + "user-templates.toml" + var configContent = buildUserTemplatesToml() + + // Ensure directory exists (should already exist but just in case) + Quickshell.execDetached(["mkdir", "-p", Settings.configDir]) + + // Write the config file + Quickshell.execDetached(["sh", "-c", `echo '${configContent.replace(/'/g, "'\\''")}' > '${userConfigPath}'`]) + + Logger.d("TemplateRegistry", "User templates config written to:", userConfigPath) + } + + // Process for checking if user templates file exists + Process { + id: fileCheckProcess + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + // File exists, skip creation + Logger.d("TemplateRegistry", "User templates config already exists, skipping creation") + } else { + // File doesn't exist, create it + doWriteUserTemplatesToml() + } + } + } +} From 990291f111ebc1b2fc822637ce0bf57681894026 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 22:32:50 +0100 Subject: [PATCH 018/107] TrayMenuPanel: increase width before eliding --- Modules/Panels/Tray/TrayMenuPanel.qml | 232 +++++++++++++++++++++++++- 1 file changed, 231 insertions(+), 1 deletion(-) diff --git a/Modules/Panels/Tray/TrayMenuPanel.qml b/Modules/Panels/Tray/TrayMenuPanel.qml index c55115c0..363bbf20 100644 --- a/Modules/Panels/Tray/TrayMenuPanel.qml +++ b/Modules/Panels/Tray/TrayMenuPanel.qml @@ -17,13 +17,39 @@ SmartPanel { property int widgetIndex: -1 // Internal - readonly property int menuWidth: 280 + property int menuWidth: 280 + readonly property int maxMenuWidth: 400 // Maximum width before text elides preferredWidth: menuWidth // Height is content-driven via panelContent // Open positioned relative to button function openAt(buttonItem) { open(buttonItem) + // Recalculate width when opening + Qt.callLater(() => { + if (panelContent) { + const calculatedWidth = panelContent.calculateMaxEntryWidth() + if (calculatedWidth > 0) { + // Remove outer padding to get entry width + panelContent.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2)) + } + } + }) + } + + // Recalculate width when panel becomes visible + onVisibleChanged: { + if (visible) { + Qt.callLater(() => { + if (panelContent) { + const calculatedWidth = panelContent.calculateMaxEntryWidth() + if (calculatedWidth > 0) { + // Remove outer padding to get entry width + panelContent.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2)) + } + } + }) + } } panelContent: Item { @@ -35,6 +61,74 @@ SmartPanel { // Only show submenu in place (replace main menu) property bool inPlaceSubmenu: true + // TextMetrics for measuring text widths + TextMetrics { + id: textMetrics + font.pointSize: Style.fontSizeS + } + + // Track maximum entry width + property real maxEntryWidth: 280 + + // Calculate maximum entry width + function calculateMaxEntryWidth() { + if (!opener || !opener.children) { + return 280 // fallback + } + + let maxWidth = 0 + const entries = [...opener.children.values] + + for (var i = 0; i < entries.length; i++) { + const entry = entries[i] + if (!entry || entry.isSeparator) + continue + + // Calculate entry width + let entryWidth = 0 + + // Left margin + entryWidth += Style.marginM + + // Text width + const text = entry.text !== "" ? entry.text.replace(/[\n\r]+/g, ' ') : "..." + textMetrics.text = text + entryWidth += textMetrics.width + + // Spacing + entryWidth += Style.marginS + + // Icon (if visible) + if (entry.icon && entry.icon !== "") { + entryWidth += Style.marginL + entryWidth += Style.marginS + } + + // Menu icon (if has children) + if (entry.hasChildren) { + // Approximate icon width based on font size + entryWidth += Style.fontSizeS * 1.2 // Approximate icon width + entryWidth += Style.marginS + } + + // Right margin + entryWidth += Style.marginM + + if (entryWidth > maxWidth) { + maxWidth = entryWidth + } + } + + // Add outer padding (Style.marginM * 2) + return Math.max(280, maxWidth + (Style.marginM * 2)) + } + + // Update menu width when maxEntryWidth changes + onMaxEntryWidthChanged: { + const calculatedWidth = maxEntryWidth + (Style.marginM * 2) + root.menuWidth = Math.min(root.maxMenuWidth, Math.max(280, calculatedWidth)) + } + // Let Panel size to our content readonly property real contentPreferredWidth: root.menuWidth readonly property real contentPreferredHeight: { @@ -52,6 +146,31 @@ SmartPanel { QsMenuOpener { id: opener menu: root.menu + + // Update menu width when children change + onChildrenChanged: { + Qt.callLater(() => { + const calculatedWidth = content.calculateMaxEntryWidth() + if (calculatedWidth > 0) { + // Remove outer padding to get entry width + content.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2)) + } + }) + } + } + + // Update width when menu changes + Connections { + target: root + function onMenuChanged() { + Qt.callLater(() => { + const calculatedWidth = content.calculateMaxEntryWidth() + if (calculatedWidth > 0) { + // Remove outer padding to get entry width + content.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2)) + } + }) + } } Component { @@ -202,6 +321,46 @@ SmartPanel { color: Color.transparent + // Calculate and update max entry width + Component.onCompleted: { + if (!modelData?.isSeparator) { + Qt.callLater(() => { + let entryWidth = 0 + + // Left margin + entryWidth += Style.marginM + + // Text width + const text = modelData?.text !== "" ? modelData.text.replace(/[\n\r]+/g, ' ') : "..." + content.textMetrics.text = text + entryWidth += content.textMetrics.width + + // Spacing + entryWidth += Style.marginS + + // Icon (if visible) + if (modelData?.icon && modelData.icon !== "") { + entryWidth += Style.marginL + entryWidth += Style.marginS + } + + // Menu icon (if has children) + if (modelData?.hasChildren) { + entryWidth += Style.fontSizeS * 1.2 // Approximate icon width + entryWidth += Style.marginL // Submenu uses marginL for right margin on icon + } + + // Right margin + entryWidth += Style.marginM + + // Update maxEntryWidth if this entry is wider + if (entryWidth > content.maxEntryWidth) { + content.maxEntryWidth = entryWidth + } + }) + } + } + NDivider { anchors.centerIn: parent width: parent.width - (Style.marginM * 2) @@ -338,6 +497,46 @@ SmartPanel { color: Color.transparent + // Calculate and update max entry width + Component.onCompleted: { + if (!modelData?.isSeparator) { + Qt.callLater(() => { + let entryWidth = 0 + + // Left margin + entryWidth += Style.marginM + + // Text width + const text = modelData?.text !== "" ? modelData.text.replace(/[\n\r]+/g, ' ') : "..." + content.textMetrics.text = text + entryWidth += content.textMetrics.width + + // Spacing + entryWidth += Style.marginS + + // Icon (if visible) + if (modelData?.icon && modelData.icon !== "") { + entryWidth += Style.marginL + entryWidth += Style.marginS + } + + // Menu icon (if has children) + if (modelData?.hasChildren) { + entryWidth += Style.fontSizeS * 1.2 // Approximate icon width + entryWidth += Style.marginS + } + + // Right margin + entryWidth += Style.marginM + + // Update maxEntryWidth if this entry is wider + if (entryWidth > content.maxEntryWidth) { + content.maxEntryWidth = entryWidth + } + }) + } + } + NDivider { anchors.centerIn: parent width: parent.width - Style.marginL @@ -453,6 +652,37 @@ SmartPanel { Layout.preferredHeight: visible ? 28 : 0 color: Color.transparent + // Calculate and update max entry width + Component.onCompleted: { + if (visible) { + Qt.callLater(() => { + let entryWidth = 0 + + // Left margin + entryWidth += Style.marginM + + // Icon width + entryWidth += Style.fontSizeS * 1.2 // Approximate icon width + + // Spacing + entryWidth += Style.marginS + + // Text width + const text = isFavorite ? I18n.tr("settings.bar.tray.unpin-application") : I18n.tr("settings.bar.tray.pin-application") + content.textMetrics.text = text + entryWidth += content.textMetrics.width + + // Right margin + entryWidth += Style.marginM + + // Update maxEntryWidth if this entry is wider + if (entryWidth > content.maxEntryWidth) { + content.maxEntryWidth = entryWidth + } + }) + } + } + readonly property bool isFavorite: { if (!root.trayItem || root.widgetSection === "" || root.widgetIndex < 0) return false From 355271768cabcd34058838ecaa510fdc06215a85 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 16:38:43 -0500 Subject: [PATCH 019/107] Services in subfolder + cleanup/fixes --- Assets/settings-default.json | 2 +- Commons/KeyboardLayout.qml | 1 + Commons/Settings.qml | 2 +- Commons/Style.qml | 4 +--- Modules/Background/Background.qml | 7 ++++--- Modules/Background/Overview.qml | 3 ++- Modules/Bar/Bar.qml | 2 +- Modules/Bar/Extras/BarPill.qml | 1 - Modules/Bar/Extras/BarPillHorizontal.qml | 2 +- Modules/Bar/Extras/BarPillVertical.qml | 2 +- Modules/Bar/Extras/BarWidgetLoader.qml | 2 +- Modules/Bar/Widgets/ActiveWindow.qml | 4 +--- Modules/Bar/Widgets/AudioVisualizer.qml | 5 +++-- Modules/Bar/Widgets/Battery.qml | 3 ++- Modules/Bar/Widgets/Bluetooth.qml | 2 +- Modules/Bar/Widgets/Brightness.qml | 2 +- Modules/Bar/Widgets/Clock.qml | 2 +- Modules/Bar/Widgets/ControlCenter.qml | 6 +++--- Modules/Bar/Widgets/CustomButton.qml | 2 +- Modules/Bar/Widgets/DarkMode.qml | 2 +- Modules/Bar/Widgets/KeepAwake.qml | 2 +- Modules/Bar/Widgets/KeyboardLayout.qml | 3 ++- Modules/Bar/Widgets/LockKeys.qml | 3 ++- Modules/Bar/Widgets/MediaMini.qml | 3 ++- Modules/Bar/Widgets/Microphone.qml | 6 +++--- Modules/Bar/Widgets/NightLight.qml | 2 +- Modules/Bar/Widgets/NoctaliaPerformance.qml | 2 +- Modules/Bar/Widgets/NotificationHistory.qml | 3 ++- Modules/Bar/Widgets/PowerProfile.qml | 2 +- Modules/Bar/Widgets/ScreenRecorder.qml | 4 +++- Modules/Bar/Widgets/SessionMenu.qml | 1 - Modules/Bar/Widgets/Spacer.qml | 1 - Modules/Bar/Widgets/SystemMonitor.qml | 3 ++- Modules/Bar/Widgets/Taskbar.qml | 3 ++- Modules/Bar/Widgets/TaskbarGrouped.qml | 3 ++- Modules/Bar/Widgets/Tray.qml | 2 +- Modules/Bar/Widgets/Volume.qml | 7 ++++--- Modules/Bar/Widgets/WallpaperSelector.qml | 2 +- Modules/Bar/Widgets/WiFi.qml | 2 +- Modules/Bar/Widgets/Workspace.qml | 3 ++- Modules/Dock/Dock.qml | 2 +- Modules/Dock/DockMenu.qml | 1 - Modules/LockScreen/LockScreen.qml | 1 - Modules/MainScreen/AllScreens.qml | 2 +- Modules/MainScreen/MainScreen.qml | 13 +------------ Modules/MainScreen/ScreenCorners.qml | 1 - Modules/MainScreen/SmartPanel.qml | 2 +- Modules/Notification/Notification.qml | 2 +- Modules/OSD/OSD.qml | 4 +++- Modules/Panels/Audio/AudioPanel.qml | 2 +- Modules/Panels/Battery/BatteryPanel.qml | 2 +- Modules/Panels/Bluetooth/BluetoothDevicesList.qml | 2 +- Modules/Panels/Bluetooth/BluetoothPanel.qml | 6 +++--- Modules/Panels/Calendar/CalendarPanel.qml | 5 +++-- Modules/Panels/ControlCenter/Cards/AudioCard.qml | 2 +- Modules/Panels/ControlCenter/Cards/MediaCard.qml | 5 +++-- Modules/Panels/ControlCenter/Cards/ProfileCard.qml | 4 ++-- .../Panels/ControlCenter/Cards/ShortcutsCard.qml | 1 - .../ControlCenter/Cards/SystemMonitorCard.qml | 2 +- Modules/Panels/ControlCenter/Cards/WeatherCard.qml | 2 +- .../Panels/ControlCenter/ControlCenterPanel.qml | 5 +++-- .../ControlCenter/ControlCenterWidgetLoader.qml | 2 +- Modules/Panels/ControlCenter/Widgets/Bluetooth.qml | 3 ++- .../Panels/ControlCenter/Widgets/CustomButton.qml | 1 - Modules/Panels/ControlCenter/Widgets/KeepAwake.qml | 2 +- .../Panels/ControlCenter/Widgets/NightLight.qml | 3 ++- .../Panels/ControlCenter/Widgets/Notifications.qml | 3 ++- .../Panels/ControlCenter/Widgets/PowerProfile.qml | 6 ++---- .../ControlCenter/Widgets/ScreenRecorder.qml | 5 ++--- .../ControlCenter/Widgets/WallpaperSelector.qml | 2 +- Modules/Panels/ControlCenter/Widgets/WiFi.qml | 3 ++- Modules/Panels/Launcher/Launcher.qml | 5 ++--- .../Panels/Launcher/Plugins/ApplicationsPlugin.qml | 1 - .../Panels/Launcher/Plugins/CalculatorPlugin.qml | 1 - .../Panels/Launcher/Plugins/ClipboardPlugin.qml | 2 +- .../NotificationHistoryPanel.qml | 2 +- Modules/Panels/SessionMenu/SessionMenu.qml | 2 +- .../Settings/Bar/BarWidgetSettingsDialog.qml | 2 +- .../ControlCenterWidgetSettingsDialog.qml | 2 +- Modules/Panels/Settings/SettingsPanel.qml | 5 ++--- Modules/Panels/Settings/Tabs/AboutTab.qml | 2 +- Modules/Panels/Settings/Tabs/AudioTab.qml | 2 +- Modules/Panels/Settings/Tabs/BarTab.qml | 3 ++- Modules/Panels/Settings/Tabs/ColorSchemeTab.qml | 4 +++- Modules/Panels/Settings/Tabs/ControlCenterTab.qml | 2 +- Modules/Panels/Settings/Tabs/DisplayTab.qml | 4 +++- Modules/Panels/Settings/Tabs/DockTab.qml | 2 +- Modules/Panels/Settings/Tabs/GeneralTab.qml | 2 +- Modules/Panels/Settings/Tabs/HooksTab.qml | 3 ++- Modules/Panels/Settings/Tabs/LauncherTab.qml | 2 +- Modules/Panels/Settings/Tabs/LocationTab.qml | 2 +- Modules/Panels/Settings/Tabs/NetworkTab.qml | 3 +-- Modules/Panels/Settings/Tabs/NotificationsTab.qml | 2 +- Modules/Panels/Settings/Tabs/OsdTab.qml | 2 +- Modules/Panels/Settings/Tabs/WallpaperTab.qml | 3 ++- Modules/Panels/SetupWizard/SetupAppearanceStep.qml | 1 - Modules/Panels/SetupWizard/SetupCustomizeStep.qml | 1 - Modules/Panels/SetupWizard/SetupDockStep.qml | 1 - Modules/Panels/SetupWizard/SetupWallpaperStep.qml | 1 - Modules/Panels/SetupWizard/SetupWizard.qml | 1 - Modules/Panels/Tray/TrayDrawerPanel.qml | 2 +- Modules/Panels/Tray/TrayMenuPanel.qml | 1 - Modules/Panels/Wallpaper/WallpaperPanel.qml | 7 +++---- Modules/Panels/WiFi/WiFiPanel.qml | 5 ++--- Modules/Toast/ToastOverlay.qml | 1 - Modules/Toast/ToastScreen.qml | 7 +++---- Modules/Tooltip/Tooltip.qml | 3 +-- Services/{ => Compositor}/CompositorService.qml | 1 - Services/{ => Compositor}/HyprlandService.qml | 0 Services/{ => Compositor}/NiriService.qml | 0 Services/{ => Compositor}/SwayService.qml | 0 Services/{ => Control}/HooksService.qml | 2 +- Services/{ => Control}/IPCService.qml | 2 +- Services/{ => DynamicTheming}/AppThemeService.qml | 1 + .../{ => DynamicTheming}/ColorPaletteGenerator.qml | 2 +- .../{ => DynamicTheming}/ColorSchemeService.qml | 1 - .../{ => DynamicTheming}/TemplateProcessor.qml | 2 ++ Services/{ => DynamicTheming}/TemplateRegistry.qml | 12 ++++++------ Services/{ => Hardware}/BatteryService.qml | 2 +- Services/{ => Hardware}/BrightnessService.qml | 0 Services/{ => Keyboard}/ClipboardService.qml | 0 Services/{ => Keyboard}/KeyboardLayoutService.qml | 1 - Services/{ => Keyboard}/LockKeysService.qml | 0 Services/{ => Location}/CalendarService.qml | 0 Services/{ => Location}/DarkModeService.qml | 1 - Services/{ => Location}/LocationService.qml | 1 - Services/{ => Location}/NightLightService.qml | 1 - Services/{ => Media}/AudioService.qml | 0 Services/{ => Media}/CavaService.qml | 1 + Services/{ => Media}/MediaService.qml | 1 - Services/{ => Media}/ScreenRecorderService.qml | 2 +- Services/{ => Networking}/BluetoothService.qml | 1 - Services/{ => Networking}/NetworkService.qml | 2 +- Services/{ => Noctalia}/GitHubService.qml | 1 - Services/{ => Noctalia}/UpdateService.qml | 0 Services/{ => Power}/IdleInhibitorService.qml | 2 +- Services/{ => Power}/PowerProfileService.qml | 2 +- Services/{ => System}/DistroService.qml | 0 Services/{ => System}/FontService.qml | 0 Services/{ => System}/NotificationService.qml | 4 ++-- Services/{ => System}/ProgramCheckerService.qml | 1 + Services/{ => System}/SystemStatService.qml | 0 Services/{ => UI}/BarService.qml | 0 Services/{ => UI}/BarWidgetRegistry.qml | 14 +++++++++----- Services/{ => UI}/ControlCenterWidgetRegistry.qml | 0 Services/{ => UI}/PanelService.qml | 0 Services/{ => UI}/ToastService.qml | 0 Services/{ => UI}/TooltipService.qml | 0 Services/{ => UI}/WallpaperService.qml | 0 Widgets/NButton.qml | 2 +- Widgets/NColorPickerDialog.qml | 1 - Widgets/NComboBox.qml | 1 - Widgets/NContextMenu.qml | 1 - Widgets/NDropShadows.qml | 2 +- Widgets/NFilePicker.qml | 1 - Widgets/NIconButton.qml | 2 +- Widgets/NIconButtonHot.qml | 2 +- Widgets/NIconPicker.qml | 6 +++--- Widgets/NSearchableComboBox.qml | 1 - Widgets/NSlider.qml | 2 +- shell.qml | 11 ++++++++--- 161 files changed, 191 insertions(+), 201 deletions(-) rename Services/{ => Compositor}/CompositorService.qml (99%) rename Services/{ => Compositor}/HyprlandService.qml (100%) rename Services/{ => Compositor}/NiriService.qml (100%) rename Services/{ => Compositor}/SwayService.qml (100%) rename Services/{ => Control}/HooksService.qml (98%) rename Services/{ => Control}/IPCService.qml (99%) rename Services/{ => DynamicTheming}/AppThemeService.qml (98%) rename Services/{ => DynamicTheming}/ColorPaletteGenerator.qml (98%) rename Services/{ => DynamicTheming}/ColorSchemeService.qml (99%) rename Services/{ => DynamicTheming}/TemplateProcessor.qml (99%) rename Services/{ => DynamicTheming}/TemplateRegistry.qml (96%) rename Services/{ => Hardware}/BatteryService.qml (99%) rename Services/{ => Hardware}/BrightnessService.qml (100%) rename Services/{ => Keyboard}/ClipboardService.qml (100%) rename Services/{ => Keyboard}/KeyboardLayoutService.qml (99%) rename Services/{ => Keyboard}/LockKeysService.qml (100%) rename Services/{ => Location}/CalendarService.qml (100%) rename Services/{ => Location}/DarkModeService.qml (99%) rename Services/{ => Location}/LocationService.qml (99%) rename Services/{ => Location}/NightLightService.qml (99%) rename Services/{ => Media}/AudioService.qml (100%) rename Services/{ => Media}/CavaService.qml (99%) rename Services/{ => Media}/MediaService.qml (99%) rename Services/{ => Media}/ScreenRecorderService.qml (99%) rename Services/{ => Networking}/BluetoothService.qml (99%) rename Services/{ => Networking}/NetworkService.qml (99%) rename Services/{ => Noctalia}/GitHubService.qml (99%) rename Services/{ => Noctalia}/UpdateService.qml (100%) rename Services/{ => Power}/IdleInhibitorService.qml (99%) rename Services/{ => Power}/PowerProfileService.qml (99%) rename Services/{ => System}/DistroService.qml (100%) rename Services/{ => System}/FontService.qml (100%) rename Services/{ => System}/NotificationService.qml (99%) rename Services/{ => System}/ProgramCheckerService.qml (99%) rename Services/{ => System}/SystemStatService.qml (100%) rename Services/{ => UI}/BarService.qml (100%) rename Services/{ => UI}/BarWidgetRegistry.qml (96%) rename Services/{ => UI}/ControlCenterWidgetRegistry.qml (100%) rename Services/{ => UI}/PanelService.qml (100%) rename Services/{ => UI}/ToastService.qml (100%) rename Services/{ => UI}/TooltipService.qml (100%) rename Services/{ => UI}/WallpaperService.qml (100%) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 2c83016d..fabf4225 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -1,5 +1,5 @@ { - "settingsVersion": 20, + "settingsVersion": 21, "setupCompleted": false, "bar": { "position": "top", diff --git a/Commons/KeyboardLayout.qml b/Commons/KeyboardLayout.qml index 38d08aa1..9b090d21 100644 --- a/Commons/KeyboardLayout.qml +++ b/Commons/KeyboardLayout.qml @@ -2,6 +2,7 @@ pragma Singleton import QtQuick +// TODO: Move to KeyboardLayoutService. QtObject { id: root diff --git a/Commons/Settings.qml b/Commons/Settings.qml index a38f5082..6e2147f5 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -4,7 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.UI import "../Helpers/QtObj2JS.js" as QtObj2JS Singleton { diff --git a/Commons/Style.qml b/Commons/Style.qml index bb06ff05..b2999d7b 100644 --- a/Commons/Style.qml +++ b/Commons/Style.qml @@ -2,9 +2,7 @@ pragma Singleton import QtQuick import Quickshell -import Quickshell.Io - -import qs.Services +import qs.Services.Power Singleton { id: root diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 9c93b40b..f6b97bdd 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -2,7 +2,8 @@ import QtQuick import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.Compositor +import qs.Services.UI Variants { id: backgroundVariants @@ -109,7 +110,7 @@ Variants { property bool dimensionsCalculated: false source: "" - smooth: true + smooth: false mipmap: false visible: false cache: false @@ -138,7 +139,7 @@ Variants { property bool dimensionsCalculated: false source: "" - smooth: true + smooth: false mipmap: false visible: false cache: false diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index a5ee733d..6dcbd3bc 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -3,7 +3,8 @@ import Quickshell import QtQuick.Effects import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.Compositor +import qs.Services.UI Loader { active: CompositorService.isNiri && Settings.data.wallpaper.enabled && Settings.data.wallpaper.overviewEnabled diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index d858da2c..2d472595 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -5,7 +5,7 @@ import Quickshell import Quickshell.Wayland import Quickshell.Services.UPower import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets import qs.Modules.Notification import qs.Modules.Bar.Extras diff --git a/Modules/Bar/Extras/BarPill.qml b/Modules/Bar/Extras/BarPill.qml index aef33339..d5befb21 100644 --- a/Modules/Bar/Extras/BarPill.qml +++ b/Modules/Bar/Extras/BarPill.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import Quickshell import qs.Commons -import qs.Services import qs.Widgets Item { diff --git a/Modules/Bar/Extras/BarPillHorizontal.qml b/Modules/Bar/Extras/BarPillHorizontal.qml index c9d84869..ed74b74b 100644 --- a/Modules/Bar/Extras/BarPillHorizontal.qml +++ b/Modules/Bar/Extras/BarPillHorizontal.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Controls import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets Item { diff --git a/Modules/Bar/Extras/BarPillVertical.qml b/Modules/Bar/Extras/BarPillVertical.qml index 492ec735..ea1001a7 100644 --- a/Modules/Bar/Extras/BarPillVertical.qml +++ b/Modules/Bar/Extras/BarPillVertical.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Controls import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets Item { diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml index fb90c645..71f21414 100644 --- a/Modules/Bar/Extras/BarWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -1,6 +1,6 @@ import QtQuick import Quickshell -import qs.Services +import qs.Services.UI import qs.Commons Item { diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 8772c5b4..d02e92a3 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -1,11 +1,9 @@ import QtQuick -import QtQuick.Controls import QtQuick.Layouts import Quickshell -import Quickshell.Wayland import Quickshell.Widgets import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets Item { diff --git a/Modules/Bar/Widgets/AudioVisualizer.qml b/Modules/Bar/Widgets/AudioVisualizer.qml index a83c9e52..cecc6952 100644 --- a/Modules/Bar/Widgets/AudioVisualizer.qml +++ b/Modules/Bar/Widgets/AudioVisualizer.qml @@ -1,9 +1,10 @@ import QtQuick import Quickshell import qs.Commons -import qs.Widgets.AudioSpectrum -import qs.Services +import qs.Services.UI +import qs.Services.Media import qs.Widgets +import qs.Widgets.AudioSpectrum Item { id: root diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index 7eb2a714..08488fe5 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -3,7 +3,8 @@ import Quickshell import Quickshell.Services.UPower import QtQuick.Layouts import qs.Commons -import qs.Services +import qs.Services.UI +import qs.Services.Hardware import qs.Widgets import qs.Modules.Bar.Extras diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 84ca7f41..8c3bb1d8 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -1,7 +1,7 @@ import QtQuick import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Modules.Bar.Extras Item { diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index 615ec16e..a94383e1 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -3,7 +3,7 @@ import Quickshell import qs.Commons import qs.Modules.Bar.Extras import qs.Modules.Panels.Settings -import qs.Services +import qs.Services.UI import qs.Widgets Item { diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml index 8a31bdc3..4eb93dba 100644 --- a/Modules/Bar/Widgets/Clock.qml +++ b/Modules/Bar/Widgets/Clock.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets Rectangle { diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index d703a05f..fc19e980 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -4,7 +4,7 @@ import Quickshell.Widgets import QtQuick.Effects import qs.Commons import qs.Widgets -import qs.Services +import qs.Services.UI NIconButton { id: root @@ -48,9 +48,9 @@ NIconButton { var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen) if (Settings.data.controlCenter.position === "close_to_bar_button") { // Willopen the panel next to the bar button. - controlCenterPanel.toggle(this) + controlCenterPanel?.toggle(this) } else { - controlCenterPanel.toggle() + controlCenterPanel?.toggle() } } onRightClicked: PanelService.getPanel("settingsPanel", screen)?.toggle() diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index ab8c3982..7e474fde 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -3,7 +3,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets import qs.Modules.Panels.Settings import qs.Modules.Bar.Extras diff --git a/Modules/Bar/Widgets/DarkMode.qml b/Modules/Bar/Widgets/DarkMode.qml index d9a2cb25..170b3f5f 100644 --- a/Modules/Bar/Widgets/DarkMode.qml +++ b/Modules/Bar/Widgets/DarkMode.qml @@ -1,7 +1,7 @@ import Quickshell import qs.Commons import qs.Widgets -import qs.Services +import qs.Services.UI NIconButton { id: root diff --git a/Modules/Bar/Widgets/KeepAwake.qml b/Modules/Bar/Widgets/KeepAwake.qml index c0546884..58b30d6f 100644 --- a/Modules/Bar/Widgets/KeepAwake.qml +++ b/Modules/Bar/Widgets/KeepAwake.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/KeyboardLayout.qml b/Modules/Bar/Widgets/KeyboardLayout.qml index 41ffd027..e00fd2d6 100644 --- a/Modules/Bar/Widgets/KeyboardLayout.qml +++ b/Modules/Bar/Widgets/KeyboardLayout.qml @@ -4,7 +4,8 @@ import Quickshell import Quickshell.Wayland import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.UI +import qs.Services.Keyboard import qs.Widgets import qs.Modules.Bar.Extras diff --git a/Modules/Bar/Widgets/LockKeys.qml b/Modules/Bar/Widgets/LockKeys.qml index 3fd5b8fb..a13dbd9b 100644 --- a/Modules/Bar/Widgets/LockKeys.qml +++ b/Modules/Bar/Widgets/LockKeys.qml @@ -4,7 +4,8 @@ import Quickshell import Quickshell.Io import qs.Commons import qs.Modules.Panels.Settings -import qs.Services +import qs.Services.Keyboard +import qs.Services.UI import qs.Widgets //import qs.Modules.Bar.Extras diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 9d626284..0867514d 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -4,7 +4,8 @@ import QtQuick.Layouts import Quickshell import qs.Widgets.AudioSpectrum import qs.Commons -import qs.Services +import qs.Services.Media +import qs.Services.UI import qs.Widgets Item { diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index 23946dfb..b3e32915 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -3,10 +3,10 @@ import Quickshell import Quickshell.Io import Quickshell.Services.Pipewire import qs.Commons -import qs.Modules.Panels.Settings -import qs.Services -import qs.Widgets import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings +import qs.Services.UI +import qs.Widgets Item { id: root diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml index 5524a5bd..2be0a047 100644 --- a/Modules/Bar/Widgets/NightLight.qml +++ b/Modules/Bar/Widgets/NightLight.qml @@ -5,7 +5,7 @@ import Quickshell import Quickshell.Wayland import qs.Commons import qs.Modules.Panels.Settings -import qs.Services +import qs.Services.UI import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/NoctaliaPerformance.qml b/Modules/Bar/Widgets/NoctaliaPerformance.qml index 443832fc..7678e92f 100644 --- a/Modules/Bar/Widgets/NoctaliaPerformance.qml +++ b/Modules/Bar/Widgets/NoctaliaPerformance.qml @@ -5,7 +5,7 @@ import Quickshell import Quickshell.Wayland import qs.Commons import qs.Modules.Panels.Settings -import qs.Services +import qs.Services.Power import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index 55f41d9b..dbd9bea9 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -4,7 +4,8 @@ import QtQuick.Controls import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.UI +import qs.Services.System import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/PowerProfile.qml b/Modules/Bar/Widgets/PowerProfile.qml index da96fcf1..e9bca98a 100644 --- a/Modules/Bar/Widgets/PowerProfile.qml +++ b/Modules/Bar/Widgets/PowerProfile.qml @@ -3,7 +3,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Services.UPower import qs.Commons -import qs.Services +import qs.Services.Power import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/ScreenRecorder.qml b/Modules/Bar/Widgets/ScreenRecorder.qml index 4e528c2f..8683f583 100644 --- a/Modules/Bar/Widgets/ScreenRecorder.qml +++ b/Modules/Bar/Widgets/ScreenRecorder.qml @@ -1,6 +1,8 @@ import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI +import qs.Services.Media +import qs.Services.System import qs.Widgets // Screen Recording Indicator diff --git a/Modules/Bar/Widgets/SessionMenu.qml b/Modules/Bar/Widgets/SessionMenu.qml index d1b79998..a22b046c 100644 --- a/Modules/Bar/Widgets/SessionMenu.qml +++ b/Modules/Bar/Widgets/SessionMenu.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/Spacer.qml b/Modules/Bar/Widgets/Spacer.qml index 3af0e792..ff94682e 100644 --- a/Modules/Bar/Widgets/Spacer.qml +++ b/Modules/Bar/Widgets/Spacer.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services import qs.Widgets Item { diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index 0adec65d..caab8fda 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -3,7 +3,8 @@ import QtQuick.Layouts import Quickshell import qs.Commons import qs.Modules.Panels.Settings -import qs.Services +import qs.Services.UI +import qs.Services.System import qs.Widgets Rectangle { diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index 959dc2df..30b41e72 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -5,7 +5,8 @@ import Quickshell import Quickshell.Widgets import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.Compositor +import qs.Services.UI import qs.Widgets Rectangle { diff --git a/Modules/Bar/Widgets/TaskbarGrouped.qml b/Modules/Bar/Widgets/TaskbarGrouped.qml index 73895274..ccf0ef41 100644 --- a/Modules/Bar/Widgets/TaskbarGrouped.qml +++ b/Modules/Bar/Widgets/TaskbarGrouped.qml @@ -5,7 +5,8 @@ import Quickshell import Quickshell.Wayland import Quickshell.Widgets import qs.Commons -import qs.Services +import qs.Services.Compositor +import qs.Services.UI import qs.Widgets Item { diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 7971eac6..7f1a3921 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -7,7 +7,7 @@ import Quickshell.Services.SystemTray import Quickshell.Widgets import qs.Commons import qs.Modules.Bar.Extras -import qs.Services +import qs.Services.UI import qs.Widgets Rectangle { diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index 5a178e37..9fb72180 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -3,10 +3,11 @@ import Quickshell import Quickshell.Io import Quickshell.Services.Pipewire import qs.Commons -import qs.Modules.Panels.Settings -import qs.Services -import qs.Widgets import qs.Modules.Bar.Extras +import qs.Modules.Panels.Settings +import qs.Services.UI +import qs.Services.Media +import qs.Widgets Item { id: root diff --git a/Modules/Bar/Widgets/WallpaperSelector.qml b/Modules/Bar/Widgets/WallpaperSelector.qml index 322b54c0..1dbbdff4 100644 --- a/Modules/Bar/Widgets/WallpaperSelector.qml +++ b/Modules/Bar/Widgets/WallpaperSelector.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index 77000e83..73e731a1 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -1,7 +1,7 @@ import QtQuick import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Modules.Bar.Extras Item { diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index 0ceec88e..f30af56f 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -6,7 +6,8 @@ import QtQuick.Effects import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.Compositor +import qs.Services.UI import qs.Widgets Item { diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 78ba4c38..5f8c5ae8 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -6,7 +6,7 @@ import Quickshell import Quickshell.Wayland import Quickshell.Widgets import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets Loader { diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index 17fcbd29..ec2cb15f 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -5,7 +5,6 @@ import Quickshell import Quickshell.Wayland import Quickshell.Widgets import qs.Commons -import qs.Services import qs.Widgets PopupWindow { diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index f329afe1..84847e11 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -9,7 +9,6 @@ import Quickshell.Services.UPower import Quickshell.Io import Quickshell.Widgets import qs.Commons -import qs.Services import qs.Widgets import qs.Widgets.AudioSpectrum diff --git a/Modules/MainScreen/AllScreens.qml b/Modules/MainScreen/AllScreens.qml index 5204404c..dc929b57 100644 --- a/Modules/MainScreen/AllScreens.qml +++ b/Modules/MainScreen/AllScreens.qml @@ -3,7 +3,7 @@ import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.UI import qs.Modules.MainScreen // ------------------------------ diff --git a/Modules/MainScreen/MainScreen.qml b/Modules/MainScreen/MainScreen.qml index 5a59cbfc..0edbe4d8 100644 --- a/Modules/MainScreen/MainScreen.qml +++ b/Modules/MainScreen/MainScreen.qml @@ -3,7 +3,7 @@ import QtQuick.Effects import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.UI import "Backgrounds" as Backgrounds // All panels @@ -49,17 +49,6 @@ PanelWindow { Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y) } - // Debug: Log mask region changes - onMaskChanged: { - Logger.d("MainScreen", "Mask changed!") - Logger.d("MainScreen", " Bar region:", barLoader.item?.barRegion) - Logger.d("MainScreen", " Panel count:", panelsRepeater.count) - for (var i = 0; i < panelsRepeater.count; i++) { - var panelItem = panelsRepeater.itemAt(i)?.item - Logger.d("MainScreen", " Panel", i, "- open:", panelItem?.isPanelOpen, "- region:", panelItem?.panelRegion) - } - } - // Wayland // Always use Exclusive keyboard focus when a panel is open // This ensures all keyboard shortcuts work reliably (Escape, etc.) diff --git a/Modules/MainScreen/ScreenCorners.qml b/Modules/MainScreen/ScreenCorners.qml index 367e6269..9f4c2e9e 100644 --- a/Modules/MainScreen/ScreenCorners.qml +++ b/Modules/MainScreen/ScreenCorners.qml @@ -1,7 +1,6 @@ import QtQuick import QtQuick.Shapes import qs.Commons -import qs.Services /** diff --git a/Modules/MainScreen/SmartPanel.qml b/Modules/MainScreen/SmartPanel.qml index 3d21b68d..547654ed 100644 --- a/Modules/MainScreen/SmartPanel.qml +++ b/Modules/MainScreen/SmartPanel.qml @@ -1,7 +1,7 @@ import QtQuick import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI /** diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index c8ffce5d..8abe45f0 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -6,7 +6,7 @@ import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Services.Notifications import qs.Commons -import qs.Services +import qs.Services.System import qs.Widgets // Simple notification popup - displays multiple notifications diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 835965e6..1c7cc2ac 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -5,8 +5,10 @@ import QtQuick.Effects import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services import qs.Widgets +import qs.Services.Hardware +import qs.Services.Media +import qs.Services.System // Unified OSD component // Loader activates only when showing OSD, deactivates when hidden to save resources diff --git a/Modules/Panels/Audio/AudioPanel.qml b/Modules/Panels/Audio/AudioPanel.qml index b2b53f2f..6d857791 100644 --- a/Modules/Panels/Audio/AudioPanel.qml +++ b/Modules/Panels/Audio/AudioPanel.qml @@ -4,9 +4,9 @@ import QtQuick.Controls import Quickshell import Quickshell.Services.Pipewire import qs.Commons -import qs.Services import qs.Widgets import qs.Modules.MainScreen +import qs.Services.Media SmartPanel { id: root diff --git a/Modules/Panels/Battery/BatteryPanel.qml b/Modules/Panels/Battery/BatteryPanel.qml index 50d31791..f9544e26 100644 --- a/Modules/Panels/Battery/BatteryPanel.qml +++ b/Modules/Panels/Battery/BatteryPanel.qml @@ -4,7 +4,7 @@ import QtQuick.Controls import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.Hardware import qs.Widgets import qs.Modules.MainScreen diff --git a/Modules/Panels/Bluetooth/BluetoothDevicesList.qml b/Modules/Panels/Bluetooth/BluetoothDevicesList.qml index 3491a80b..147f1042 100644 --- a/Modules/Panels/Bluetooth/BluetoothDevicesList.qml +++ b/Modules/Panels/Bluetooth/BluetoothDevicesList.qml @@ -5,7 +5,7 @@ import Quickshell import Quickshell.Bluetooth import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.Networking import qs.Widgets NBox { diff --git a/Modules/Panels/Bluetooth/BluetoothPanel.qml b/Modules/Panels/Bluetooth/BluetoothPanel.qml index db1402ff..fead0d5c 100644 --- a/Modules/Panels/Bluetooth/BluetoothPanel.qml +++ b/Modules/Panels/Bluetooth/BluetoothPanel.qml @@ -3,9 +3,9 @@ import QtQuick.Layouts import QtQuick.Controls import Quickshell import Quickshell.Bluetooth -import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.UI +import qs.Services.Networking import qs.Widgets import qs.Modules.MainScreen @@ -13,7 +13,7 @@ SmartPanel { id: root preferredWidth: Math.round(340 * Style.uiScaleRatio) - preferredHeight: 500 * Style.uiScaleRatio + preferredHeight: Math.round(500 * Style.uiScaleRatio) panelContent: Rectangle { color: Color.transparent diff --git a/Modules/Panels/Calendar/CalendarPanel.qml b/Modules/Panels/Calendar/CalendarPanel.qml index f28158fc..a01f7836 100644 --- a/Modules/Panels/Calendar/CalendarPanel.qml +++ b/Modules/Panels/Calendar/CalendarPanel.qml @@ -4,10 +4,11 @@ import QtQuick.Layouts import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services -import qs.Widgets import qs.Modules.MainScreen import qs.Modules.Panels.ControlCenter.Cards +import qs.Services.Location +import qs.Services.UI +import qs.Widgets SmartPanel { id: root diff --git a/Modules/Panels/ControlCenter/Cards/AudioCard.qml b/Modules/Panels/ControlCenter/Cards/AudioCard.qml index 3370b71f..47c35711 100644 --- a/Modules/Panels/ControlCenter/Cards/AudioCard.qml +++ b/Modules/Panels/ControlCenter/Cards/AudioCard.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.Media import qs.Widgets // Audio controls card: output and input volume controls diff --git a/Modules/Panels/ControlCenter/Cards/MediaCard.qml b/Modules/Panels/ControlCenter/Cards/MediaCard.qml index 9879d84c..2914ee09 100644 --- a/Modules/Panels/ControlCenter/Cards/MediaCard.qml +++ b/Modules/Panels/ControlCenter/Cards/MediaCard.qml @@ -3,10 +3,11 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Effects import Quickshell -import qs.Widgets.AudioSpectrum import qs.Commons -import qs.Services +import qs.Services.Media +import qs.Services.UI import qs.Widgets +import qs.Widgets.AudioSpectrum NBox { id: root diff --git a/Modules/Panels/ControlCenter/Cards/ProfileCard.qml b/Modules/Panels/ControlCenter/Cards/ProfileCard.qml index a8e04484..2a75e700 100644 --- a/Modules/Panels/ControlCenter/Cards/ProfileCard.qml +++ b/Modules/Panels/ControlCenter/Cards/ProfileCard.qml @@ -5,10 +5,10 @@ import Quickshell import Quickshell.Io import Quickshell.Widgets import qs.Commons -import qs.Services -import qs.Widgets import qs.Modules.Panels.ControlCenter.Cards import qs.Modules.Panels.Settings +import qs.Services.UI +import qs.Widgets // Header card with avatar, user and quick actions NBox { diff --git a/Modules/Panels/ControlCenter/Cards/ShortcutsCard.qml b/Modules/Panels/ControlCenter/Cards/ShortcutsCard.qml index 5e7c7ad6..91fba4b9 100644 --- a/Modules/Panels/ControlCenter/Cards/ShortcutsCard.qml +++ b/Modules/Panels/ControlCenter/Cards/ShortcutsCard.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services import qs.Widgets import qs.Modules.Panels.ControlCenter import qs.Modules.Panels.ControlCenter.Cards diff --git a/Modules/Panels/ControlCenter/Cards/SystemMonitorCard.qml b/Modules/Panels/ControlCenter/Cards/SystemMonitorCard.qml index fc94aa97..2aa62105 100644 --- a/Modules/Panels/ControlCenter/Cards/SystemMonitorCard.qml +++ b/Modules/Panels/ControlCenter/Cards/SystemMonitorCard.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.System import qs.Widgets // Unified system card: monitors CPU, temp, memory, disk diff --git a/Modules/Panels/ControlCenter/Cards/WeatherCard.qml b/Modules/Panels/ControlCenter/Cards/WeatherCard.qml index b9bd0344..52fcd7bc 100644 --- a/Modules/Panels/ControlCenter/Cards/WeatherCard.qml +++ b/Modules/Panels/ControlCenter/Cards/WeatherCard.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.Location import qs.Widgets // Weather overview card (placeholder data) diff --git a/Modules/Panels/ControlCenter/ControlCenterPanel.qml b/Modules/Panels/ControlCenter/ControlCenterPanel.qml index 510d781f..07be9664 100644 --- a/Modules/Panels/ControlCenter/ControlCenterPanel.qml +++ b/Modules/Panels/ControlCenter/ControlCenterPanel.qml @@ -3,10 +3,11 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services -import qs.Widgets import qs.Modules.MainScreen import qs.Modules.Panels.ControlCenter.Cards +import qs.Services.Media +import qs.Services.UI +import qs.Widgets SmartPanel { id: root diff --git a/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml b/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml index ebfb05f7..0fb3cd8b 100644 --- a/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml +++ b/Modules/Panels/ControlCenter/ControlCenterWidgetLoader.qml @@ -1,6 +1,6 @@ import QtQuick import Quickshell -import qs.Services +import qs.Services.UI import qs.Commons Item { diff --git a/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml b/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml index 5378156d..d3ab79ad 100644 --- a/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml +++ b/Modules/Panels/ControlCenter/Widgets/Bluetooth.qml @@ -1,7 +1,8 @@ import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.Networking +import qs.Services.UI import qs.Widgets NIconButtonHot { diff --git a/Modules/Panels/ControlCenter/Widgets/CustomButton.qml b/Modules/Panels/ControlCenter/Widgets/CustomButton.qml index 84c89c62..bad214ad 100644 --- a/Modules/Panels/ControlCenter/Widgets/CustomButton.qml +++ b/Modules/Panels/ControlCenter/Widgets/CustomButton.qml @@ -2,7 +2,6 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services import qs.Widgets Item { diff --git a/Modules/Panels/ControlCenter/Widgets/KeepAwake.qml b/Modules/Panels/ControlCenter/Widgets/KeepAwake.qml index 96d60733..5b459e7e 100644 --- a/Modules/Panels/ControlCenter/Widgets/KeepAwake.qml +++ b/Modules/Panels/ControlCenter/Widgets/KeepAwake.qml @@ -1,7 +1,7 @@ import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.Power import qs.Widgets NIconButtonHot { diff --git a/Modules/Panels/ControlCenter/Widgets/NightLight.qml b/Modules/Panels/ControlCenter/Widgets/NightLight.qml index c59126ad..c45f197a 100644 --- a/Modules/Panels/ControlCenter/Widgets/NightLight.qml +++ b/Modules/Panels/ControlCenter/Widgets/NightLight.qml @@ -2,7 +2,8 @@ import QtQuick.Layouts import Quickshell import qs.Commons import qs.Modules.Panels.Settings -import qs.Services +import qs.Services.System +import qs.Services.UI import qs.Widgets NIconButtonHot { diff --git a/Modules/Panels/ControlCenter/Widgets/Notifications.qml b/Modules/Panels/ControlCenter/Widgets/Notifications.qml index 35f5cdf7..54a8d4fc 100644 --- a/Modules/Panels/ControlCenter/Widgets/Notifications.qml +++ b/Modules/Panels/ControlCenter/Widgets/Notifications.qml @@ -1,7 +1,8 @@ import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.System +import qs.Services.UI import qs.Widgets NIconButtonHot { diff --git a/Modules/Panels/ControlCenter/Widgets/PowerProfile.qml b/Modules/Panels/ControlCenter/Widgets/PowerProfile.qml index ca5f497f..1a211c16 100644 --- a/Modules/Panels/ControlCenter/Widgets/PowerProfile.qml +++ b/Modules/Panels/ControlCenter/Widgets/PowerProfile.qml @@ -2,7 +2,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Services.UPower import qs.Commons -import qs.Services +import qs.Services.Power import qs.Widgets // Performance @@ -15,7 +15,5 @@ NIconButtonHot { icon: PowerProfileService.getIcon() hot: !PowerProfileService.isDefault() tooltipText: I18n.tr("quickSettings.powerProfile.tooltip.action") - onClicked: { - PowerProfileService.cycleProfile() - } + onClicked: PowerProfileService.cycleProfile() } diff --git a/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml index 85fbacd9..b2e6520b 100644 --- a/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml +++ b/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml @@ -1,7 +1,7 @@ import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets NIconButtonHot { @@ -14,8 +14,7 @@ NIconButtonHot { onClicked: { ScreenRecorderService.toggleRecording() if (!ScreenRecorderService.isRecording) { - var panel = PanelService.getPanel("controlCenterPanel", screen) - panel?.close() + PanelService.getPanel("controlCenterPanel", screen)?.close } } } diff --git a/Modules/Panels/ControlCenter/Widgets/WallpaperSelector.qml b/Modules/Panels/ControlCenter/Widgets/WallpaperSelector.qml index daa941ee..6d6e840d 100644 --- a/Modules/Panels/ControlCenter/Widgets/WallpaperSelector.qml +++ b/Modules/Panels/ControlCenter/Widgets/WallpaperSelector.qml @@ -1,7 +1,7 @@ import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets NIconButtonHot { diff --git a/Modules/Panels/ControlCenter/Widgets/WiFi.qml b/Modules/Panels/ControlCenter/Widgets/WiFi.qml index a24f43e1..e40047f1 100644 --- a/Modules/Panels/ControlCenter/Widgets/WiFi.qml +++ b/Modules/Panels/ControlCenter/Widgets/WiFi.qml @@ -1,7 +1,8 @@ import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.Networking +import qs.Services.UI import qs.Widgets NIconButtonHot { diff --git a/Modules/Panels/Launcher/Launcher.qml b/Modules/Panels/Launcher/Launcher.qml index 43e1f886..d26f1b0b 100644 --- a/Modules/Panels/Launcher/Launcher.qml +++ b/Modules/Panels/Launcher/Launcher.qml @@ -4,7 +4,6 @@ import QtQuick.Layouts import Quickshell import Quickshell.Widgets import qs.Commons -import qs.Services import qs.Widgets import qs.Modules.MainScreen import "Plugins" @@ -13,8 +12,8 @@ SmartPanel { id: root // Panel configuration - preferredWidth: 500 * Style.uiScaleRatio - preferredHeight: 600 * Style.uiScaleRatio + preferredWidth: Math.round(500 * Style.uiScaleRatio) + preferredHeight: Math.round(600 * Style.uiScaleRatio) preferredWidthRatio: 0.3 preferredHeightRatio: 0.5 diff --git a/Modules/Panels/Launcher/Plugins/ApplicationsPlugin.qml b/Modules/Panels/Launcher/Plugins/ApplicationsPlugin.qml index 8add1460..3d3a1e60 100644 --- a/Modules/Panels/Launcher/Plugins/ApplicationsPlugin.qml +++ b/Modules/Panels/Launcher/Plugins/ApplicationsPlugin.qml @@ -2,7 +2,6 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services import "../../../../Helpers/FuzzySort.js" as Fuzzysort Item { diff --git a/Modules/Panels/Launcher/Plugins/CalculatorPlugin.qml b/Modules/Panels/Launcher/Plugins/CalculatorPlugin.qml index 9eca7fc7..e8aaf5e3 100644 --- a/Modules/Panels/Launcher/Plugins/CalculatorPlugin.qml +++ b/Modules/Panels/Launcher/Plugins/CalculatorPlugin.qml @@ -1,5 +1,4 @@ import QtQuick -import qs.Services import qs.Commons import "../../../../Helpers/AdvancedMath.js" as AdvancedMath diff --git a/Modules/Panels/Launcher/Plugins/ClipboardPlugin.qml b/Modules/Panels/Launcher/Plugins/ClipboardPlugin.qml index 0d5837bd..e6922b87 100644 --- a/Modules/Panels/Launcher/Plugins/ClipboardPlugin.qml +++ b/Modules/Panels/Launcher/Plugins/ClipboardPlugin.qml @@ -1,7 +1,7 @@ import QtQuick import Quickshell import qs.Commons -import qs.Services +import qs.Services.Keyboard Item { id: root diff --git a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml index 3c90e942..53faaa8a 100644 --- a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml +++ b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml @@ -5,7 +5,7 @@ import Quickshell import Quickshell.Wayland import Quickshell.Services.Notifications import qs.Commons -import qs.Services +import qs.Services.System import qs.Widgets import qs.Modules.MainScreen diff --git a/Modules/Panels/SessionMenu/SessionMenu.qml b/Modules/Panels/SessionMenu/SessionMenu.qml index 4fdcdeea..a8c8f593 100644 --- a/Modules/Panels/SessionMenu/SessionMenu.qml +++ b/Modules/Panels/SessionMenu/SessionMenu.qml @@ -7,7 +7,7 @@ import Quickshell.Io import Quickshell.Widgets import Quickshell.Wayland import qs.Commons -import qs.Services +import qs.Services.Compositor import qs.Widgets import qs.Modules.MainScreen diff --git a/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml b/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml index c6f866fc..577c252f 100644 --- a/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml @@ -4,7 +4,7 @@ import QtQuick.Effects import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services +import qs.Services.UI // Widget Settings Dialog Component Popup { diff --git a/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml b/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml index 4b9c9fb1..805f2fc5 100644 --- a/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml +++ b/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services +import qs.Services.UI // Widget Settings Dialog Component Popup { diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index f1694ab0..1df4c2a1 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -2,12 +2,11 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell -import Quickshell.Wayland import qs.Modules.Panels.Settings.Tabs import qs.Commons -import qs.Services -import qs.Widgets import qs.Modules.MainScreen +import qs.Services.System +import qs.Widgets SmartPanel { id: root diff --git a/Modules/Panels/Settings/Tabs/AboutTab.qml b/Modules/Panels/Settings/Tabs/AboutTab.qml index 3bd64a45..b3463bc3 100644 --- a/Modules/Panels/Settings/Tabs/AboutTab.qml +++ b/Modules/Panels/Settings/Tabs/AboutTab.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.Noctalia import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/AudioTab.qml b/Modules/Panels/Settings/Tabs/AudioTab.qml index 2133f5a1..96fa6dc5 100644 --- a/Modules/Panels/Settings/Tabs/AudioTab.qml +++ b/Modules/Panels/Settings/Tabs/AudioTab.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell.Services.Pipewire import qs.Commons -import qs.Services +import qs.Services.Media import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/BarTab.qml b/Modules/Panels/Settings/Tabs/BarTab.qml index b01e1f88..5e0c2fad 100644 --- a/Modules/Panels/Settings/Tabs/BarTab.qml +++ b/Modules/Panels/Settings/Tabs/BarTab.qml @@ -3,7 +3,8 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.Compositor +import qs.Services.UI import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml index 80f0bf80..c4570b8b 100644 --- a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml @@ -4,7 +4,9 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.DynamicTheming +import qs.Services.System +import qs.Services.UI import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/ControlCenterTab.qml b/Modules/Panels/Settings/Tabs/ControlCenterTab.qml index 8e96428f..989f56e7 100644 --- a/Modules/Panels/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Panels/Settings/Tabs/ControlCenterTab.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/DisplayTab.qml b/Modules/Panels/Settings/Tabs/DisplayTab.qml index ff863964..d9530e28 100644 --- a/Modules/Panels/Settings/Tabs/DisplayTab.qml +++ b/Modules/Panels/Settings/Tabs/DisplayTab.qml @@ -4,7 +4,9 @@ import QtQuick.Controls import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.Compositor +import qs.Services.Location +import qs.Services.Hardware import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/DockTab.qml b/Modules/Panels/Settings/Tabs/DockTab.qml index e9784bea..8afe2e59 100644 --- a/Modules/Panels/Settings/Tabs/DockTab.qml +++ b/Modules/Panels/Settings/Tabs/DockTab.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.Compositor import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/GeneralTab.qml b/Modules/Panels/Settings/Tabs/GeneralTab.qml index 80b282e9..c5de3c6c 100644 --- a/Modules/Panels/Settings/Tabs/GeneralTab.qml +++ b/Modules/Panels/Settings/Tabs/GeneralTab.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services +import qs.Services.System import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/HooksTab.qml b/Modules/Panels/Settings/Tabs/HooksTab.qml index 0b664cd3..b129bd7c 100644 --- a/Modules/Panels/Settings/Tabs/HooksTab.qml +++ b/Modules/Panels/Settings/Tabs/HooksTab.qml @@ -2,7 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services +import qs.Services.Control +import qs.Services.UI import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/LauncherTab.qml b/Modules/Panels/Settings/Tabs/LauncherTab.qml index 642d1ee8..e4cc4631 100644 --- a/Modules/Panels/Settings/Tabs/LauncherTab.qml +++ b/Modules/Panels/Settings/Tabs/LauncherTab.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services +import qs.Services.System import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/LocationTab.qml b/Modules/Panels/Settings/Tabs/LocationTab.qml index c2f264ce..b91edeb6 100644 --- a/Modules/Panels/Settings/Tabs/LocationTab.qml +++ b/Modules/Panels/Settings/Tabs/LocationTab.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services +import qs.Services.Location import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/NetworkTab.qml b/Modules/Panels/Settings/Tabs/NetworkTab.qml index 958a2842..9ff4c6d2 100644 --- a/Modules/Panels/Settings/Tabs/NetworkTab.qml +++ b/Modules/Panels/Settings/Tabs/NetworkTab.qml @@ -2,9 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell -import Quickshell.Bluetooth import qs.Commons -import qs.Services +import qs.Services.Networking import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/NotificationsTab.qml b/Modules/Panels/Settings/Tabs/NotificationsTab.qml index b37f1d62..6c0a9e37 100644 --- a/Modules/Panels/Settings/Tabs/NotificationsTab.qml +++ b/Modules/Panels/Settings/Tabs/NotificationsTab.qml @@ -3,7 +3,7 @@ import QtQuick.Layouts import QtQuick.Controls import Quickshell import qs.Commons -import qs.Services +import qs.Services.Compositor import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/OsdTab.qml b/Modules/Panels/Settings/Tabs/OsdTab.qml index 906f8883..3474ec87 100644 --- a/Modules/Panels/Settings/Tabs/OsdTab.qml +++ b/Modules/Panels/Settings/Tabs/OsdTab.qml @@ -3,7 +3,7 @@ import QtQuick.Layouts import QtQuick.Controls import Quickshell import qs.Commons -import qs.Services +import qs.Services.Compositor import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/Settings/Tabs/WallpaperTab.qml b/Modules/Panels/Settings/Tabs/WallpaperTab.qml index 6fb57733..ec3995a0 100644 --- a/Modules/Panels/Settings/Tabs/WallpaperTab.qml +++ b/Modules/Panels/Settings/Tabs/WallpaperTab.qml @@ -4,7 +4,8 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.Compositor +import qs.Services.UI import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/SetupWizard/SetupAppearanceStep.qml b/Modules/Panels/SetupWizard/SetupAppearanceStep.qml index 7a2df8c0..73c8da0c 100644 --- a/Modules/Panels/SetupWizard/SetupAppearanceStep.qml +++ b/Modules/Panels/SetupWizard/SetupAppearanceStep.qml @@ -3,7 +3,6 @@ import QtQuick.Layouts import QtQuick.Controls import Quickshell.Io import qs.Commons -import qs.Services import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/SetupWizard/SetupCustomizeStep.qml b/Modules/Panels/SetupWizard/SetupCustomizeStep.qml index 52da1576..2526933d 100644 --- a/Modules/Panels/SetupWizard/SetupCustomizeStep.qml +++ b/Modules/Panels/SetupWizard/SetupCustomizeStep.qml @@ -3,7 +3,6 @@ import QtQuick.Layouts import QtQuick.Controls import Quickshell import qs.Commons -import qs.Services import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/SetupWizard/SetupDockStep.qml b/Modules/Panels/SetupWizard/SetupDockStep.qml index 79164869..c9446d7f 100644 --- a/Modules/Panels/SetupWizard/SetupDockStep.qml +++ b/Modules/Panels/SetupWizard/SetupDockStep.qml @@ -3,7 +3,6 @@ import QtQuick.Layouts import QtQuick.Controls import Quickshell import qs.Commons -import qs.Services import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/SetupWizard/SetupWallpaperStep.qml b/Modules/Panels/SetupWizard/SetupWallpaperStep.qml index d1d0df06..dd08d7ec 100644 --- a/Modules/Panels/SetupWizard/SetupWallpaperStep.qml +++ b/Modules/Panels/SetupWizard/SetupWallpaperStep.qml @@ -5,7 +5,6 @@ import QtQuick.Effects import Quickshell import Quickshell.Io import qs.Commons -import qs.Services import qs.Widgets import "../../../Helpers/FuzzySort.js" as FuzzySort diff --git a/Modules/Panels/SetupWizard/SetupWizard.qml b/Modules/Panels/SetupWizard/SetupWizard.qml index 74dd1fb3..f8ee4df6 100644 --- a/Modules/Panels/SetupWizard/SetupWizard.qml +++ b/Modules/Panels/SetupWizard/SetupWizard.qml @@ -4,7 +4,6 @@ import QtQuick.Controls import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services import qs.Widgets import qs.Modules.MainScreen diff --git a/Modules/Panels/Tray/TrayDrawerPanel.qml b/Modules/Panels/Tray/TrayDrawerPanel.qml index 85cced89..20db163a 100644 --- a/Modules/Panels/Tray/TrayDrawerPanel.qml +++ b/Modules/Panels/Tray/TrayDrawerPanel.qml @@ -4,7 +4,7 @@ import Quickshell import Quickshell.Widgets import Quickshell.Services.SystemTray import qs.Commons -import qs.Services +import qs.Services.UI import qs.Widgets import qs.Modules.MainScreen diff --git a/Modules/Panels/Tray/TrayMenuPanel.qml b/Modules/Panels/Tray/TrayMenuPanel.qml index c55115c0..bfe11708 100644 --- a/Modules/Panels/Tray/TrayMenuPanel.qml +++ b/Modules/Panels/Tray/TrayMenuPanel.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services import qs.Widgets import qs.Modules.MainScreen diff --git a/Modules/Panels/Wallpaper/WallpaperPanel.qml b/Modules/Panels/Wallpaper/WallpaperPanel.qml index 60e2cd41..d2fc547f 100644 --- a/Modules/Panels/Wallpaper/WallpaperPanel.qml +++ b/Modules/Panels/Wallpaper/WallpaperPanel.qml @@ -2,12 +2,11 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell -import Quickshell.Wayland import qs.Commons -import qs.Modules.Panels.Settings -import qs.Services -import qs.Widgets import qs.Modules.MainScreen +import qs.Modules.Panels.Settings +import qs.Services.UI +import qs.Widgets import "../../../Helpers/FuzzySort.js" as FuzzySort SmartPanel { diff --git a/Modules/Panels/WiFi/WiFiPanel.qml b/Modules/Panels/WiFi/WiFiPanel.qml index accb5782..8dc08084 100644 --- a/Modules/Panels/WiFi/WiFiPanel.qml +++ b/Modules/Panels/WiFi/WiFiPanel.qml @@ -2,11 +2,10 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell -import Quickshell.Wayland import qs.Commons -import qs.Services -import qs.Widgets import qs.Modules.MainScreen +import qs.Services.Networking +import qs.Widgets SmartPanel { id: root diff --git a/Modules/Toast/ToastOverlay.qml b/Modules/Toast/ToastOverlay.qml index 3051108d..db5dbe56 100644 --- a/Modules/Toast/ToastOverlay.qml +++ b/Modules/Toast/ToastOverlay.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services import qs.Widgets Variants { diff --git a/Modules/Toast/ToastScreen.qml b/Modules/Toast/ToastScreen.qml index 70b1f38b..7ed7720c 100644 --- a/Modules/Toast/ToastScreen.qml +++ b/Modules/Toast/ToastScreen.qml @@ -1,10 +1,8 @@ import QtQuick -import QtQuick.Controls import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Services -import qs.Widgets +import qs.Services.UI Item { id: root @@ -200,8 +198,9 @@ Item { WlrLayershell.layer: (Settings.data.notifications && Settings.data.notifications.overlayLayer) ? WlrLayer.Overlay : WlrLayer.Top WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - exclusionMode: PanelWindow.ExclusionMode.Ignore + WlrLayershell.exclusionMode: ExclusionMode.Ignore + //mask: Region {} function showToast(message, description, icon, type, duration) { toastItem.show(message, description, icon, type, duration) } diff --git a/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml index f30306d3..584c1529 100644 --- a/Modules/Tooltip/Tooltip.qml +++ b/Modules/Tooltip/Tooltip.qml @@ -1,8 +1,7 @@ import QtQuick -import QtQuick.Layouts +// import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Services import qs.Widgets PopupWindow { diff --git a/Services/CompositorService.qml b/Services/Compositor/CompositorService.qml similarity index 99% rename from Services/CompositorService.qml rename to Services/Compositor/CompositorService.qml index cd4b16b1..17275b34 100644 --- a/Services/CompositorService.qml +++ b/Services/Compositor/CompositorService.qml @@ -4,7 +4,6 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services Singleton { id: root diff --git a/Services/HyprlandService.qml b/Services/Compositor/HyprlandService.qml similarity index 100% rename from Services/HyprlandService.qml rename to Services/Compositor/HyprlandService.qml diff --git a/Services/NiriService.qml b/Services/Compositor/NiriService.qml similarity index 100% rename from Services/NiriService.qml rename to Services/Compositor/NiriService.qml diff --git a/Services/SwayService.qml b/Services/Compositor/SwayService.qml similarity index 100% rename from Services/SwayService.qml rename to Services/Compositor/SwayService.qml diff --git a/Services/HooksService.qml b/Services/Control/HooksService.qml similarity index 98% rename from Services/HooksService.qml rename to Services/Control/HooksService.qml index db9f5e17..646cfbfd 100644 --- a/Services/HooksService.qml +++ b/Services/Control/HooksService.qml @@ -3,7 +3,7 @@ pragma Singleton import QtQuick import Quickshell import qs.Commons -import qs.Services +import qs.Services.UI Singleton { id: root diff --git a/Services/IPCService.qml b/Services/Control/IPCService.qml similarity index 99% rename from Services/IPCService.qml rename to Services/Control/IPCService.qml index 406cfb07..1d753cce 100644 --- a/Services/IPCService.qml +++ b/Services/Control/IPCService.qml @@ -4,7 +4,7 @@ import Quickshell.Io import Quickshell.Wayland import Quickshell.Widgets import qs.Commons -import qs.Services +import qs.Services.UI Item { id: root diff --git a/Services/AppThemeService.qml b/Services/DynamicTheming/AppThemeService.qml similarity index 98% rename from Services/AppThemeService.qml rename to Services/DynamicTheming/AppThemeService.qml index f694d235..167309da 100644 --- a/Services/AppThemeService.qml +++ b/Services/DynamicTheming/AppThemeService.qml @@ -3,6 +3,7 @@ pragma Singleton import QtQuick import Quickshell import qs.Commons +import qs.Services.UI Singleton { id: root diff --git a/Services/ColorPaletteGenerator.qml b/Services/DynamicTheming/ColorPaletteGenerator.qml similarity index 98% rename from Services/ColorPaletteGenerator.qml rename to Services/DynamicTheming/ColorPaletteGenerator.qml index 05b9d7c2..343b1758 100644 --- a/Services/ColorPaletteGenerator.qml +++ b/Services/DynamicTheming/ColorPaletteGenerator.qml @@ -2,7 +2,7 @@ pragma Singleton import QtQuick import Quickshell -import "../Helpers/ColorsConvert.js" as ColorsConvert +import "../../Helpers/ColorsConvert.js" as ColorsConvert Singleton { id: root diff --git a/Services/ColorSchemeService.qml b/Services/DynamicTheming/ColorSchemeService.qml similarity index 99% rename from Services/ColorSchemeService.qml rename to Services/DynamicTheming/ColorSchemeService.qml index 002a1d89..734d8bd9 100644 --- a/Services/ColorSchemeService.qml +++ b/Services/DynamicTheming/ColorSchemeService.qml @@ -5,7 +5,6 @@ import Qt.labs.folderlistmodel import Quickshell import Quickshell.Io import qs.Commons -import qs.Services Singleton { id: root diff --git a/Services/TemplateProcessor.qml b/Services/DynamicTheming/TemplateProcessor.qml similarity index 99% rename from Services/TemplateProcessor.qml rename to Services/DynamicTheming/TemplateProcessor.qml index d5d0d0d8..b436967c 100644 --- a/Services/TemplateProcessor.qml +++ b/Services/DynamicTheming/TemplateProcessor.qml @@ -4,6 +4,8 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons +import qs.Services.System +import qs.Services.UI Singleton { id: root diff --git a/Services/TemplateRegistry.qml b/Services/DynamicTheming/TemplateRegistry.qml similarity index 96% rename from Services/TemplateRegistry.qml rename to Services/DynamicTheming/TemplateRegistry.qml index a4157f48..40d0542c 100644 --- a/Services/TemplateRegistry.qml +++ b/Services/DynamicTheming/TemplateRegistry.qml @@ -51,7 +51,7 @@ Singleton { }, { "path": "~/.config/gtk-4.0/gtk.css" }], - "postProcess": mode => `gsettings set org.gnome.desktop.interface color-scheme prefer-${mode}\n` + "postProcess": mode => `gsettings set org.gnome.desktop.interface color-scheme prefer-${mode}` }, { "id": "qt", "name": "Qt", @@ -78,7 +78,7 @@ Singleton { "outputs": [{ "path": "~/.config/fuzzel/themes/noctalia" }], - "postProcess": () => `${colorsApplyScript} fuzzel\n` + "postProcess": () => `${colorsApplyScript} fuzzel` }, { "id": "vicinae", "name": "Vicinae", @@ -87,7 +87,7 @@ Singleton { "outputs": [{ "path": "~/.local/share/vicinae/themes/matugen.toml" }], - "postProcess": () => `cp -n ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae\n` + "postProcess": () => `cp -n ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae` }, { "id": "walker", "name": "Walker", @@ -96,7 +96,7 @@ Singleton { "outputs": [{ "path": "~/.config/walker/themes/noctalia/style.css" }], - "postProcess": () => `${colorsApplyScript} walker\n`, + "postProcess": () => `${colorsApplyScript} walker`, "strict": true // Use strict mode for palette generation (preserves custom surface/outline values) }, { "id": "pywalfox", @@ -106,8 +106,8 @@ Singleton { "outputs": [{ "path": "~/.cache/wal/colors.json" }], - "postProcess": () => `${colorsApplyScript} pywalfox\n` - }, // CONSOLIDATED DISCORD CLIENTS (requirement #2) + "postProcess": () => `${colorsApplyScript} pywalfox` + }, // CONSOLIDATED DISCORD CLIENTS { "id": "discord", "name": "Discord", diff --git a/Services/BatteryService.qml b/Services/Hardware/BatteryService.qml similarity index 99% rename from Services/BatteryService.qml rename to Services/Hardware/BatteryService.qml index 14b831c9..41d63695 100644 --- a/Services/BatteryService.qml +++ b/Services/Hardware/BatteryService.qml @@ -4,7 +4,7 @@ import Quickshell import Quickshell.Io import Quickshell.Services.UPower import qs.Commons -import qs.Services +import qs.Services.UI Singleton { id: root diff --git a/Services/BrightnessService.qml b/Services/Hardware/BrightnessService.qml similarity index 100% rename from Services/BrightnessService.qml rename to Services/Hardware/BrightnessService.qml diff --git a/Services/ClipboardService.qml b/Services/Keyboard/ClipboardService.qml similarity index 100% rename from Services/ClipboardService.qml rename to Services/Keyboard/ClipboardService.qml diff --git a/Services/KeyboardLayoutService.qml b/Services/Keyboard/KeyboardLayoutService.qml similarity index 99% rename from Services/KeyboardLayoutService.qml rename to Services/Keyboard/KeyboardLayoutService.qml index ed0b4f8b..7876b27a 100644 --- a/Services/KeyboardLayoutService.qml +++ b/Services/Keyboard/KeyboardLayoutService.qml @@ -5,7 +5,6 @@ import Quickshell import Quickshell.Io import Quickshell.Hyprland import qs.Commons -import qs.Services Singleton { id: root diff --git a/Services/LockKeysService.qml b/Services/Keyboard/LockKeysService.qml similarity index 100% rename from Services/LockKeysService.qml rename to Services/Keyboard/LockKeysService.qml diff --git a/Services/CalendarService.qml b/Services/Location/CalendarService.qml similarity index 100% rename from Services/CalendarService.qml rename to Services/Location/CalendarService.qml diff --git a/Services/DarkModeService.qml b/Services/Location/DarkModeService.qml similarity index 99% rename from Services/DarkModeService.qml rename to Services/Location/DarkModeService.qml index ad3eef00..e7c3315c 100644 --- a/Services/DarkModeService.qml +++ b/Services/Location/DarkModeService.qml @@ -3,7 +3,6 @@ pragma Singleton import QtQuick import Quickshell import qs.Commons -import qs.Services Singleton { id: root diff --git a/Services/LocationService.qml b/Services/Location/LocationService.qml similarity index 99% rename from Services/LocationService.qml rename to Services/Location/LocationService.qml index 7c2df5d4..775c9616 100644 --- a/Services/LocationService.qml +++ b/Services/Location/LocationService.qml @@ -4,7 +4,6 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services // Weather logic and caching with stable UI properties Singleton { diff --git a/Services/NightLightService.qml b/Services/Location/NightLightService.qml similarity index 99% rename from Services/NightLightService.qml rename to Services/Location/NightLightService.qml index 864ae87a..8160c9e9 100644 --- a/Services/NightLightService.qml +++ b/Services/Location/NightLightService.qml @@ -4,7 +4,6 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services Singleton { id: root diff --git a/Services/AudioService.qml b/Services/Media/AudioService.qml similarity index 100% rename from Services/AudioService.qml rename to Services/Media/AudioService.qml diff --git a/Services/CavaService.qml b/Services/Media/CavaService.qml similarity index 99% rename from Services/CavaService.qml rename to Services/Media/CavaService.qml index 16324bc7..622181c8 100644 --- a/Services/CavaService.qml +++ b/Services/Media/CavaService.qml @@ -4,6 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons +import qs.Services.UI Singleton { id: root diff --git a/Services/MediaService.qml b/Services/Media/MediaService.qml similarity index 99% rename from Services/MediaService.qml rename to Services/Media/MediaService.qml index b96cafbd..be8bab1e 100644 --- a/Services/MediaService.qml +++ b/Services/Media/MediaService.qml @@ -4,7 +4,6 @@ import QtQuick import Quickshell import Quickshell.Services.Mpris import qs.Commons -import qs.Services Singleton { id: root diff --git a/Services/ScreenRecorderService.qml b/Services/Media/ScreenRecorderService.qml similarity index 99% rename from Services/ScreenRecorderService.qml rename to Services/Media/ScreenRecorderService.qml index c2e09fc4..8b4d9f98 100644 --- a/Services/ScreenRecorderService.qml +++ b/Services/Media/ScreenRecorderService.qml @@ -4,7 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.System Singleton { id: root diff --git a/Services/BluetoothService.qml b/Services/Networking/BluetoothService.qml similarity index 99% rename from Services/BluetoothService.qml rename to Services/Networking/BluetoothService.qml index 0ab4a30f..b130a489 100644 --- a/Services/BluetoothService.qml +++ b/Services/Networking/BluetoothService.qml @@ -5,7 +5,6 @@ import Quickshell import Quickshell.Bluetooth import Quickshell.Io import qs.Commons -import qs.Services Singleton { id: root diff --git a/Services/NetworkService.qml b/Services/Networking/NetworkService.qml similarity index 99% rename from Services/NetworkService.qml rename to Services/Networking/NetworkService.qml index 142d300a..61731a4c 100644 --- a/Services/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -4,7 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.UI Singleton { id: root diff --git a/Services/GitHubService.qml b/Services/Noctalia/GitHubService.qml similarity index 99% rename from Services/GitHubService.qml rename to Services/Noctalia/GitHubService.qml index 1a2efc8a..6f7c1bf4 100644 --- a/Services/GitHubService.qml +++ b/Services/Noctalia/GitHubService.qml @@ -4,7 +4,6 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services // GitHub API logic and caching Singleton { diff --git a/Services/UpdateService.qml b/Services/Noctalia/UpdateService.qml similarity index 100% rename from Services/UpdateService.qml rename to Services/Noctalia/UpdateService.qml diff --git a/Services/IdleInhibitorService.qml b/Services/Power/IdleInhibitorService.qml similarity index 99% rename from Services/IdleInhibitorService.qml rename to Services/Power/IdleInhibitorService.qml index 941e2f3d..80aaba3f 100644 --- a/Services/IdleInhibitorService.qml +++ b/Services/Power/IdleInhibitorService.qml @@ -4,7 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services +import qs.Services.UI Singleton { id: root diff --git a/Services/PowerProfileService.qml b/Services/Power/PowerProfileService.qml similarity index 99% rename from Services/PowerProfileService.qml rename to Services/Power/PowerProfileService.qml index cb778628..f086e9c1 100644 --- a/Services/PowerProfileService.qml +++ b/Services/Power/PowerProfileService.qml @@ -4,7 +4,7 @@ import QtQuick import Quickshell import Quickshell.Services.UPower import qs.Commons -import qs.Services +import qs.Services.UI Singleton { id: root diff --git a/Services/DistroService.qml b/Services/System/DistroService.qml similarity index 100% rename from Services/DistroService.qml rename to Services/System/DistroService.qml diff --git a/Services/FontService.qml b/Services/System/FontService.qml similarity index 100% rename from Services/FontService.qml rename to Services/System/FontService.qml diff --git a/Services/NotificationService.qml b/Services/System/NotificationService.qml similarity index 99% rename from Services/NotificationService.qml rename to Services/System/NotificationService.qml index 706e55fd..1442e5fb 100644 --- a/Services/NotificationService.qml +++ b/Services/System/NotificationService.qml @@ -6,8 +6,8 @@ import Quickshell import Quickshell.Io import Quickshell.Services.Notifications import qs.Commons -import qs.Services -import "../Helpers/sha256.js" as Checksum +import qs.Services.Power +import "../../Helpers/sha256.js" as Checksum Singleton { id: root diff --git a/Services/ProgramCheckerService.qml b/Services/System/ProgramCheckerService.qml similarity index 99% rename from Services/ProgramCheckerService.qml rename to Services/System/ProgramCheckerService.qml index fa3cb406..e569ce1e 100644 --- a/Services/ProgramCheckerService.qml +++ b/Services/System/ProgramCheckerService.qml @@ -4,6 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons +import qs.Services.DynamicTheming // Service to check if various programs are available on the system Singleton { diff --git a/Services/SystemStatService.qml b/Services/System/SystemStatService.qml similarity index 100% rename from Services/SystemStatService.qml rename to Services/System/SystemStatService.qml diff --git a/Services/BarService.qml b/Services/UI/BarService.qml similarity index 100% rename from Services/BarService.qml rename to Services/UI/BarService.qml diff --git a/Services/BarWidgetRegistry.qml b/Services/UI/BarWidgetRegistry.qml similarity index 96% rename from Services/BarWidgetRegistry.qml rename to Services/UI/BarWidgetRegistry.qml index 30cf715b..5cd91190 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/UI/BarWidgetRegistry.qml @@ -12,7 +12,8 @@ Singleton { property var widgets: ({ "ActiveWindow": activeWindowComponent, "AudioVisualizer": audioVisualizerComponent, - "Battery": batteryComponent, + "Battery"// HEAVY + : batteryComponent, "Bluetooth": bluetoothComponent, "Brightness": brightnessComponent, "Clock": clockComponent, @@ -27,18 +28,21 @@ Singleton { "NightLight": nightLightComponent, "NoctaliaPerformance": noctaliaPerformanceComponent, "NotificationHistory": notificationHistoryComponent, - "PowerProfile": powerProfileComponent, + "PowerProfile"// HEAVY + : powerProfileComponent, "ScreenRecorder": screenRecorderComponent, "SessionMenu": sessionMenuComponent, "Spacer": spacerComponent, "SystemMonitor": systemMonitorComponent, - "Taskbar": taskbarComponent, + "Taskbar"// HEAVY + : taskbarComponent, "TaskbarGrouped": taskbarGroupedComponent, "Tray": trayComponent, - "Volume": volumeComponent, + "Volume"// A BIT HEAVY ? + : volumeComponent, "WiFi": wiFiComponent, "WallpaperSelector": wallpaperSelectorComponent, - "Workspace": workspaceComponent + "Workspace": workspaceComponent // HEAVY }) property var widgetMetadata: ({ diff --git a/Services/ControlCenterWidgetRegistry.qml b/Services/UI/ControlCenterWidgetRegistry.qml similarity index 100% rename from Services/ControlCenterWidgetRegistry.qml rename to Services/UI/ControlCenterWidgetRegistry.qml diff --git a/Services/PanelService.qml b/Services/UI/PanelService.qml similarity index 100% rename from Services/PanelService.qml rename to Services/UI/PanelService.qml diff --git a/Services/ToastService.qml b/Services/UI/ToastService.qml similarity index 100% rename from Services/ToastService.qml rename to Services/UI/ToastService.qml diff --git a/Services/TooltipService.qml b/Services/UI/TooltipService.qml similarity index 100% rename from Services/TooltipService.qml rename to Services/UI/TooltipService.qml diff --git a/Services/WallpaperService.qml b/Services/UI/WallpaperService.qml similarity index 100% rename from Services/WallpaperService.qml rename to Services/UI/WallpaperService.qml diff --git a/Widgets/NButton.qml b/Widgets/NButton.qml index aed27ab7..ce6c68cf 100644 --- a/Widgets/NButton.qml +++ b/Widgets/NButton.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services +import qs.Services.UI Rectangle { id: root diff --git a/Widgets/NColorPickerDialog.qml b/Widgets/NColorPickerDialog.qml index 21c624e6..5bfb56db 100644 --- a/Widgets/NColorPickerDialog.qml +++ b/Widgets/NColorPickerDialog.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets Popup { diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index deb0ba9d..b90a1d9f 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets RowLayout { diff --git a/Widgets/NContextMenu.qml b/Widgets/NContextMenu.qml index 45b55aed..6c951116 100644 --- a/Widgets/NContextMenu.qml +++ b/Widgets/NContextMenu.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services Popup { id: root diff --git a/Widgets/NDropShadows.qml b/Widgets/NDropShadows.qml index 9dc2a4e5..a0fb3bb3 100644 --- a/Widgets/NDropShadows.qml +++ b/Widgets/NDropShadows.qml @@ -1,7 +1,7 @@ import QtQuick import QtQuick.Effects import qs.Commons -import qs.Services +import qs.Services.Power // Unified shadow system Item { diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 9489d114..936ce8ba 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -5,7 +5,6 @@ import Qt.labs.folderlistmodel import Quickshell import Quickshell.Io import qs.Commons -import qs.Services import qs.Widgets import "../Helpers/FuzzySort.js" as FuzzySort diff --git a/Widgets/NIconButton.qml b/Widgets/NIconButton.qml index 2fd507e5..c72d7d38 100644 --- a/Widgets/NIconButton.qml +++ b/Widgets/NIconButton.qml @@ -2,7 +2,7 @@ import QtQuick import Quickshell import Quickshell.Widgets import qs.Commons -import qs.Services +import qs.Services.UI Rectangle { id: root diff --git a/Widgets/NIconButtonHot.qml b/Widgets/NIconButtonHot.qml index ff80b3ef..481d526f 100644 --- a/Widgets/NIconButtonHot.qml +++ b/Widgets/NIconButtonHot.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Effects import qs.Commons -import qs.Services +import qs.Services.UI Rectangle { id: root diff --git a/Widgets/NIconPicker.qml b/Widgets/NIconPicker.qml index 2a4916e9..996917ba 100644 --- a/Widgets/NIconPicker.qml +++ b/Widgets/NIconPicker.qml @@ -4,7 +4,7 @@ import QtQuick.Layouts import QtQuick.Window import qs.Commons import qs.Widgets -import qs.Services +import qs.Services.UI Popup { id: root @@ -15,8 +15,8 @@ Popup { signal iconSelected(string iconName) - width: 900 * Style.uiScaleRatio - height: 700 * Style.uiScaleRatio + width: Math.round(900 * Style.uiScaleRatio) + height: Math.round(700 * Style.uiScaleRatio) anchors.centerIn: Overlay.overlay padding: Style.marginXL diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index b4c82b52..c4a74a19 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons -import qs.Services import qs.Widgets import "../Helpers/FuzzySort.js" as Fuzzysort diff --git a/Widgets/NSlider.qml b/Widgets/NSlider.qml index 82bbf102..793dd7bf 100644 --- a/Widgets/NSlider.qml +++ b/Widgets/NSlider.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Effects import qs.Commons -import qs.Services +import qs.Services.UI Slider { id: root diff --git a/shell.qml b/shell.qml index 8217e7e6..af944e62 100644 --- a/shell.qml +++ b/shell.qml @@ -12,7 +12,14 @@ import Quickshell // Commons & Services import qs.Commons -import qs.Services +import qs.Services.Control +import qs.Services.DynamicTheming +import qs.Services.Hardware +import qs.Services.Location +import qs.Services.Networking +import qs.Services.Power +import qs.Services.System +import qs.Services.UI // Modules import qs.Modules.Background @@ -63,11 +70,9 @@ ShellRoot { WallpaperService.init() AppThemeService.init() ColorSchemeService.init() - BarWidgetRegistry.init() LocationService.init() NightLightService.apply() DarkModeService.init() - FontService.init() HooksService.init() BluetoothService.init() BatteryService.init() From e84cc3fb62b98561315f5a8d013cd3b286852a59 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 8 Nov 2025 23:17:46 +0100 Subject: [PATCH 020/107] TrayMenuPanel: fix submenu width calculation --- Modules/Panels/Tray/TrayMenuPanel.qml | 319 ++++++++------------------ 1 file changed, 97 insertions(+), 222 deletions(-) diff --git a/Modules/Panels/Tray/TrayMenuPanel.qml b/Modules/Panels/Tray/TrayMenuPanel.qml index 363bbf20..a9a1967a 100644 --- a/Modules/Panels/Tray/TrayMenuPanel.qml +++ b/Modules/Panels/Tray/TrayMenuPanel.qml @@ -18,38 +18,14 @@ SmartPanel { // Internal property int menuWidth: 280 - readonly property int maxMenuWidth: 400 // Maximum width before text elides + readonly property int minMenuWidth: 280 + readonly property int maxMenuWidth: 600 preferredWidth: menuWidth // Height is content-driven via panelContent // Open positioned relative to button function openAt(buttonItem) { open(buttonItem) - // Recalculate width when opening - Qt.callLater(() => { - if (panelContent) { - const calculatedWidth = panelContent.calculateMaxEntryWidth() - if (calculatedWidth > 0) { - // Remove outer padding to get entry width - panelContent.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2)) - } - } - }) - } - - // Recalculate width when panel becomes visible - onVisibleChanged: { - if (visible) { - Qt.callLater(() => { - if (panelContent) { - const calculatedWidth = panelContent.calculateMaxEntryWidth() - if (calculatedWidth > 0) { - // Remove outer padding to get entry width - panelContent.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2)) - } - } - }) - } } panelContent: Item { @@ -61,74 +37,6 @@ SmartPanel { // Only show submenu in place (replace main menu) property bool inPlaceSubmenu: true - // TextMetrics for measuring text widths - TextMetrics { - id: textMetrics - font.pointSize: Style.fontSizeS - } - - // Track maximum entry width - property real maxEntryWidth: 280 - - // Calculate maximum entry width - function calculateMaxEntryWidth() { - if (!opener || !opener.children) { - return 280 // fallback - } - - let maxWidth = 0 - const entries = [...opener.children.values] - - for (var i = 0; i < entries.length; i++) { - const entry = entries[i] - if (!entry || entry.isSeparator) - continue - - // Calculate entry width - let entryWidth = 0 - - // Left margin - entryWidth += Style.marginM - - // Text width - const text = entry.text !== "" ? entry.text.replace(/[\n\r]+/g, ' ') : "..." - textMetrics.text = text - entryWidth += textMetrics.width - - // Spacing - entryWidth += Style.marginS - - // Icon (if visible) - if (entry.icon && entry.icon !== "") { - entryWidth += Style.marginL - entryWidth += Style.marginS - } - - // Menu icon (if has children) - if (entry.hasChildren) { - // Approximate icon width based on font size - entryWidth += Style.fontSizeS * 1.2 // Approximate icon width - entryWidth += Style.marginS - } - - // Right margin - entryWidth += Style.marginM - - if (entryWidth > maxWidth) { - maxWidth = entryWidth - } - } - - // Add outer padding (Style.marginM * 2) - return Math.max(280, maxWidth + (Style.marginM * 2)) - } - - // Update menu width when maxEntryWidth changes - onMaxEntryWidthChanged: { - const calculatedWidth = maxEntryWidth + (Style.marginM * 2) - root.menuWidth = Math.min(root.maxMenuWidth, Math.max(280, calculatedWidth)) - } - // Let Panel size to our content readonly property real contentPreferredWidth: root.menuWidth readonly property real contentPreferredHeight: { @@ -146,33 +54,110 @@ SmartPanel { QsMenuOpener { id: opener menu: root.menu + onChildrenChanged: Qt.callLater(() => calculateMenuWidth(opener)) + } - // Update menu width when children change - onChildrenChanged: { + // Text metrics for measuring text widths + TextMetrics { + id: textMetrics + font.pointSize: Style.fontSizeS + } + + // Watch for submenu changes + onActiveSubMenuChanged: { + if (activeSubMenu && inPlaceSubmenu) { Qt.callLater(() => { - const calculatedWidth = content.calculateMaxEntryWidth() - if (calculatedWidth > 0) { - // Remove outer padding to get entry width - content.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2)) + // Find the subMenuOpener in the loaded component + if (inPlaceSubMenuLoader.item) { + const flickable = inPlaceSubMenuLoader.item.children[1] // Flickable + if (flickable && flickable.children.length > 0) { + const columnLayout = flickable.children[0] + if (columnLayout && columnLayout.children.length > 0) { + const subOpener = columnLayout.children[0] + if (subOpener) { + calculateMenuWidth(subOpener) + } + } + } } }) + } else if (!activeSubMenu) { + // Reset to main menu width when submenu closes + Qt.callLater(() => calculateMenuWidth(opener)) } } - // Update width when menu changes - Connections { - target: root - function onMenuChanged() { - Qt.callLater(() => { - const calculatedWidth = content.calculateMaxEntryWidth() - if (calculatedWidth > 0) { - // Remove outer padding to get entry width - content.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2)) - } - }) + // Calculate required menu width based on menu entries + function calculateMenuWidth(menuOpener) { + let maxWidth = root.minMenuWidth + + // For submenus, also measure the "Back" button + if (menuOpener !== opener && content.inPlaceSubmenu) { + textMetrics.text = I18n.tr("settings.bar.tray.back") + const backWidth = (Style.marginM * 4) + Style.fontSizeS + Style.marginS + textMetrics.width + maxWidth = Math.max(maxWidth, backWidth) } + + // Check all menu entries + if (menuOpener && menuOpener.children) { + try { + const entries = menuOpener.children.values ? [...menuOpener.children.values] : [] + + for (var i = 0; i < entries.length; i++) { + const entry = entries[i] + if (!entry || entry.isSeparator) + continue + + const text = entry.text || "" + if (text === "") + continue + + // Measure the text + textMetrics.text = text.replace(/[\n\r]+/g, ' ') + const textWidth = textMetrics.width + + // Calculate total width: + // - Outer inset: Style.marginM * 2 (parent width reduction) + // - RowLayout margins: Style.marginM * 2 (left + right) + // - Text width + // - Spacing: Style.marginS + // - Icon: Style.marginL (if present) + // - Submenu arrow: ~20px (if present) + let requiredWidth = (Style.marginM * 4) + textWidth + + if (entry.icon && entry.icon !== "") { + requiredWidth += Style.marginS + Style.marginL + } + + if (entry.hasChildren) { + requiredWidth += Style.marginS + 20 + } + + maxWidth = Math.max(maxWidth, requiredWidth) + } + } catch (e) { + // Silently ignore errors during width calculation + } + } + + // Check pin/unpin button (only for main menu) + if (menuOpener === opener && root.trayItem !== null && root.widgetSection !== "" && root.widgetIndex >= 0) { + textMetrics.text = I18n.tr("settings.bar.tray.pin-application") + let pinWidth = (Style.marginM * 4) + textMetrics.width + Style.marginS + 20 + maxWidth = Math.max(maxWidth, pinWidth) + + textMetrics.text = I18n.tr("settings.bar.tray.unpin-application") + let unpinWidth = (Style.marginM * 4) + textMetrics.width + Style.marginS + 20 + maxWidth = Math.max(maxWidth, unpinWidth) + } + + // Clamp to min/max and apply + const finalWidth = Math.min(root.maxMenuWidth, Math.max(root.minMenuWidth, Math.ceil(maxWidth))) + root.menuWidth = finalWidth } + Component.onCompleted: Qt.callLater(() => calculateMenuWidth(opener)) + Component { id: subMenuComponent @@ -233,6 +218,7 @@ SmartPanel { QsMenuOpener { id: subMenuOpener menu: content.activeSubMenu + onChildrenChanged: Qt.callLater(() => content.calculateMenuWidth(subMenuOpener)) } // Back button (only shown when submenu is in-place) @@ -321,46 +307,6 @@ SmartPanel { color: Color.transparent - // Calculate and update max entry width - Component.onCompleted: { - if (!modelData?.isSeparator) { - Qt.callLater(() => { - let entryWidth = 0 - - // Left margin - entryWidth += Style.marginM - - // Text width - const text = modelData?.text !== "" ? modelData.text.replace(/[\n\r]+/g, ' ') : "..." - content.textMetrics.text = text - entryWidth += content.textMetrics.width - - // Spacing - entryWidth += Style.marginS - - // Icon (if visible) - if (modelData?.icon && modelData.icon !== "") { - entryWidth += Style.marginL - entryWidth += Style.marginS - } - - // Menu icon (if has children) - if (modelData?.hasChildren) { - entryWidth += Style.fontSizeS * 1.2 // Approximate icon width - entryWidth += Style.marginL // Submenu uses marginL for right margin on icon - } - - // Right margin - entryWidth += Style.marginM - - // Update maxEntryWidth if this entry is wider - if (entryWidth > content.maxEntryWidth) { - content.maxEntryWidth = entryWidth - } - }) - } - } - NDivider { anchors.centerIn: parent width: parent.width - (Style.marginM * 2) @@ -497,46 +443,6 @@ SmartPanel { color: Color.transparent - // Calculate and update max entry width - Component.onCompleted: { - if (!modelData?.isSeparator) { - Qt.callLater(() => { - let entryWidth = 0 - - // Left margin - entryWidth += Style.marginM - - // Text width - const text = modelData?.text !== "" ? modelData.text.replace(/[\n\r]+/g, ' ') : "..." - content.textMetrics.text = text - entryWidth += content.textMetrics.width - - // Spacing - entryWidth += Style.marginS - - // Icon (if visible) - if (modelData?.icon && modelData.icon !== "") { - entryWidth += Style.marginL - entryWidth += Style.marginS - } - - // Menu icon (if has children) - if (modelData?.hasChildren) { - entryWidth += Style.fontSizeS * 1.2 // Approximate icon width - entryWidth += Style.marginS - } - - // Right margin - entryWidth += Style.marginM - - // Update maxEntryWidth if this entry is wider - if (entryWidth > content.maxEntryWidth) { - content.maxEntryWidth = entryWidth - } - }) - } - } - NDivider { anchors.centerIn: parent width: parent.width - Style.marginL @@ -652,37 +558,6 @@ SmartPanel { Layout.preferredHeight: visible ? 28 : 0 color: Color.transparent - // Calculate and update max entry width - Component.onCompleted: { - if (visible) { - Qt.callLater(() => { - let entryWidth = 0 - - // Left margin - entryWidth += Style.marginM - - // Icon width - entryWidth += Style.fontSizeS * 1.2 // Approximate icon width - - // Spacing - entryWidth += Style.marginS - - // Text width - const text = isFavorite ? I18n.tr("settings.bar.tray.unpin-application") : I18n.tr("settings.bar.tray.pin-application") - content.textMetrics.text = text - entryWidth += content.textMetrics.width - - // Right margin - entryWidth += Style.marginM - - // Update maxEntryWidth if this entry is wider - if (entryWidth > content.maxEntryWidth) { - content.maxEntryWidth = entryWidth - } - }) - } - } - readonly property bool isFavorite: { if (!root.trayItem || root.widgetSection === "" || root.widgetIndex < 0) return false From 168bf54f4143c06f8c5ef2087b7f8ca78e4eff79 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 19:29:02 -0500 Subject: [PATCH 021/107] Fixing app templating after refactor --- Assets/Translations/de.json | 9 +++-- Assets/Translations/en.json | 10 +++-- Assets/Translations/es.json | 9 +++-- Assets/Translations/fr.json | 9 +++-- Assets/Translations/pt.json | 9 +++-- Assets/Translations/tr.json | 9 +++-- Assets/Translations/uk-UA.json | 9 +++-- Assets/Translations/zh-CN.json | 9 +++-- Assets/settings-default.json | 12 +++--- CLAUDE.md | 4 +- Commons/Settings.qml | 13 +++--- Modules/Background/Background.qml | 4 +- Modules/Bar/Widgets/ScreenRecorder.qml | 2 +- Modules/MainScreen/AllScreens.qml | 6 ++- Modules/MainScreen/MainScreen.qml | 17 +------- Modules/Panels/Launcher/Launcher.qml | 4 +- .../Panels/Settings/Tabs/ColorSchemeTab.qml | 10 ++--- Services/Location/NightLightService.qml | 1 + Services/Media/ScreenRecorderService.qml | 12 +++--- Services/System/ProgramCheckerService.qml | 2 +- .../AppThemeService.qml | 7 ++-- .../ColorPaletteGenerator.qml | 0 .../ColorSchemeService.qml | 1 + .../TemplateProcessor.qml | 40 ++++++++++++------- .../TemplateRegistry.qml | 0 Services/UI/PanelService.qml | 2 +- Services/UI/ToastService.qml | 2 +- shell.qml | 2 +- 28 files changed, 113 insertions(+), 101 deletions(-) rename Services/{DynamicTheming => Theming}/AppThemeService.qml (91%) rename Services/{DynamicTheming => Theming}/ColorPaletteGenerator.qml (100%) rename Services/{DynamicTheming => Theming}/ColorSchemeService.qml (99%) rename Services/{DynamicTheming => Theming}/TemplateProcessor.qml (92%) rename Services/{DynamicTheming => Theming}/TemplateRegistry.qml (100%) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 7b336459..f17e667c 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1719,10 +1719,6 @@ "kofi": { "opened": "Ko-fi-Seite in Ihrem Browser geöffnet" }, - "matugen": { - "failed": "Matugen-Verarbeitung fehlgeschlagen", - "failed-general": "Matugen ist beim Verarbeiten der Vorlagen auf einen Fehler gestoßen" - }, "missing-control-center": { "description": "Das Kontrollzentrum-Widget wurde aus der Leiste entfernt. Um es erneut über die Leiste zu öffnen, fügen Sie das Widget wieder hinzu. Sie können es auch durch Rechtsklick auf die Leiste öffnen.", "label": "Letztes Kontrollzentrum-Widget entfernt" @@ -1755,6 +1751,11 @@ "started": "Aufnahme gestartet", "stopping": "Aufnahme wird gestoppt…" }, + "theming-processor-failed": { + "desc-generic": "Beim Verarbeiten von Vorlagen ist ein Fehler aufgetreten.", + "title-matugen": "Matugen-Templating-Verarbeitung fehlgeschlagen", + "title-predefined": "Die Verarbeitung des vordefinierten Farbschemas ist fehlgeschlagen." + }, "wallpaper-colors": { "disabled": "Hintergrundbild-Farben deaktiviert", "enabled": "Hintergrundbild-Farben aktiviert", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 518eefb9..5a00ad0c 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1719,10 +1719,6 @@ "kofi": { "opened": "Ko-fi page opened in your browser" }, - "matugen": { - "failed": "Matugen processing failed", - "failed-general": "Matugen encountered an error while processing templates" - }, "missing-control-center": { "description": "The Control Center widget has been removed from the bar. To access it from the bar again, you will need to re-add the widget. You can open it with right clicking on the bar too.", "label": "Last Control Center widget removed" @@ -1755,7 +1751,13 @@ "started": "Recording started", "stopping": "Stopping recording…" }, + "theming-processor-failed": { + "desc-generic": "Encountered an error while processing templates", + "title-matugen": "Matugen templating processing failed", + "title-predefined": "Predefined color cheme processing failed" + }, "wallpaper-colors": { + "label": "Wallpaper colors generation", "disabled": "Wallpaper colors disabled", "enabled": "Wallpaper colors enabled", "not-installed": "Matugen not installed - required for wallpaper color extraction" diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 9399c515..91ffaa57 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1719,10 +1719,6 @@ "kofi": { "opened": "Página de Ko-fi abierta en tu navegador" }, - "matugen": { - "failed": "Error en el procesamiento de Matugen", - "failed-general": "Matugen encontró un error al procesar las plantillas" - }, "missing-control-center": { "description": "El widget del Centro de control se eliminó de la barra. Para acceder a él nuevamente desde la barra, debes volver a añadir el widget. También puedes abrirlo haciendo clic derecho en la barra.", "label": "Se eliminó el último widget del Centro de control" @@ -1755,6 +1751,11 @@ "started": "Grabación iniciada", "stopping": "Deteniendo grabación..." }, + "theming-processor-failed": { + "desc-generic": "Se encontró un error al procesar las plantillas.", + "title-matugen": "Falló el procesamiento de la plantilla Matugen.", + "title-predefined": "Falló el procesamiento del esquema de color predefinido." + }, "wallpaper-colors": { "disabled": "Colores del fondo de pantalla desactivados", "enabled": "Colores del fondo de pantalla activados", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 1eff9cdc..0ce5ef9d 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1719,10 +1719,6 @@ "kofi": { "opened": "Page Ko-fi ouverte dans votre navigateur" }, - "matugen": { - "failed": "Échec du traitement Matugen", - "failed-general": "Matugen a rencontré une erreur lors du traitement des modèles" - }, "missing-control-center": { "description": "Le widget du Centre de contrôle a été retiré de la barre. Pour y accéder à nouveau depuis la barre, veuillez ré‑ajouter le widget. Vous pouvez aussi l'ouvrir en cliquant avec le bouton droit sur la barre.", "label": "Dernier widget du Centre de contrôle supprimé" @@ -1755,6 +1751,11 @@ "started": "Enregistrement démarré", "stopping": "Arrêt de l'enregistrement..." }, + "theming-processor-failed": { + "desc-generic": "Une erreur s'est produite lors du traitement des modèles.", + "title-matugen": "Le traitement du modèle Matugen a échoué.", + "title-predefined": "Le traitement du schéma de couleurs prédéfini a échoué." + }, "wallpaper-colors": { "disabled": "Couleurs du fond d'écran désactivées", "enabled": "Couleurs du fond d'écran activées", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 5fbcd5c5..c3baf25e 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1719,10 +1719,6 @@ "kofi": { "opened": "Página do Ko-fi aberta no seu navegador" }, - "matugen": { - "failed": "Falha no processamento do Matugen", - "failed-general": "Matugen encontrou um erro ao processar os modelos" - }, "missing-control-center": { "description": "O widget da Central de Controle foi removido da barra. Para acessá-lo novamente pela barra, adicione o widget novamente. Você também pode abri-lo clicando com o botão direito na barra.", "label": "Último widget da Central de Controle removido" @@ -1755,6 +1751,11 @@ "started": "Gravação iniciada", "stopping": "Parando gravação…" }, + "theming-processor-failed": { + "desc-generic": "Ocorreu um erro ao processar os templates.", + "title-matugen": "Falha no processamento do template Matugen", + "title-predefined": "O processamento do esquema de cores predefinido falhou." + }, "wallpaper-colors": { "disabled": "Cores do papel de parede desativadas", "enabled": "Cores do papel de parede ativadas", diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 7f3dd309..43e038ce 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -1719,10 +1719,6 @@ "kofi": { "opened": "Ko-fi sayfası tarayıcınızda açıldı" }, - "matugen": { - "failed": "Matugen işlemi başarısız oldu", - "failed-general": "Matugen şablonları işlerken bir hata ile karşılaştı" - }, "missing-control-center": { "description": "Kontrol Merkezi widget'ı çubuktan kaldırıldı. Çubuktan tekrar erişmek için widget'ı yeniden eklemeniz gerekecek. Çubuğa sağ tıklayarak da açabilirsiniz.", "label": "Son Kontrol Merkezi widget'ı kaldırıldı" @@ -1755,6 +1751,11 @@ "started": "Kayıt başladı", "stopping": "Kayıt durduruluyor..." }, + "theming-processor-failed": { + "desc-generic": "Şablonlar işlenirken bir hatayla karşılaşıldı.", + "title-matugen": "Matugen şablon işleme başarısız oldu.", + "title-predefined": "Önceden tanımlanmış renk şeması işleme başarısız oldu." + }, "wallpaper-colors": { "disabled": "Duvar kağıdı renkleri devre dışı", "enabled": "Duvar kağıdı renkleri etkin", diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index 8f5e9163..3969ea44 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -1719,10 +1719,6 @@ "kofi": { "opened": "Сторінка Ko-fi відкрита у вашому браузері" }, - "matugen": { - "failed": "Помилка обробки Matugen", - "failed-general": "Matugen зіткнувся з помилкою під час обробки шаблонів" - }, "missing-control-center": { "description": "Віджет Центру керування видалено з панелі. Щоб знову отримати до нього доступ з панелі, вам потрібно повторно додати віджет. Ви також можете відкрити його, клацнувши правою кнопкою миші на панелі.", "label": "Останній віджет Центру керування видалено" @@ -1755,6 +1751,11 @@ "started": "Запис розпочато", "stopping": "Зупинка запису…" }, + "theming-processor-failed": { + "desc-generic": "Виникла помилка під час обробки шаблонів.", + "title-matugen": "Помилка обробки шаблонів Matugen", + "title-predefined": "Помилка обробки попередньо визначеної колірної схеми" + }, "wallpaper-colors": { "disabled": "Кольори шпалер вимкнено", "enabled": "Кольори шпалер увімкнено", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 6ab2bbef..371faa55 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1719,10 +1719,6 @@ "kofi": { "opened": "Ko-fi页面已在您的浏览器中打开" }, - "matugen": { - "failed": "Matugen 处理失败", - "failed-general": "Matugen 在处理模板时遇到错误" - }, "missing-control-center": { "description": "控制中心小部件已从状态栏中移除。要再次从状态栏访问它,您需要重新添加小部件。您也可以通过右键点击状态栏来打开它。", "label": "最后一个控制中心小部件已移除" @@ -1755,6 +1751,11 @@ "started": "录制已开始", "stopping": "正在停止录制…" }, + "theming-processor-failed": { + "desc-generic": "处理模板时遇到错误", + "title-matugen": "Matugen模板处理失败", + "title-predefined": "预定义的颜色方案处理失败" + }, "wallpaper-colors": { "disabled": "壁纸颜色已禁用", "enabled": "壁纸颜色已启用", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index fabf4225..ed319e04 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -257,17 +257,17 @@ "wezterm": false, "fuzzel": false, "discord": false, + "pywalfox": false, + "vicinae": false, + "walker": false, + "code": false, + "enableUserTemplates": false, "discord_vesktop": false, "discord_webcord": false, "discord_armcord": false, "discord_equibop": false, "discord_lightcord": false, - "discord_dorion": false, - "pywalfox": false, - "vicinae": false, - "walker": false, - "code": false, - "enableUserTemplates": false + "discord_dorion": false }, "nightLight": { "enabled": false, diff --git a/CLAUDE.md b/CLAUDE.md index 82ccb76b..bfa699f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -327,7 +327,7 @@ BarWidgetRegistry and ControlCenterWidgetRegistry: Services in Quickshell use the `pragma Singleton` directive: - Add `pragma Singleton` at the top of the QML file (before imports) - No qmldir needed - Quickshell handles singleton resolution automatically -- Import with standard path: `import qs.Services` or `import qs.Services.DynamicTheming` +- Import with standard path: `import qs.Services` or `import qs.Services.Theming` - Access via singleton name: `AppThemeService.generate()` Example: @@ -346,7 +346,7 @@ Singleton { } ``` -### DynamicTheming Architecture +### Theming Architecture The theming system was refactored (Jan 2025) into 4 focused services in `Services/`: **AppThemeService.qml** (~60 lines) - Main facade diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 6e2147f5..404e7897 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -402,17 +402,18 @@ Singleton { property bool wezterm: false property bool fuzzel: false property bool discord: false - property bool discord_vesktop: false - property bool discord_webcord: false - property bool discord_armcord: false - property bool discord_equibop: false - property bool discord_lightcord: false - property bool discord_dorion: false property bool pywalfox: false property bool vicinae: false property bool walker: false property bool code: false property bool enableUserTemplates: 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 + 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 } // night light diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index f6b97bdd..abea1806 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -332,10 +332,10 @@ Variants { var dim = Qt.size(0, 0) if (screenWidth >= screenHeight) { const w = Math.min(screenWidth, wpWidth) - dim = Qt.size(w, w / imageAspectRatio) + dim = Qt.size(Math.round(w), Math.round(w / imageAspectRatio)) } else { const h = Math.min(screenHeight, wpHeight) - dim = Qt.size(h * imageAspectRatio, h) + dim = Qt.size(Math.round(h * imageAspectRatio), Math.round(h)) } Logger.d("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height) diff --git a/Modules/Bar/Widgets/ScreenRecorder.qml b/Modules/Bar/Widgets/ScreenRecorder.qml index 8683f583..085db1e3 100644 --- a/Modules/Bar/Widgets/ScreenRecorder.qml +++ b/Modules/Bar/Widgets/ScreenRecorder.qml @@ -24,7 +24,7 @@ NIconButton { function handleClick() { if (!ScreenRecorderService.isAvailable) { - ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc"), 7000) + ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc")) return } ScreenRecorderService.toggleRecording() diff --git a/Modules/MainScreen/AllScreens.qml b/Modules/MainScreen/AllScreens.qml index dc929b57..9758790d 100644 --- a/Modules/MainScreen/AllScreens.qml +++ b/Modules/MainScreen/AllScreens.qml @@ -15,8 +15,10 @@ Variants { required property ShellScreen modelData property bool shouldBeActive: { - if (!modelData || !modelData.name) - Logger.d("Shell", "MainScreen activated for", modelData?.name) + if (!modelData || !modelData.name) { + return false + } + Logger.d("Shell", "MainScreen activated for", modelData?.name) return true } diff --git a/Modules/MainScreen/MainScreen.qml b/Modules/MainScreen/MainScreen.qml index 0edbe4d8..1d6fa665 100644 --- a/Modules/MainScreen/MainScreen.qml +++ b/Modules/MainScreen/MainScreen.qml @@ -163,7 +163,6 @@ PanelWindow { Component.onCompleted: { objectName = "audioPanel-" + (screen?.name || "unknown") PanelService.registerPanel(audioPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -175,7 +174,6 @@ PanelWindow { Component.onCompleted: { objectName = "batteryPanel-" + (screen?.name || "unknown") PanelService.registerPanel(batteryPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -187,7 +185,6 @@ PanelWindow { Component.onCompleted: { objectName = "bluetoothPanel-" + (screen?.name || "unknown") PanelService.registerPanel(bluetoothPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -199,7 +196,6 @@ PanelWindow { Component.onCompleted: { objectName = "controlCenterPanel-" + (screen?.name || "unknown") PanelService.registerPanel(controlCenterPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -211,7 +207,6 @@ PanelWindow { Component.onCompleted: { objectName = "calendarPanel-" + (screen?.name || "unknown") PanelService.registerPanel(calendarPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -223,7 +218,6 @@ PanelWindow { Component.onCompleted: { objectName = "launcherPanel-" + (screen?.name || "unknown") PanelService.registerPanel(launcherPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -235,7 +229,6 @@ PanelWindow { Component.onCompleted: { objectName = "notificationHistoryPanel-" + (screen?.name || "unknown") PanelService.registerPanel(notificationHistoryPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -247,7 +240,6 @@ PanelWindow { Component.onCompleted: { objectName = "sessionMenuPanel-" + (screen?.name || "unknown") PanelService.registerPanel(sessionMenuPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -259,7 +251,6 @@ PanelWindow { Component.onCompleted: { objectName = "settingsPanel-" + (screen?.name || "unknown") PanelService.registerPanel(settingsPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -271,7 +262,6 @@ PanelWindow { Component.onCompleted: { objectName = "setupWizardPanel-" + (screen?.name || "unknown") PanelService.registerPanel(setupWizardPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -283,7 +273,6 @@ PanelWindow { Component.onCompleted: { objectName = "trayDrawerPanel-" + (screen?.name || "unknown") PanelService.registerPanel(trayDrawerPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -295,7 +284,6 @@ PanelWindow { Component.onCompleted: { objectName = "trayMenuPanel-" + (screen?.name || "unknown") PanelService.registerPanel(trayMenuPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -307,7 +295,6 @@ PanelWindow { Component.onCompleted: { objectName = "wallpaperPanel-" + (screen?.name || "unknown") PanelService.registerPanel(wallpaperPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -319,7 +306,6 @@ PanelWindow { Component.onCompleted: { objectName = "wifiPanel-" + (screen?.name || "unknown") PanelService.registerPanel(wifiPanel) - Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name) } } @@ -338,9 +324,8 @@ PanelWindow { property ShellScreen screen: root.screen onLoaded: { - Logger.d("MainScreen", "Bar loaded:", item !== null) if (item) { - Logger.d("MainScreen", "Bar screen", item.screen?.name, "size:", item.width, "x", item.height) + Logger.d("MainScreen", "Bar loaded with screen", item.screen?.name) // Bind screen to bar component (use binding for reactivity) item.screen = Qt.binding(function () { return barLoader.screen diff --git a/Modules/Panels/Launcher/Launcher.qml b/Modules/Panels/Launcher/Launcher.qml index d26f1b0b..7cc1248a 100644 --- a/Modules/Panels/Launcher/Launcher.qml +++ b/Modules/Panels/Launcher/Launcher.qml @@ -4,8 +4,10 @@ import QtQuick.Layouts import Quickshell import Quickshell.Widgets import qs.Commons -import qs.Widgets import qs.Modules.MainScreen +import qs.Services.Keyboard +import qs.Widgets + import "Plugins" SmartPanel { diff --git a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml index c4570b8b..b4059379 100644 --- a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml @@ -4,7 +4,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import qs.Commons -import qs.Services.DynamicTheming +import qs.Services.Theming import qs.Services.System import qs.Services.UI import qs.Widgets @@ -113,9 +113,9 @@ ColumnLayout { if (exitCode === 0) { Settings.data.colorSchemes.useWallpaperColors = true AppThemeService.generate() - ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.enabled"), "settings-color-scheme") + ToastService.showNotice(I18n.tr("toast.wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.enabled"), "settings-color-scheme") } else { - ToastService.showWarning(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.not-installed")) + ToastService.showWarning(I18n.tr("oast.wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.not-installed")) } } @@ -130,7 +130,6 @@ ColumnLayout { Repeater { model: ColorSchemeService.schemes - delegate: Item { FileView { path: modelData @@ -250,7 +249,7 @@ ColumnLayout { matugenCheck.running = true } else { Settings.data.colorSchemes.useWallpaperColors = false - ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"), "settings-color-scheme") + ToastService.showNotice(I18n.tr("toast.wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"), "settings-color-scheme") if (Settings.data.colorSchemes.predefinedScheme) { ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) @@ -470,6 +469,7 @@ ColumnLayout { ColumnLayout { Layout.fillWidth: true spacing: Style.marginL + visible: Settings.data.colorSchemes.useWallpaperColors || Settings.data.colorSchemes.generateTemplatesForPredefined NHeader { label: I18n.tr("settings.color-scheme.templates.section.label") diff --git a/Services/Location/NightLightService.qml b/Services/Location/NightLightService.qml index 8160c9e9..e25c828a 100644 --- a/Services/Location/NightLightService.qml +++ b/Services/Location/NightLightService.qml @@ -4,6 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons +import qs.Services.UI Singleton { id: root diff --git a/Services/Media/ScreenRecorderService.qml b/Services/Media/ScreenRecorderService.qml index 8b4d9f98..22b8f649 100644 --- a/Services/Media/ScreenRecorderService.qml +++ b/Services/Media/ScreenRecorderService.qml @@ -113,7 +113,7 @@ Singleton { // Check if gpu-screen-recorder is not installed const stdout = String(recorderProcess.stdout.text || "").trim() if (stdout === "GPU_SCREEN_RECORDER_NOT_INSTALLED") { - ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc"), 7000) + ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc")) return } @@ -121,9 +121,9 @@ Singleton { if (exitCode !== 0) { const err = String(recorderProcess.stderr.text || "").trim() if (err.length > 0) - ToastService.showError(I18n.tr("toast.recording.failed-start"), err, 7000) + ToastService.showError(I18n.tr("toast.recording.failed-start"), err) else - ToastService.showError(I18n.tr("toast.recording.failed-start"), I18n.tr("toast.recording.failed-gpu"), 7000) + ToastService.showError(I18n.tr("toast.recording.failed-start"), I18n.tr("toast.recording.failed-gpu")) } } else if (isRecording) { // Process ended normally while recording @@ -135,9 +135,9 @@ Singleton { } else { const err2 = String(recorderProcess.stderr.text || "").trim() if (err2.length > 0) - ToastService.showError(I18n.tr("toast.recording.failed-start"), err2, 7000) + ToastService.showError(I18n.tr("toast.recording.failed-start"), err2) else - ToastService.showError(I18n.tr("toast.recording.failed-start"), I18n.tr("toast.recording.failed-general"), 7000) + ToastService.showError(I18n.tr("toast.recording.failed-start"), I18n.tr("toast.recording.failed-general")) } } } @@ -153,7 +153,7 @@ Singleton { } else { isPending = false hasActiveRecording = false - ToastService.showError(I18n.tr("toast.recording.no-portals"), I18n.tr("toast.recording.no-portals-desc"), 8000) + ToastService.showError(I18n.tr("toast.recording.no-portals"), I18n.tr("toast.recording.no-portals-desc")) } } } diff --git a/Services/System/ProgramCheckerService.qml b/Services/System/ProgramCheckerService.qml index e569ce1e..13b2bc34 100644 --- a/Services/System/ProgramCheckerService.qml +++ b/Services/System/ProgramCheckerService.qml @@ -4,7 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons -import qs.Services.DynamicTheming +import qs.Services.Theming // Service to check if various programs are available on the system Singleton { diff --git a/Services/DynamicTheming/AppThemeService.qml b/Services/Theming/AppThemeService.qml similarity index 91% rename from Services/DynamicTheming/AppThemeService.qml rename to Services/Theming/AppThemeService.qml index 167309da..74c95099 100644 --- a/Services/DynamicTheming/AppThemeService.qml +++ b/Services/Theming/AppThemeService.qml @@ -8,15 +8,15 @@ import qs.Services.UI Singleton { id: root - // PUBLIC API - maintain compatibility with existing code readonly property string colorsApplyScript: Quickshell.shellDir + '/Bin/colors-apply.sh' - // Connections to trigger regeneration Connections { target: WallpaperService + + // When the wallpaper changes, regenerate with Matugen if necessary function onWallpaperChanged(screenName, path) { if (screenName === Screen.name && Settings.data.colorSchemes.useWallpaperColors) { - generate() + generateFromWallpaper() } } } @@ -39,6 +39,7 @@ Singleton { generateFromWallpaper() } else { ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) + generateFromPredefinedScheme() } } diff --git a/Services/DynamicTheming/ColorPaletteGenerator.qml b/Services/Theming/ColorPaletteGenerator.qml similarity index 100% rename from Services/DynamicTheming/ColorPaletteGenerator.qml rename to Services/Theming/ColorPaletteGenerator.qml diff --git a/Services/DynamicTheming/ColorSchemeService.qml b/Services/Theming/ColorSchemeService.qml similarity index 99% rename from Services/DynamicTheming/ColorSchemeService.qml rename to Services/Theming/ColorSchemeService.qml index 734d8bd9..a853e651 100644 --- a/Services/DynamicTheming/ColorSchemeService.qml +++ b/Services/Theming/ColorSchemeService.qml @@ -5,6 +5,7 @@ import Qt.labs.folderlistmodel import Quickshell import Quickshell.Io import qs.Commons +import qs.Services.UI Singleton { id: root diff --git a/Services/DynamicTheming/TemplateProcessor.qml b/Services/Theming/TemplateProcessor.qml similarity index 92% rename from Services/DynamicTheming/TemplateProcessor.qml rename to Services/Theming/TemplateProcessor.qml index b436967c..86f2c3bb 100644 --- a/Services/DynamicTheming/TemplateProcessor.qml +++ b/Services/Theming/TemplateProcessor.qml @@ -29,7 +29,7 @@ Singleton { /** * Process wallpaper colors using matugen CLI - * Requirement #4: Dual-path architecture (wallpaper uses matugen CLI) + * Dual-path architecture (wallpaper uses matugen CLI) */ function processWallpaperColors(wallpaperPath, mode) { const content = buildMatugenConfig() @@ -39,6 +39,7 @@ Singleton { const wp = wallpaperPath.replace(/'/g, "'\\''") const script = buildMatugenScript(content, wp, mode) + generateProcess.generator = "matugen" generateProcess.command = ["bash", "-lc", script] generateProcess.running = true } @@ -46,7 +47,7 @@ Singleton { /** * Process predefined color scheme using sed scripts - * Requirement #4: Dual-path architecture (predefined uses sed scripts) + * Dual-path architecture (predefined uses sed scripts) */ function processPredefinedScheme(schemeData, mode) { handleTerminalThemes(mode) @@ -57,6 +58,7 @@ Singleton { // Add user templates if enabled (requirement #1) script += buildUserTemplateCommandForPredefined(schemeData, mode) + generateProcess.generator = "predefined" generateProcess.command = ["bash", "-lc", script] generateProcess.running = true } @@ -167,7 +169,6 @@ Singleton { } } }) - return script } @@ -184,6 +185,7 @@ Singleton { const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) const baseConfigDir = outputDir.replace("/themes", "") + script += `\n` script += `if [ -d "${baseConfigDir}" ]; then\n` script += ` mkdir -p ${outputDir}\n` script += ` cp '${templatePath}' '${outputPath}'\n` @@ -205,9 +207,11 @@ Singleton { const outputPath = output.path.replace("~", homeDir) const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) + script += `\n` script += `mkdir -p ${outputDir}\n` script += `cp '${templatePath}' '${outputPath}'\n` script += replaceColorsInFile(outputPath, palette) + script += `\n` }) if (app.postProcess) { @@ -269,7 +273,7 @@ Singleton { } // ================================================================================ - // USER TEMPLATES + // USER TEMPLATES, advanced usage // ================================================================================ function buildUserTemplateCommand(input, mode) { if (!Settings.data.templates.enableUserTemplates) @@ -320,17 +324,21 @@ Singleton { workingDirectory: Quickshell.shellDir running: false + // Error reporting helpers + property string generator: "" + function showError() { + const description = (stderr.text && stderr.text.trim() !== "") ? stderr.text.trim() : ((stdout.text && stdout.text.trim() !== "") ? stdout.text.trim() : I18n.tr("toast.theming-processor-failed.desc-generic")) + const title = I18n.tr(`toast.theming-processor-failed.title-${generator}`) + + // Give a bit more time to the user to read, as it can contains important information for debugging user's templates. + ToastService.showError(title, description, 8000) + return description + } + onExited: function (exitCode) { if (exitCode !== 0) { - const errorMsg = (stderr.text && stderr.text.trim() !== "") ? stderr.text.trim() : ((stdout.text && stdout.text.trim() !== "") ? stdout.text.trim() : I18n.tr("toast.matugen.failed-general")) - Logger.e("TemplateProcessor", "Process failed with exit code:", exitCode) - if (stderr.text && stderr.text.trim() !== "") { - Logger.e("TemplateProcessor", "stderr:", stderr.text) - } - if (stdout.text && stdout.text.trim() !== "") { - Logger.e("TemplateProcessor", "stdout:", stdout.text) - } - ToastService.showError(I18n.tr("toast.matugen.failed"), errorMsg, 6000) + const description = generateProcess.showError() + Logger.e("TemplateProcessor", "Process failed with exit code", exitCode, description) } } @@ -342,8 +350,10 @@ Singleton { } stderr: StdioCollector { onStreamFinished: { - if (this.text) - Logger.d("TemplateProcessor", "stderr:", this.text) + if (this.text) { + const description = generateProcess.showError() + Logger.e("TemplateProcessor", "Process failed with exit code", exitCode, description) + } } } } diff --git a/Services/DynamicTheming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml similarity index 100% rename from Services/DynamicTheming/TemplateRegistry.qml rename to Services/Theming/TemplateRegistry.qml diff --git a/Services/UI/PanelService.qml b/Services/UI/PanelService.qml index e7519cca..d22fc575 100644 --- a/Services/UI/PanelService.qml +++ b/Services/UI/PanelService.qml @@ -25,7 +25,7 @@ Singleton { // Register this panel (called after panel is loaded) function registerPanel(panel) { registeredPanels[panel.objectName] = panel - Logger.i("PanelService", "Registered panel:", panel.objectName) + Logger.d("PanelService", "Registered panel:", panel.objectName) } // Returns a panel (loads it on-demand if not yet loaded) diff --git a/Services/UI/ToastService.qml b/Services/UI/ToastService.qml index fd39e6ba..5a5a0929 100644 --- a/Services/UI/ToastService.qml +++ b/Services/UI/ToastService.qml @@ -18,7 +18,7 @@ Singleton { notify(message, description, "", "warning", duration) } - function showError(message, description = "", duration = 5000) { + function showError(message, description = "", duration = 6000) { notify(message, description, "", "error", duration) } } diff --git a/shell.qml b/shell.qml index af944e62..646a3c5d 100644 --- a/shell.qml +++ b/shell.qml @@ -13,7 +13,7 @@ import Quickshell // Commons & Services import qs.Commons import qs.Services.Control -import qs.Services.DynamicTheming +import qs.Services.Theming import qs.Services.Hardware import qs.Services.Location import qs.Services.Networking From 4b751df53fd4a46b8a8605dcc3ae0008c0d0d651 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 22:25:41 -0500 Subject: [PATCH 022/107] Optimization: caching to GPU texture when no animations. --- .../MainScreen/Backgrounds/AllBackgrounds.qml | 216 +++++----- Modules/MainScreen/ScreenCorners.qml | 394 +++++++++--------- 2 files changed, 316 insertions(+), 294 deletions(-) diff --git a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml index 75487bc2..7d953f3d 100644 --- a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml +++ b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml @@ -25,125 +25,133 @@ Item { anchors.fill: parent - // The unified Shape container - Shape { - id: backgroundsShape + // Wrapper with layer caching for better shadow performance + Item { anchors.fill: parent - // Use curve renderer for smooth corners - preferredRendererType: Shape.CurveRenderer + // Enable layer caching to prevent continuous re-rendering + // This caches the Shape to a GPU texture, reducing GPU tessellation overhead + layer.enabled: true - // CRITICAL: Shape must not block mouse input! - // ShapePaths inside will render, but the Shape container itself should be transparent to input - enabled: false // Disable mouse input on the Shape itself + // The unified Shape container + Shape { + id: backgroundsShape + anchors.fill: parent - Component.onCompleted: { - Logger.d("AllBackgrounds", "AllBackgrounds initialized") - Logger.d("AllBackgrounds", " bar:", root.bar) - Logger.d("AllBackgrounds", " windowRoot:", root.windowRoot) - } + // Use curve renderer for smooth corners (GPU-accelerated) + preferredRendererType: Shape.CurveRenderer + + enabled: false // Disable mouse input on the Shape itself + + Component.onCompleted: { + Logger.d("AllBackgrounds", "AllBackgrounds initialized") + Logger.d("AllBackgrounds", " bar:", root.bar) + Logger.d("AllBackgrounds", " windowRoot:", root.windowRoot) + } - /** + /** * Bar */ - BarBackground { - bar: root.bar - shapeContainer: backgroundsShape - } + BarBackground { + bar: root.bar + shapeContainer: backgroundsShape + } - /** + /** * Panels */ - // Audio - PanelBackground { - panel: root.windowRoot.audioPanel - shapeContainer: backgroundsShape + // Audio + PanelBackground { + panel: root.windowRoot.audioPanel + shapeContainer: backgroundsShape + } + + // Battery + PanelBackground { + panel: root.windowRoot.batteryPanel + shapeContainer: backgroundsShape + } + + // Bluetooth + PanelBackground { + panel: root.windowRoot.bluetoothPanel + shapeContainer: backgroundsShape + } + + // Calendar + PanelBackground { + panel: root.windowRoot.calendarPanel + shapeContainer: backgroundsShape + } + + // Control Center + PanelBackground { + panel: root.windowRoot.controlCenterPanel + shapeContainer: backgroundsShape + } + + // Launcher + PanelBackground { + panel: root.windowRoot.launcherPanel + shapeContainer: backgroundsShape + } + + // Notification History + PanelBackground { + panel: root.windowRoot.notificationHistoryPanel + shapeContainer: backgroundsShape + } + + // Session Menu + PanelBackground { + panel: root.windowRoot.sessionMenuPanel + shapeContainer: backgroundsShape + } + + // Settings + PanelBackground { + panel: root.windowRoot.settingsPanel + shapeContainer: backgroundsShape + } + + // Setup Wizard + PanelBackground { + panel: root.windowRoot.setupWizardPanel + shapeContainer: backgroundsShape + } + + // TrayDrawer + PanelBackground { + panel: root.windowRoot.trayDrawerPanel + shapeContainer: backgroundsShape + } + + // TrayMenu + PanelBackground { + panel: root.windowRoot.trayMenuPanel + shapeContainer: backgroundsShape + } + + // Wallpaper + PanelBackground { + panel: root.windowRoot.wallpaperPanel + shapeContainer: backgroundsShape + } + + // WiFi + PanelBackground { + panel: root.windowRoot.wifiPanel + shapeContainer: backgroundsShape + } } - // Battery - PanelBackground { - panel: root.windowRoot.batteryPanel - shapeContainer: backgroundsShape + // Apply shadow to the cached layer + NDropShadows { + anchors.fill: parent + source: backgroundsShape } - - // Bluetooth - PanelBackground { - panel: root.windowRoot.bluetoothPanel - shapeContainer: backgroundsShape - } - - // Calendar - PanelBackground { - panel: root.windowRoot.calendarPanel - shapeContainer: backgroundsShape - } - - // Control Center - PanelBackground { - panel: root.windowRoot.controlCenterPanel - shapeContainer: backgroundsShape - } - - // Launcher - PanelBackground { - panel: root.windowRoot.launcherPanel - shapeContainer: backgroundsShape - } - - // Notification History - PanelBackground { - panel: root.windowRoot.notificationHistoryPanel - shapeContainer: backgroundsShape - } - - // Session Menu - PanelBackground { - panel: root.windowRoot.sessionMenuPanel - shapeContainer: backgroundsShape - } - - // Settings - PanelBackground { - panel: root.windowRoot.settingsPanel - shapeContainer: backgroundsShape - } - - // Setup Wizard - PanelBackground { - panel: root.windowRoot.setupWizardPanel - shapeContainer: backgroundsShape - } - - // TrayDrawer - PanelBackground { - panel: root.windowRoot.trayDrawerPanel - shapeContainer: backgroundsShape - } - - // TrayMenu - PanelBackground { - panel: root.windowRoot.trayMenuPanel - shapeContainer: backgroundsShape - } - - // Wallpaper - PanelBackground { - panel: root.windowRoot.wallpaperPanel - shapeContainer: backgroundsShape - } - - // WiFi - PanelBackground { - panel: root.windowRoot.wifiPanel - shapeContainer: backgroundsShape - } - } - - NDropShadows { - anchors.fill: parent - source: backgroundsShape } } diff --git a/Modules/MainScreen/ScreenCorners.qml b/Modules/MainScreen/ScreenCorners.qml index 9f4c2e9e..89dec642 100644 --- a/Modules/MainScreen/ScreenCorners.qml +++ b/Modules/MainScreen/ScreenCorners.qml @@ -9,207 +9,221 @@ import qs.Commons * Renders concave corners at the screen edges to create a rounded screen effect. * Self-contained Shape component (no shadows). */ -Shape { +Item { id: root anchors.fill: parent - preferredRendererType: Shape.CurveRenderer - enabled: false // Disable mouse input - ShapePath { - id: cornersPath + // Wrapper with layer caching to reduce GPU tessellation overhead + Item { + anchors.fill: parent - // Corner configuration - readonly property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Color.black : Color.mSurface - readonly property real cornerRadius: Style.screenRadius - readonly property real cornerSize: Style.screenRadius + // Cache the Shape to a texture to prevent continuous re-tessellation + layer.enabled: true - // Determine margins based on bar position - readonly property real topMargin: 0 - readonly property real bottomMargin: 0 - readonly property real leftMargin: 0 - readonly property real rightMargin: 0 + Shape { + id: cornersShape - // Screen dimensions - readonly property real screenWidth: root.width - readonly property real screenHeight: root.height + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + enabled: false // Disable mouse input - // Only show screen corners if enabled and appropriate conditions are met - readonly property bool shouldShow: Settings.data.general.showScreenCorners + ShapePath { + id: cornersPath - // ShapePath configuration - strokeWidth: -1 // No stroke, fill only - fillColor: shouldShow ? cornerColor : Color.transparent + // Corner configuration + readonly property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Color.black : Color.mSurface + readonly property real cornerRadius: Style.screenRadius + readonly property real cornerSize: Style.screenRadius - // Smooth color animation - Behavior on fillColor { - ColorAnimation { - duration: Style.animationFast + // Determine margins based on bar position + readonly property real topMargin: 0 + readonly property real bottomMargin: 0 + readonly property real leftMargin: 0 + readonly property real rightMargin: 0 + + // Screen dimensions + readonly property real screenWidth: cornersShape.width + readonly property real screenHeight: cornersShape.height + + // Only show screen corners if enabled and appropriate conditions are met + readonly property bool shouldShow: Settings.data.general.showScreenCorners + + // ShapePath configuration + strokeWidth: -1 // No stroke, fill only + fillColor: shouldShow ? cornerColor : Color.transparent + + // Smooth color animation + Behavior on fillColor { + ColorAnimation { + duration: Style.animationFast + } + } + + // ========== PATH DEFINITION ========== + // Draws 4 separate corner squares at screen edges + // Each corner square has a concave arc on the inner diagonal + + // ========== TOP-LEFT CORNER ========== + // Arc is at the bottom-right of this square (inner diagonal) + // Start at top-left screen corner + startX: leftMargin + startY: topMargin + + // Top edge (moving right) + PathLine { + relativeX: cornersPath.cornerSize + relativeY: 0 + } + + // Right edge (moving down toward arc) + PathLine { + relativeX: 0 + relativeY: cornersPath.cornerSize - cornersPath.cornerRadius + } + + // Concave arc (bottom-right corner of square, curving inward toward screen center) + PathArc { + relativeX: -cornersPath.cornerRadius + relativeY: cornersPath.cornerRadius + radiusX: cornersPath.cornerRadius + radiusY: cornersPath.cornerRadius + direction: PathArc.Counterclockwise + } + + // Bottom edge (moving left) + PathLine { + relativeX: -(cornersPath.cornerSize - cornersPath.cornerRadius) + relativeY: 0 + } + + // Left edge (moving up) - closes back to start + PathLine { + relativeX: 0 + relativeY: -cornersPath.cornerSize + } + + // ========== TOP-RIGHT CORNER ========== + // Arc is at the bottom-left of this square (inner diagonal) + PathMove { + x: cornersPath.screenWidth - cornersPath.rightMargin - cornersPath.cornerSize + y: cornersPath.topMargin + } + + // Top edge (moving right) + PathLine { + relativeX: cornersPath.cornerSize + relativeY: 0 + } + + // Right edge (moving down) + PathLine { + relativeX: 0 + relativeY: cornersPath.cornerSize + } + + // Bottom edge (moving left toward arc) + PathLine { + relativeX: -(cornersPath.cornerSize - cornersPath.cornerRadius) + relativeY: 0 + } + + // Concave arc (bottom-left corner of square, curving inward toward screen center) + PathArc { + relativeX: -cornersPath.cornerRadius + relativeY: -cornersPath.cornerRadius + radiusX: cornersPath.cornerRadius + radiusY: cornersPath.cornerRadius + direction: PathArc.Counterclockwise + } + + // Left edge (moving up) - closes back to start + PathLine { + relativeX: 0 + relativeY: -(cornersPath.cornerSize - cornersPath.cornerRadius) + } + + // ========== BOTTOM-LEFT CORNER ========== + // Arc is at the top-right of this square (inner diagonal) + PathMove { + x: cornersPath.leftMargin + y: cornersPath.screenHeight - cornersPath.bottomMargin - cornersPath.cornerSize + } + + // Top edge (moving right toward arc) + PathLine { + relativeX: cornersPath.cornerSize - cornersPath.cornerRadius + relativeY: 0 + } + + // Concave arc (top-right corner of square, curving inward toward screen center) + PathArc { + relativeX: cornersPath.cornerRadius + relativeY: cornersPath.cornerRadius + radiusX: cornersPath.cornerRadius + radiusY: cornersPath.cornerRadius + direction: PathArc.Counterclockwise + } + + // Right edge (moving down) + PathLine { + relativeX: 0 + relativeY: cornersPath.cornerSize - cornersPath.cornerRadius + } + + // Bottom edge (moving left) + PathLine { + relativeX: -cornersPath.cornerSize + relativeY: 0 + } + + // Left edge (moving up) - closes back to start + PathLine { + relativeX: 0 + relativeY: -cornersPath.cornerSize + } + + // ========== BOTTOM-RIGHT CORNER ========== + // Arc is at the top-left of this square (inner diagonal) + // Start at bottom-right of square (different from other corners!) + PathMove { + x: cornersPath.screenWidth - cornersPath.rightMargin + y: cornersPath.screenHeight - cornersPath.bottomMargin + } + + // Bottom edge (moving left) + PathLine { + relativeX: -cornersPath.cornerSize + relativeY: 0 + } + + // Left edge (moving up toward arc) + PathLine { + relativeX: 0 + relativeY: -(cornersPath.cornerSize - cornersPath.cornerRadius) + } + + // Concave arc (top-left corner of square, curving inward toward screen center) + PathArc { + relativeX: cornersPath.cornerRadius + relativeY: -cornersPath.cornerRadius + radiusX: cornersPath.cornerRadius + radiusY: cornersPath.cornerRadius + direction: PathArc.Counterclockwise + } + + // Top edge (moving right) + PathLine { + relativeX: cornersPath.cornerSize - cornersPath.cornerRadius + relativeY: 0 + } + + // Right edge (moving down) - closes back to start + PathLine { + relativeX: 0 + relativeY: cornersPath.cornerSize + } } } - - // ========== PATH DEFINITION ========== - // Draws 4 separate corner squares at screen edges - // Each corner square has a concave arc on the inner diagonal - - // ========== TOP-LEFT CORNER ========== - // Arc is at the bottom-right of this square (inner diagonal) - // Start at top-left screen corner - startX: leftMargin - startY: topMargin - - // Top edge (moving right) - PathLine { - relativeX: cornersPath.cornerSize - relativeY: 0 - } - - // Right edge (moving down toward arc) - PathLine { - relativeX: 0 - relativeY: cornersPath.cornerSize - cornersPath.cornerRadius - } - - // Concave arc (bottom-right corner of square, curving inward toward screen center) - PathArc { - relativeX: -cornersPath.cornerRadius - relativeY: cornersPath.cornerRadius - radiusX: cornersPath.cornerRadius - radiusY: cornersPath.cornerRadius - direction: PathArc.Counterclockwise - } - - // Bottom edge (moving left) - PathLine { - relativeX: -(cornersPath.cornerSize - cornersPath.cornerRadius) - relativeY: 0 - } - - // Left edge (moving up) - closes back to start - PathLine { - relativeX: 0 - relativeY: -cornersPath.cornerSize - } - - // ========== TOP-RIGHT CORNER ========== - // Arc is at the bottom-left of this square (inner diagonal) - PathMove { - x: cornersPath.screenWidth - cornersPath.rightMargin - cornersPath.cornerSize - y: cornersPath.topMargin - } - - // Top edge (moving right) - PathLine { - relativeX: cornersPath.cornerSize - relativeY: 0 - } - - // Right edge (moving down) - PathLine { - relativeX: 0 - relativeY: cornersPath.cornerSize - } - - // Bottom edge (moving left toward arc) - PathLine { - relativeX: -(cornersPath.cornerSize - cornersPath.cornerRadius) - relativeY: 0 - } - - // Concave arc (bottom-left corner of square, curving inward toward screen center) - PathArc { - relativeX: -cornersPath.cornerRadius - relativeY: -cornersPath.cornerRadius - radiusX: cornersPath.cornerRadius - radiusY: cornersPath.cornerRadius - direction: PathArc.Counterclockwise - } - - // Left edge (moving up) - closes back to start - PathLine { - relativeX: 0 - relativeY: -(cornersPath.cornerSize - cornersPath.cornerRadius) - } - - // ========== BOTTOM-LEFT CORNER ========== - // Arc is at the top-right of this square (inner diagonal) - PathMove { - x: cornersPath.leftMargin - y: cornersPath.screenHeight - cornersPath.bottomMargin - cornersPath.cornerSize - } - - // Top edge (moving right toward arc) - PathLine { - relativeX: cornersPath.cornerSize - cornersPath.cornerRadius - relativeY: 0 - } - - // Concave arc (top-right corner of square, curving inward toward screen center) - PathArc { - relativeX: cornersPath.cornerRadius - relativeY: cornersPath.cornerRadius - radiusX: cornersPath.cornerRadius - radiusY: cornersPath.cornerRadius - direction: PathArc.Counterclockwise - } - - // Right edge (moving down) - PathLine { - relativeX: 0 - relativeY: cornersPath.cornerSize - cornersPath.cornerRadius - } - - // Bottom edge (moving left) - PathLine { - relativeX: -cornersPath.cornerSize - relativeY: 0 - } - - // Left edge (moving up) - closes back to start - PathLine { - relativeX: 0 - relativeY: -cornersPath.cornerSize - } - - // ========== BOTTOM-RIGHT CORNER ========== - // Arc is at the top-left of this square (inner diagonal) - // Start at bottom-right of square (different from other corners!) - PathMove { - x: cornersPath.screenWidth - cornersPath.rightMargin - y: cornersPath.screenHeight - cornersPath.bottomMargin - } - - // Bottom edge (moving left) - PathLine { - relativeX: -cornersPath.cornerSize - relativeY: 0 - } - - // Left edge (moving up toward arc) - PathLine { - relativeX: 0 - relativeY: -(cornersPath.cornerSize - cornersPath.cornerRadius) - } - - // Concave arc (top-left corner of square, curving inward toward screen center) - PathArc { - relativeX: cornersPath.cornerRadius - relativeY: -cornersPath.cornerRadius - radiusX: cornersPath.cornerRadius - radiusY: cornersPath.cornerRadius - direction: PathArc.Counterclockwise - } - - // Top edge (moving right) - PathLine { - relativeX: cornersPath.cornerSize - cornersPath.cornerRadius - relativeY: 0 - } - - // Right edge (moving down) - closes back to start - PathLine { - relativeX: 0 - relativeY: cornersPath.cornerSize - } } } From 8f4637b3b93c045bf12c524dc1f67ae9b797af22 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 22:28:41 -0500 Subject: [PATCH 023/107] SettingsPanel: on bar position changed, recalculate position. --- Modules/Panels/Settings/SettingsPanel.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index 1df4c2a1..c8113b22 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -31,6 +31,12 @@ SmartPanel { } } + onBarPositionChanged: { + if (isPanelOpen) { + Qt.callLater(root.setPosition) + } + } + // Tabs enumeration, order is NOT relevant enum Tab { About, From 373398236a8c95131e937eb71af016fe080d31ad Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 22:35:39 -0500 Subject: [PATCH 024/107] KickOff Font Service on startup --- shell.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/shell.qml b/shell.qml index 646a3c5d..f21e6d58 100644 --- a/shell.qml +++ b/shell.qml @@ -79,6 +79,7 @@ ShellRoot { IdleInhibitorService.init() PowerProfileService.init() DistroService.init() + FontService.init() // Only open the setup wizard for new users if (!Settings.data.setupCompleted) { From 8b7ec73fdffda666777b57185e2ede004ff83a79 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 22:37:19 -0500 Subject: [PATCH 025/107] No need to track open popups anymore. Avoid widgets to include services directly --- .../Settings/Bar/BarWidgetSettingsDialog.qml | 7 ------- .../ControlCenterWidgetSettingsDialog.qml | 5 ----- Modules/Panels/Settings/SettingsPanel.qml | 2 +- Services/UI/PanelService.qml | 19 ------------------- Widgets/NColorPickerDialog.qml | 8 -------- Widgets/NComboBox.qml | 8 -------- Widgets/NContextMenu.qml | 3 --- Widgets/NFilePicker.qml | 8 -------- Widgets/NIconPicker.qml | 6 ------ Widgets/NSearchableComboBox.qml | 8 -------- 10 files changed, 1 insertion(+), 73 deletions(-) diff --git a/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml b/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml index 577c252f..e77eb945 100644 --- a/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml @@ -25,19 +25,12 @@ Popup { anchors.centerIn: parent onOpened: { - // Mark this popup has opened in the PanelService - PanelService.willOpenPopup(root) - // Load settings when popup opens with data if (widgetData && widgetId) { loadWidgetSettings() } } - onClosed: { - PanelService.willClosePopup(root) - } - background: Rectangle { id: bgRect diff --git a/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml b/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml index 805f2fc5..8ebea72e 100644 --- a/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml +++ b/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml @@ -23,16 +23,11 @@ Popup { anchors.centerIn: parent onOpened: { - PanelService.willOpenPopup(root) if (widgetData && widgetId) { loadWidgetSettings() } } - onClosed: { - PanelService.willClosePopup(root) - } - background: Rectangle { color: Color.mSurface radius: Style.radiusL diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index c8113b22..b0b287d0 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -12,7 +12,7 @@ SmartPanel { id: root preferredWidth: Math.round(820 * Style.uiScaleRatio) - preferredHeight: Math.round(900 * Style.uiScaleRatio) + preferredHeight: Math.round(910 * Style.uiScaleRatio) readonly property bool attachToBar: Settings.data.ui.settingsPanelAttachToBar readonly property string barPosition: Settings.data.bar.position diff --git a/Services/UI/PanelService.qml b/Services/UI/PanelService.qml index d22fc575..0d4896d1 100644 --- a/Services/UI/PanelService.qml +++ b/Services/UI/PanelService.qml @@ -16,12 +16,6 @@ Singleton { signal willOpen signal didClose - // Currently opened popups, can have more than one. - // ex: when opening an NIconPicker from a widget setting. - property var openedPopups: [] - property bool hasOpenedPopup: false - signal popupChanged - // Register this panel (called after panel is loaded) function registerPanel(panel) { registeredPanels[panel.objectName] = panel @@ -77,17 +71,4 @@ Singleton { // emit signal didClose() } - - // Popups - function willOpenPopup(popup) { - openedPopups.push(popup) - hasOpenedPopup = (openedPopups.length !== 0) - popupChanged() - } - - function willClosePopup(popup) { - openedPopups = openedPopups.filter(p => p !== popup) - hasOpenedPopup = (openedPopups.length !== 0) - popupChanged() - } } diff --git a/Widgets/NColorPickerDialog.qml b/Widgets/NColorPickerDialog.qml index 5bfb56db..4f142f16 100644 --- a/Widgets/NColorPickerDialog.qml +++ b/Widgets/NColorPickerDialog.qml @@ -26,14 +26,6 @@ Popup { modal: true - onOpened: { - PanelService.willOpenPopup(root) - } - - onClosed: { - PanelService.willClosePopup(root) - } - function rgbToHsv(r, g, b) { r /= 255 g /= 255 diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index b90a1d9f..b0c7faed 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -108,14 +108,6 @@ RowLayout { implicitHeight: Math.min(root.popupHeight, contentItem.implicitHeight + Style.marginM * 2) padding: Style.marginM - onOpened: { - PanelService.willOpenPopup(root) - } - - onClosed: { - PanelService.willClosePopup(root) - } - contentItem: NListView { model: combo.popup.visible ? root.model : null implicitHeight: contentHeight diff --git a/Widgets/NContextMenu.qml b/Widgets/NContextMenu.qml index 6c951116..eed0a423 100644 --- a/Widgets/NContextMenu.qml +++ b/Widgets/NContextMenu.qml @@ -17,9 +17,6 @@ Popup { width: 180 padding: Style.marginS - onOpened: PanelService.willOpenPopup(root) - onClosed: PanelService.willClosePopup(root) - background: Rectangle { color: Color.mSurfaceVariant border.color: Color.mOutline diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 936ce8ba..84eb473d 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -27,14 +27,6 @@ Popup { signal accepted(var paths) signal cancelled - onOpened: { - PanelService.willOpenPopup(root) - } - - onClosed: { - PanelService.willClosePopup(root) - } - function openFilePicker() { if (!root.currentPath) root.currentPath = root.initialPath diff --git a/Widgets/NIconPicker.qml b/Widgets/NIconPicker.qml index 996917ba..f37e01f7 100644 --- a/Widgets/NIconPicker.qml +++ b/Widgets/NIconPicker.qml @@ -4,7 +4,6 @@ import QtQuick.Layouts import QtQuick.Window import qs.Commons import qs.Widgets -import qs.Services.UI Popup { id: root @@ -36,11 +35,6 @@ Popup { selectedIcon = initialIcon query = initialIcon searchInput.forceActiveFocus() - PanelService.willOpenPopup(root) - } - - onClosed: { - PanelService.willClosePopup(root) } background: Rectangle { diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index c4a74a19..bd334124 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -158,14 +158,6 @@ RowLayout { height: root.popupHeight + 60 padding: Style.marginM - onOpened: { - PanelService.willOpenPopup(root) - } - - onClosed: { - PanelService.willClosePopup(root) - } - contentItem: ColumnLayout { spacing: Style.marginS From 8bd2f0ca7f7fce49f86f5f32ed205364c057f492 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 22:41:08 -0500 Subject: [PATCH 026/107] Lockscreen-settings: fixed missing param --- Modules/Panels/Settings/Tabs/LockScreenTab.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Panels/Settings/Tabs/LockScreenTab.qml b/Modules/Panels/Settings/Tabs/LockScreenTab.qml index f7b938c5..46b9497d 100644 --- a/Modules/Panels/Settings/Tabs/LockScreenTab.qml +++ b/Modules/Panels/Settings/Tabs/LockScreenTab.qml @@ -12,7 +12,7 @@ ColumnLayout { label: I18n.tr("settings.lock-screen.lock-on-suspend.label") description: I18n.tr("settings.lock-screen.lock-on-suspend.description") checked: Settings.data.general.lockOnSuspend - onToggled: Settings.data.general.lockOnSuspend = checked + onToggled: checked => Settings.data.general.lockOnSuspend = checked } NToggle { From 3b7411f1ad4ce5d766ff715490cd95983941b968 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 23:07:46 -0500 Subject: [PATCH 027/107] Bar-Widgets: re-adding a lot of missing services since the refactor. --- Modules/Bar/Widgets/ActiveWindow.qml | 1 + Modules/Bar/Widgets/Bluetooth.qml | 3 ++- Modules/Bar/Widgets/Brightness.qml | 1 + Modules/Bar/Widgets/KeepAwake.qml | 1 + Modules/Bar/Widgets/Microphone.qml | 1 + Modules/Bar/Widgets/NightLight.qml | 1 + Modules/Bar/Widgets/NoctaliaPerformance.qml | 1 + Modules/Bar/Widgets/PowerProfile.qml | 1 + Modules/Bar/Widgets/SessionMenu.qml | 1 + Modules/Bar/Widgets/Spacer.qml | 1 + Services/Hardware/BatteryService.qml | 2 +- Services/Keyboard/KeyboardLayoutService.qml | 2 +- Services/Media/ScreenRecorderService.qml | 1 + Services/Networking/BluetoothService.qml | 1 + Services/UI/PanelService.qml | 3 +-- 15 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index d02e92a3..bd8fc4f5 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -3,6 +3,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Widgets import qs.Commons +import qs.Services.Compositor import qs.Services.UI import qs.Widgets diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 8c3bb1d8..2ce3fea6 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -1,8 +1,9 @@ import QtQuick import Quickshell import qs.Commons -import qs.Services.UI import qs.Modules.Bar.Extras +import qs.Services.Networking +import qs.Services.UI Item { id: root diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index a94383e1..e426c4aa 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -3,6 +3,7 @@ import Quickshell import qs.Commons import qs.Modules.Bar.Extras import qs.Modules.Panels.Settings +import qs.Services.Hardware import qs.Services.UI import qs.Widgets diff --git a/Modules/Bar/Widgets/KeepAwake.qml b/Modules/Bar/Widgets/KeepAwake.qml index 58b30d6f..46f975d6 100644 --- a/Modules/Bar/Widgets/KeepAwake.qml +++ b/Modules/Bar/Widgets/KeepAwake.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons +import qs.Services.Power import qs.Services.UI import qs.Widgets diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index b3e32915..d384cfb5 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -5,6 +5,7 @@ import Quickshell.Services.Pipewire import qs.Commons import qs.Modules.Bar.Extras import qs.Modules.Panels.Settings +import qs.Services.Media import qs.Services.UI import qs.Widgets diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml index 2be0a047..c04d713d 100644 --- a/Modules/Bar/Widgets/NightLight.qml +++ b/Modules/Bar/Widgets/NightLight.qml @@ -5,6 +5,7 @@ import Quickshell import Quickshell.Wayland import qs.Commons import qs.Modules.Panels.Settings +import qs.Services.System import qs.Services.UI import qs.Widgets diff --git a/Modules/Bar/Widgets/NoctaliaPerformance.qml b/Modules/Bar/Widgets/NoctaliaPerformance.qml index 7678e92f..4263604a 100644 --- a/Modules/Bar/Widgets/NoctaliaPerformance.qml +++ b/Modules/Bar/Widgets/NoctaliaPerformance.qml @@ -6,6 +6,7 @@ import Quickshell.Wayland import qs.Commons import qs.Modules.Panels.Settings import qs.Services.Power +import qs.Services.UI import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/PowerProfile.qml b/Modules/Bar/Widgets/PowerProfile.qml index e9bca98a..724d7a4d 100644 --- a/Modules/Bar/Widgets/PowerProfile.qml +++ b/Modules/Bar/Widgets/PowerProfile.qml @@ -4,6 +4,7 @@ import Quickshell import Quickshell.Services.UPower import qs.Commons import qs.Services.Power +import qs.Services.UI import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/SessionMenu.qml b/Modules/Bar/Widgets/SessionMenu.qml index a22b046c..64192d32 100644 --- a/Modules/Bar/Widgets/SessionMenu.qml +++ b/Modules/Bar/Widgets/SessionMenu.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons +import qs.Services.UI import qs.Widgets NIconButton { diff --git a/Modules/Bar/Widgets/Spacer.qml b/Modules/Bar/Widgets/Spacer.qml index ff94682e..92bc33f3 100644 --- a/Modules/Bar/Widgets/Spacer.qml +++ b/Modules/Bar/Widgets/Spacer.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import qs.Commons +import qs.Services.UI import qs.Widgets Item { diff --git a/Services/Hardware/BatteryService.qml b/Services/Hardware/BatteryService.qml index 41d63695..a655be64 100644 --- a/Services/Hardware/BatteryService.qml +++ b/Services/Hardware/BatteryService.qml @@ -123,7 +123,7 @@ Singleton { Settings.data.battery.chargingMode = BatteryService.chargingMode } else if (exitCode === 2) { ToastService.showWarning(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.initial-setup")) - PanelService.getPanel("batteryPanel", screen)?.toggle(this) + PanelService.getPanel("batteryPanel", null)?.toggle(this) BatteryService.runInstaller() } else { ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.set-failed")) diff --git a/Services/Keyboard/KeyboardLayoutService.qml b/Services/Keyboard/KeyboardLayoutService.qml index 7876b27a..d8e1a648 100644 --- a/Services/Keyboard/KeyboardLayoutService.qml +++ b/Services/Keyboard/KeyboardLayoutService.qml @@ -3,8 +3,8 @@ pragma Singleton import QtQuick import Quickshell import Quickshell.Io -import Quickshell.Hyprland import qs.Commons +import qs.Services.Compositor Singleton { id: root diff --git a/Services/Media/ScreenRecorderService.qml b/Services/Media/ScreenRecorderService.qml index 22b8f649..7338ccbb 100644 --- a/Services/Media/ScreenRecorderService.qml +++ b/Services/Media/ScreenRecorderService.qml @@ -5,6 +5,7 @@ import Quickshell import Quickshell.Io import qs.Commons import qs.Services.System +import qs.Services.UI Singleton { id: root diff --git a/Services/Networking/BluetoothService.qml b/Services/Networking/BluetoothService.qml index b130a489..4a7f8745 100644 --- a/Services/Networking/BluetoothService.qml +++ b/Services/Networking/BluetoothService.qml @@ -5,6 +5,7 @@ import Quickshell import Quickshell.Bluetooth import Quickshell.Io import qs.Commons +import qs.Services.UI Singleton { id: root diff --git a/Services/UI/PanelService.qml b/Services/UI/PanelService.qml index 0d4896d1..79682646 100644 --- a/Services/UI/PanelService.qml +++ b/Services/UI/PanelService.qml @@ -25,8 +25,7 @@ Singleton { // Returns a panel (loads it on-demand if not yet loaded) function getPanel(name, screen) { if (!screen) { - Logger.w("PanelService", "missing screen for getPanel:", name) - Logger.callStack() + Logger.d("PanelService", "missing screen for getPanel:", name) // If no screen specified, return the first matching panel for (var key in registeredPanels) { if (key.startsWith(name + "-")) { From d6d4c65dea2863a47621bb05b44148507b8a2579 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 23:25:32 -0500 Subject: [PATCH 028/107] Project structure - Merged KeyboardLayout in KeyboardLayoutService - Renamed TablerIcons -> IconsTabler --- CLAUDE.md | 3 +- Commons/Icons.qml | 6 +- Commons/{TablerIcons.qml => IconsTabler.qml} | 0 Commons/KeyboardLayout.qml | 206 ------------------- Services/Keyboard/KeyboardLayoutService.qml | 200 +++++++++++++++++- 5 files changed, 203 insertions(+), 212 deletions(-) rename Commons/{TablerIcons.qml => IconsTabler.qml} (100%) delete mode 100644 Commons/KeyboardLayout.qml diff --git a/CLAUDE.md b/CLAUDE.md index bfa699f9..e6a59884 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -123,12 +123,11 @@ Common components used throughout the shell: - `I18n.qml` - Internationalization/translations - `Color.qml` - Color utilities and helpers - `Icons.qml` - Icon management -- `TablerIcons.qml` - Tabler icon set (207KB icon definitions) +- `IconsTabler.qml` - Tabler icon set (207KB icon definitions) - `ThemeIcons.qml` - Theme-specific icons - `Logger.qml` - Logging utility - `Style.qml` - Shared styling definitions - `Time.qml` - Time utilities -- `KeyboardLayout.qml` - Keyboard layout definitions #### `/Widgets/` - Reusable UI Components 40+ custom QML widgets with the "N" prefix (Noctalia): diff --git a/Commons/Icons.qml b/Commons/Icons.qml index 919782d6..acd7e1b7 100644 --- a/Commons/Icons.qml +++ b/Commons/Icons.qml @@ -10,9 +10,9 @@ Singleton { // Expose the font family name for easy access readonly property string fontFamily: currentFontLoader ? currentFontLoader.name : "" - readonly property string defaultIcon: TablerIcons.defaultIcon - readonly property var icons: TablerIcons.icons - readonly property var aliases: TablerIcons.aliases + readonly property string defaultIcon: IconsTabler.defaultIcon + readonly property var icons: IconsTabler.icons + readonly property var aliases: IconsTabler.aliases readonly property string fontPath: "/Assets/Fonts/tabler/tabler-icons.ttf" // Current active font loader diff --git a/Commons/TablerIcons.qml b/Commons/IconsTabler.qml similarity index 100% rename from Commons/TablerIcons.qml rename to Commons/IconsTabler.qml diff --git a/Commons/KeyboardLayout.qml b/Commons/KeyboardLayout.qml deleted file mode 100644 index 9b090d21..00000000 --- a/Commons/KeyboardLayout.qml +++ /dev/null @@ -1,206 +0,0 @@ -pragma Singleton - -import QtQuick - -// TODO: Move to KeyboardLayoutService. -QtObject { - id: root - - // Comprehensive language name to ISO code mapping - property var languageMap: { - "english"// English variants - : "us", - "american": "us", - "united states": "us", - "us english": "us", - "british": "gb", - "uk": "ua", - "united kingdom"// FIXED: Ukrainian language code should map to Ukraine - : "gb", - "english (uk)": "gb", - "canadian": "ca", - "canada": "ca", - "canadian english": "ca", - "australian": "au", - "australia": "au", - "swedish"// Nordic countries - : "se", - "svenska": "se", - "sweden": "se", - "norwegian": "no", - "norsk": "no", - "norway": "no", - "danish": "dk", - "dansk": "dk", - "denmark": "dk", - "finnish": "fi", - "suomi": "fi", - "finland": "fi", - "icelandic": "is", - "íslenska": "is", - "iceland": "is", - "german"// Western/Central European Germanic - : "de", - "deutsch": "de", - "germany": "de", - "austrian": "at", - "austria": "at", - "österreich": "at", - "swiss": "ch", - "switzerland": "ch", - "schweiz": "ch", - "suisse": "ch", - "dutch": "nl", - "nederlands": "nl", - "netherlands": "nl", - "holland": "nl", - "belgian": "be", - "belgium": "be", - "belgië": "be", - "belgique": "be", - "french"// Romance languages (Western/Southern Europe) - : "fr", - "français": "fr", - "france": "fr", - "canadian french": "ca", - "spanish": "es", - "español": "es", - "spain": "es", - "castilian": "es", - "italian": "it", - "italiano": "it", - "italy": "it", - "portuguese": "pt", - "português": "pt", - "portugal": "pt", - "catalan": "ad", - "català": "ad", - "andorra": "ad", - "romanian"// Eastern European Romance - : "ro", - "română": "ro", - "romania": "ro", - "russian"// Slavic languages (Eastern Europe) - : "ru", - "русский": "ru", - "russia": "ru", - "polish": "pl", - "polski": "pl", - "poland": "pl", - "czech": "cz", - "čeština": "cz", - "czech republic": "cz", - "slovak": "sk", - "slovenčina": "sk", - "slovakia": "sk", - "uk": "ua", - "ukrainian"// Ukrainian language code - : "ua", - "українська": "ua", - "ukraine": "ua", - "bulgarian": "bg", - "български": "bg", - "bulgaria": "bg", - "serbian": "rs", - "srpski": "rs", - "serbia": "rs", - "croatian": "hr", - "hrvatski": "hr", - "croatia": "hr", - "slovenian": "si", - "slovenščina": "si", - "slovenia": "si", - "bosnian": "ba", - "bosanski": "ba", - "bosnia": "ba", - "macedonian": "mk", - "македонски": "mk", - "macedonia": "mk", - "irish"// Celtic languages (Western Europe) - : "ie", - "gaeilge": "ie", - "ireland": "ie", - "welsh": "gb", - "cymraeg": "gb", - "wales": "gb", - "scottish": "gb", - "gàidhlig": "gb", - "scotland": "gb", - "estonian"// Baltic languages (Northern Europe) - : "ee", - "eesti": "ee", - "estonia": "ee", - "latvian": "lv", - "latviešu": "lv", - "latvia": "lv", - "lithuanian": "lt", - "lietuvių": "lt", - "lithuania": "lt", - "hungarian"// Other European languages - : "hu", - "magyar": "hu", - "hungary": "hu", - "greek": "gr", - "ελληνικά": "gr", - "greece": "gr", - "albanian": "al", - "shqip": "al", - "albania": "al", - "maltese": "mt", - "malti": "mt", - "malta": "mt", - "turkish"// West/Southwest Asian languages - : "tr", - "türkçe": "tr", - "turkey": "tr", - "arabic": "ar", - "العربية": "ar", - "arab": "ar", - "hebrew": "il", - "עברית": "il", - "israel": "il", - "brazilian"// South American languages - : "br", - "brazilian portuguese": "br", - "brasil": "br", - "brazil": "br", - "japanese"// East Asian languages - : "jp", - "日本語": "jp", - "japan": "jp", - "korean": "kr", - "한국어": "kr", - "korea": "kr", - "south korea": "kr", - "chinese": "cn", - "中文": "cn", - "china": "cn", - "simplified chinese": "cn", - "traditional chinese": "tw", - "taiwan": "tw", - "繁體中文": "tw", - "thai"// Southeast Asian languages - : "th", - "ไทย": "th", - "thailand": "th", - "vietnamese": "vn", - "tiếng việt": "vn", - "vietnam": "vn", - "hindi"// South Asian languages - : "in", - "हिन्दी": "in", - "india": "in", - "afrikaans"// African languages - : "za", - "south africa": "za", - "south african": "za", - "qwerty"// Layout variants - : "us", - "dvorak": "us", - "colemak": "us", - "workman": "us", - "azerty": "fr", - "norman": "fr", - "qwertz": "de" - } -} diff --git a/Services/Keyboard/KeyboardLayoutService.qml b/Services/Keyboard/KeyboardLayoutService.qml index d8e1a648..a28ff993 100644 --- a/Services/Keyboard/KeyboardLayoutService.qml +++ b/Services/Keyboard/KeyboardLayoutService.qml @@ -186,7 +186,7 @@ Singleton { } // Check for exact matches or partial matches in language map from Commons - const entries = Object.entries(KeyboardLayout.languageMap) + const entries = Object.entries(languageMap) for (var i = 0; i < entries.length; i++) { const lang = entries[i][0] const code = entries[i][1] @@ -229,4 +229,202 @@ Singleton { } } } + + // Comprehensive language name to ISO code mapping + property var languageMap: { + "english"// English variants + : "us", + "american": "us", + "united states": "us", + "us english": "us", + "british": "gb", + "uk": "ua", + "united kingdom"// FIXED: Ukrainian language code should map to Ukraine + : "gb", + "english (uk)": "gb", + "canadian": "ca", + "canada": "ca", + "canadian english": "ca", + "australian": "au", + "australia": "au", + "swedish"// Nordic countries + : "se", + "svenska": "se", + "sweden": "se", + "norwegian": "no", + "norsk": "no", + "norway": "no", + "danish": "dk", + "dansk": "dk", + "denmark": "dk", + "finnish": "fi", + "suomi": "fi", + "finland": "fi", + "icelandic": "is", + "íslenska": "is", + "iceland": "is", + "german"// Western/Central European Germanic + : "de", + "deutsch": "de", + "germany": "de", + "austrian": "at", + "austria": "at", + "österreich": "at", + "swiss": "ch", + "switzerland": "ch", + "schweiz": "ch", + "suisse": "ch", + "dutch": "nl", + "nederlands": "nl", + "netherlands": "nl", + "holland": "nl", + "belgian": "be", + "belgium": "be", + "belgië": "be", + "belgique": "be", + "french"// Romance languages (Western/Southern Europe) + : "fr", + "français": "fr", + "france": "fr", + "canadian french": "ca", + "spanish": "es", + "español": "es", + "spain": "es", + "castilian": "es", + "italian": "it", + "italiano": "it", + "italy": "it", + "portuguese": "pt", + "português": "pt", + "portugal": "pt", + "catalan": "ad", + "català": "ad", + "andorra": "ad", + "romanian"// Eastern European Romance + : "ro", + "română": "ro", + "romania": "ro", + "russian"// Slavic languages (Eastern Europe) + : "ru", + "русский": "ru", + "russia": "ru", + "polish": "pl", + "polski": "pl", + "poland": "pl", + "czech": "cz", + "čeština": "cz", + "czech republic": "cz", + "slovak": "sk", + "slovenčina": "sk", + "slovakia": "sk", + "uk": "ua", + "ukrainian"// Ukrainian language code + : "ua", + "українська": "ua", + "ukraine": "ua", + "bulgarian": "bg", + "български": "bg", + "bulgaria": "bg", + "serbian": "rs", + "srpski": "rs", + "serbia": "rs", + "croatian": "hr", + "hrvatski": "hr", + "croatia": "hr", + "slovenian": "si", + "slovenščina": "si", + "slovenia": "si", + "bosnian": "ba", + "bosanski": "ba", + "bosnia": "ba", + "macedonian": "mk", + "македонски": "mk", + "macedonia": "mk", + "irish"// Celtic languages (Western Europe) + : "ie", + "gaeilge": "ie", + "ireland": "ie", + "welsh": "gb", + "cymraeg": "gb", + "wales": "gb", + "scottish": "gb", + "gàidhlig": "gb", + "scotland": "gb", + "estonian"// Baltic languages (Northern Europe) + : "ee", + "eesti": "ee", + "estonia": "ee", + "latvian": "lv", + "latviešu": "lv", + "latvia": "lv", + "lithuanian": "lt", + "lietuvių": "lt", + "lithuania": "lt", + "hungarian"// Other European languages + : "hu", + "magyar": "hu", + "hungary": "hu", + "greek": "gr", + "ελληνικά": "gr", + "greece": "gr", + "albanian": "al", + "shqip": "al", + "albania": "al", + "maltese": "mt", + "malti": "mt", + "malta": "mt", + "turkish"// West/Southwest Asian languages + : "tr", + "türkçe": "tr", + "turkey": "tr", + "arabic": "ar", + "العربية": "ar", + "arab": "ar", + "hebrew": "il", + "עברית": "il", + "israel": "il", + "brazilian"// South American languages + : "br", + "brazilian portuguese": "br", + "brasil": "br", + "brazil": "br", + "japanese"// East Asian languages + : "jp", + "日本語": "jp", + "japan": "jp", + "korean": "kr", + "한국어": "kr", + "korea": "kr", + "south korea": "kr", + "chinese": "cn", + "中文": "cn", + "china": "cn", + "simplified chinese": "cn", + "traditional chinese": "tw", + "taiwan": "tw", + "繁體中文": "tw", + "thai"// Southeast Asian languages + : "th", + "ไทย": "th", + "thailand": "th", + "vietnamese": "vn", + "tiếng việt": "vn", + "vietnam": "vn", + "hindi"// South Asian languages + : "in", + "हिन्दी": "in", + "india": "in", + "afrikaans"// African languages + : "za", + "south africa": "za", + "south african": "za", + "qwerty"// Layout variants + : "us", + "dvorak": "us", + "colemak": "us", + "workman": "us", + "azerty": "fr", + "norman": "fr", + "qwertz": "de" + } } From 33ba447af1eca658e2e5531b09ab77f6aabf296c Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 23:39:44 -0500 Subject: [PATCH 029/107] WiFi: added missing service import --- Modules/Bar/Widgets/WiFi.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index 73e731a1..ad2d1ede 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell import qs.Commons +import qs.Services.Networking import qs.Services.UI import qs.Modules.Bar.Extras From 9cd25c93d30355b5016e25935fabd8e1b24f7fbd Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 23:40:03 -0500 Subject: [PATCH 030/107] Dock: GPU optimization --- Modules/Dock/Dock.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 5f8c5ae8..e04425dc 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -303,6 +303,10 @@ Loader { border.width: Style.borderS border.color: Qt.alpha(Color.mOutline, Settings.data.dock.backgroundOpacity) + // Enable layer caching to reduce GPU usage from continuous animations + // (pulse animations on active indicators run infinitely) + layer.enabled: true + MouseArea { id: dockMouseArea anchors.fill: parent From 0b8a156b3da34bc67eac69697b28dd868dd24972 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 23:50:36 -0500 Subject: [PATCH 031/107] Dock: GPU optimization save 6% GPU continuously due to pulsating dot below the active icon. --- Modules/Dock/Dock.qml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index e04425dc..fc6c1614 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -566,22 +566,6 @@ Loader { radius: Style.radiusXS anchors.top: parent.bottom anchors.horizontalCenter: parent.horizontalCenter - - // Pulse animation for active indicator - SequentialAnimation on opacity { - running: isActive - loops: Animation.Infinite - NumberAnimation { - to: 0.6 - duration: Style.animationSlowest - easing.type: Easing.InOutQuad - } - NumberAnimation { - to: 1.0 - duration: Style.animationSlowest - easing.type: Easing.InOutQuad - } - } } } } From 1f14ddfdaa2ee3e5d6f29a92d8aad0fcc0579d40 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 8 Nov 2025 23:50:49 -0500 Subject: [PATCH 032/107] CC-ScreenRec: missing import --- Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml index b2e6520b..19090b50 100644 --- a/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml +++ b/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml @@ -1,6 +1,7 @@ import QtQuick.Layouts import Quickshell import qs.Commons +import qs.Services.Media import qs.Services.UI import qs.Widgets From 13803d844086139449d620e56be31db599bc0f81 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 00:09:05 -0500 Subject: [PATCH 033/107] SetupWizard: Added missing services import --- Modules/Panels/SetupWizard/SetupAppearanceStep.qml | 2 ++ Modules/Panels/SetupWizard/SetupDockStep.qml | 1 + Modules/Panels/SetupWizard/SetupWallpaperStep.qml | 1 + 3 files changed, 4 insertions(+) diff --git a/Modules/Panels/SetupWizard/SetupAppearanceStep.qml b/Modules/Panels/SetupWizard/SetupAppearanceStep.qml index 73c8da0c..56621419 100644 --- a/Modules/Panels/SetupWizard/SetupAppearanceStep.qml +++ b/Modules/Panels/SetupWizard/SetupAppearanceStep.qml @@ -3,6 +3,8 @@ import QtQuick.Layouts import QtQuick.Controls import Quickshell.Io import qs.Commons +import qs.Services.System +import qs.Services.Theming import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/SetupWizard/SetupDockStep.qml b/Modules/Panels/SetupWizard/SetupDockStep.qml index c9446d7f..3b3e3168 100644 --- a/Modules/Panels/SetupWizard/SetupDockStep.qml +++ b/Modules/Panels/SetupWizard/SetupDockStep.qml @@ -3,6 +3,7 @@ import QtQuick.Layouts import QtQuick.Controls import Quickshell import qs.Commons +import qs.Services.Compositor import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/SetupWizard/SetupWallpaperStep.qml b/Modules/Panels/SetupWizard/SetupWallpaperStep.qml index dd08d7ec..33b1edef 100644 --- a/Modules/Panels/SetupWizard/SetupWallpaperStep.qml +++ b/Modules/Panels/SetupWizard/SetupWallpaperStep.qml @@ -5,6 +5,7 @@ import QtQuick.Effects import Quickshell import Quickshell.Io import qs.Commons +import qs.Services.UI import qs.Widgets import "../../../Helpers/FuzzySort.js" as FuzzySort From fb5988347973869806abea9d7f3131943f857e20 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 00:25:47 -0500 Subject: [PATCH 034/107] CC-ScreenRec: missing import --- Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml index 19090b50..0cbfec0d 100644 --- a/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml +++ b/Modules/Panels/ControlCenter/Widgets/ScreenRecorder.qml @@ -2,6 +2,7 @@ import QtQuick.Layouts import Quickshell import qs.Commons import qs.Services.Media +import qs.Services.System import qs.Services.UI import qs.Widgets From 871104be591f3a1efd0ea1bb45a635737b05e14a Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sun, 9 Nov 2025 09:46:45 +0100 Subject: [PATCH 035/107] SessionMenu: add LockScreen import LockScreen: add missing imports N*Spectrum: add null checks --- Modules/LockScreen/LockScreen.qml | 4 ++++ Modules/Panels/SessionMenu/SessionMenu.qml | 1 + Widgets/AudioSpectrum/NLinearSpectrum.qml | 4 ++-- Widgets/AudioSpectrum/NMirroredSpectrum.qml | 4 ++-- Widgets/AudioSpectrum/NWaveSpectrum.qml | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 84847e11..92a99941 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -9,6 +9,10 @@ import Quickshell.Services.UPower import Quickshell.Io import Quickshell.Widgets import qs.Commons +import qs.Services.Hardware +import qs.Services.Location +import qs.Services.Media +import qs.Services.UI import qs.Widgets import qs.Widgets.AudioSpectrum diff --git a/Modules/Panels/SessionMenu/SessionMenu.qml b/Modules/Panels/SessionMenu/SessionMenu.qml index a8c8f593..942ae471 100644 --- a/Modules/Panels/SessionMenu/SessionMenu.qml +++ b/Modules/Panels/SessionMenu/SessionMenu.qml @@ -8,6 +8,7 @@ import Quickshell.Widgets import Quickshell.Wayland import qs.Commons import qs.Services.Compositor +import qs.Services.UI import qs.Widgets import qs.Modules.MainScreen diff --git a/Widgets/AudioSpectrum/NLinearSpectrum.qml b/Widgets/AudioSpectrum/NLinearSpectrum.qml index a0bbd34c..97231894 100644 --- a/Widgets/AudioSpectrum/NLinearSpectrum.qml +++ b/Widgets/AudioSpectrum/NLinearSpectrum.qml @@ -14,7 +14,7 @@ Item { property real minimumSignalValue: 0.05 // Default to 5% of height // Pre compute horizontal mirroring - readonly property int valuesCount: values.length + readonly property int valuesCount: (values && Array.isArray(values)) ? values.length : 0 readonly property int totalBars: valuesCount * 2 readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0 @@ -27,7 +27,7 @@ Item { property int valueIndex: index < root.valuesCount ? root.valuesCount - 1 - index // Mirrored half : index - root.valuesCount // Normal half - property real rawAmp: root.values[valueIndex] + property real rawAmp: (root.values && root.values[valueIndex] !== undefined) ? root.values[valueIndex] : 0 property real amp: (root.showMinimumSignal && rawAmp === 0) ? root.minimumSignalValue : rawAmp color: root.fillColor diff --git a/Widgets/AudioSpectrum/NMirroredSpectrum.qml b/Widgets/AudioSpectrum/NMirroredSpectrum.qml index dd5c98ee..ae2f87db 100644 --- a/Widgets/AudioSpectrum/NMirroredSpectrum.qml +++ b/Widgets/AudioSpectrum/NMirroredSpectrum.qml @@ -14,7 +14,7 @@ Item { property real minimumSignalValue: 0.05 // Default to 5% of height // Pre-compute mirroring - readonly property int valuesCount: values.length + readonly property int valuesCount: (values && Array.isArray(values)) ? values.length : 0 readonly property int totalBars: valuesCount * 2 readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0 @@ -30,7 +30,7 @@ Item { property int valueIndex: index < root.valuesCount ? root.valuesCount - 1 - index // Mirrored half : index - root.valuesCount // Normal half - property real rawAmp: root.values[valueIndex] + property real rawAmp: (root.values && root.values[valueIndex] !== undefined) ? root.values[valueIndex] : 0 property real amp: (root.showMinimumSignal && rawAmp === 0) ? root.minimumSignalValue : rawAmp property real barSize: (vertical ? root.width : root.height) * amp diff --git a/Widgets/AudioSpectrum/NWaveSpectrum.qml b/Widgets/AudioSpectrum/NWaveSpectrum.qml index 556726b4..f4533908 100644 --- a/Widgets/AudioSpectrum/NWaveSpectrum.qml +++ b/Widgets/AudioSpectrum/NWaveSpectrum.qml @@ -32,7 +32,7 @@ Item { var ctx = getContext("2d") ctx.reset() - if (values.length === 0) { + if (!values || !Array.isArray(values) || values.length === 0) { return } From ecc76777d126c744d273cb543d37827cf17ef026 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sun, 9 Nov 2025 10:00:22 +0100 Subject: [PATCH 036/107] Template/Discord: fix link colors --- Assets/MatugenTemplates/vesktop.css | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/Assets/MatugenTemplates/vesktop.css b/Assets/MatugenTemplates/vesktop.css index 82ea111f..6de0a101 100644 --- a/Assets/MatugenTemplates/vesktop.css +++ b/Assets/MatugenTemplates/vesktop.css @@ -218,4 +218,72 @@ h3[class*="postTitleText"]:hover { [class*="tooltip"] div { color: var(--text-2) !important; opacity: 1 !important; +} + +/* Improve link readability - target Discord's link callout containers */ +[class*="linkCalloutContainer"] { + --text-muted: var(--text-3) !important; + --text-default: var(--accent-1) !important; +} + +[class*="linkCalloutContainer"], +[class*="linkCalloutContainer"] * { + opacity: 1 !important; +} + +/* Override Discord's text-muted variable for links (protocol and path parts) */ +[class*="linkCalloutContainer"] [style*="--text-muted"], +[class*="linkCalloutContainer"] [style*="color: var(--text-muted)"], +[class*="linkCalloutContainer"] [class*="normal"]:not([class*="semibold"]), +[class*="linkCalloutContainer"] [data-text-variant*="normal"]:not([data-text-variant*="semibold"]) { + color: var(--text-3) !important; + opacity: 0.85 !important; +} + +/* Make the domain (semibold part) stand out with accent color */ +[class*="linkCalloutContainer"] [class*="semibold"], +[class*="linkCalloutContainer"] [data-text-variant*="semibold"], +[class*="linkCalloutContainer"] [style*="--text-default"], +[class*="linkCalloutContainer"] [style*="color: var(--text-default)"] { + color: var(--accent-1) !important; + font-weight: 600 !important; + opacity: 1 !important; +} + +/* Hover state for links - make entire link brighter */ +[class*="linkCalloutContainer"]:hover, +[class*="linkCalloutContainer"]:hover * { + opacity: 1 !important; +} + +[class*="linkCalloutContainer"]:hover [class*="semibold"], +[class*="linkCalloutContainer"]:hover [data-text-variant*="semibold"] { + color: var(--accent-1) !important; + opacity: 1 !important; + font-weight: 600 !important; +} + +[class*="linkCalloutContainer"]:hover [class*="normal"]:not([class*="semibold"]) { + color: var(--text-2) !important; + opacity: 0.9 !important; +} + +/* General link styling for embedded links */ +a[href], +[class*="embed"] a, +[class*="embed"] [class*="link"], +[class*="embedDescription"] a { + color: var(--accent-1) !important; + opacity: 1 !important; + text-decoration: underline !important; + text-decoration-color: var(--accent-1) !important; + text-underline-offset: 2px !important; +} + +a[href]:hover, +[class*="embed"] a:hover, +[class*="embed"] [class*="link"]:hover { + color: var(--accent-1) !important; + opacity: 1 !important; + text-decoration-thickness: 2px !important; } \ No newline at end of file From 14a5bbb45dadb7d3f59e6b96081062b2cfac29b7 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sun, 9 Nov 2025 10:02:14 +0100 Subject: [PATCH 037/107] Template/Discord: actually fix link color --- Assets/MatugenTemplates/vesktop.css | 71 ++++++++--------------------- 1 file changed, 19 insertions(+), 52 deletions(-) diff --git a/Assets/MatugenTemplates/vesktop.css b/Assets/MatugenTemplates/vesktop.css index 6de0a101..7208535a 100644 --- a/Assets/MatugenTemplates/vesktop.css +++ b/Assets/MatugenTemplates/vesktop.css @@ -220,70 +220,37 @@ h3[class*="postTitleText"]:hover { opacity: 1 !important; } -/* Improve link readability - target Discord's link callout containers */ -[class*="linkCalloutContainer"] { +/* Improve link readability - ONLY target link callout containers (not channels or other UI) */ +/* Override CSS variables only within link callout containers */ +div[class*="linkCalloutContainer"] { --text-muted: var(--text-3) !important; --text-default: var(--accent-1) !important; } -[class*="linkCalloutContainer"], -[class*="linkCalloutContainer"] * { - opacity: 1 !important; -} - -/* Override Discord's text-muted variable for links (protocol and path parts) */ -[class*="linkCalloutContainer"] [style*="--text-muted"], -[class*="linkCalloutContainer"] [style*="color: var(--text-muted)"], -[class*="linkCalloutContainer"] [class*="normal"]:not([class*="semibold"]), -[class*="linkCalloutContainer"] [data-text-variant*="normal"]:not([data-text-variant*="semibold"]) { +/* Make muted parts (protocol, path) more readable - only direct child spans in link containers */ +div[class*="linkCalloutContainer"] > span[class*="normal"][data-text-variant*="normal"][style*="--text-muted"], +div[class*="linkCalloutContainer"] > span[class*="normal"][style*="color: var(--text-muted)"] { color: var(--text-3) !important; - opacity: 0.85 !important; -} - -/* Make the domain (semibold part) stand out with accent color */ -[class*="linkCalloutContainer"] [class*="semibold"], -[class*="linkCalloutContainer"] [data-text-variant*="semibold"], -[class*="linkCalloutContainer"] [style*="--text-default"], -[class*="linkCalloutContainer"] [style*="color: var(--text-default)"] { - color: var(--accent-1) !important; - font-weight: 600 !important; - opacity: 1 !important; -} - -/* Hover state for links - make entire link brighter */ -[class*="linkCalloutContainer"]:hover, -[class*="linkCalloutContainer"]:hover * { - opacity: 1 !important; -} - -[class*="linkCalloutContainer"]:hover [class*="semibold"], -[class*="linkCalloutContainer"]:hover [data-text-variant*="semibold"] { - color: var(--accent-1) !important; - opacity: 1 !important; - font-weight: 600 !important; -} - -[class*="linkCalloutContainer"]:hover [class*="normal"]:not([class*="semibold"]) { - color: var(--text-2) !important; opacity: 0.9 !important; } -/* General link styling for embedded links */ -a[href], -[class*="embed"] a, -[class*="embed"] [class*="link"], -[class*="embedDescription"] a { +/* Make the domain (semibold part) stand out with accent color - only direct child spans in link containers */ +div[class*="linkCalloutContainer"] > span[class*="semibold"], +div[class*="linkCalloutContainer"] > span[data-text-variant*="semibold"], +div[class*="linkCalloutContainer"] > span[style*="color: var(--text-default)"] { color: var(--accent-1) !important; + font-weight: 600 !important; opacity: 1 !important; - text-decoration: underline !important; - text-decoration-color: var(--accent-1) !important; - text-underline-offset: 2px !important; } -a[href]:hover, -[class*="embed"] a:hover, -[class*="embed"] [class*="link"]:hover { +/* Hover state for link containers - only affects spans inside link containers */ +div[class*="linkCalloutContainer"]:hover > span[class*="normal"][data-text-variant*="normal"] { + color: var(--text-2) !important; + opacity: 1 !important; +} + +div[class*="linkCalloutContainer"]:hover > span[class*="semibold"], +div[class*="linkCalloutContainer"]:hover > span[data-text-variant*="semibold"] { color: var(--accent-1) !important; opacity: 1 !important; - text-decoration-thickness: 2px !important; } \ No newline at end of file From fe3840b95ab5ba7e723e38d71e38c248c5eb1568 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sun, 9 Nov 2025 10:18:23 +0100 Subject: [PATCH 038/107] GeneralTab: add missing import SetupWizard: move setup steps around --- Modules/Panels/Settings/Tabs/GeneralTab.qml | 1 + Modules/Panels/SetupWizard/SetupWizard.qml | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Modules/Panels/Settings/Tabs/GeneralTab.qml b/Modules/Panels/Settings/Tabs/GeneralTab.qml index c5de3c6c..c3bdfa99 100644 --- a/Modules/Panels/Settings/Tabs/GeneralTab.qml +++ b/Modules/Panels/Settings/Tabs/GeneralTab.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts import Quickshell import qs.Commons import qs.Services.System +import qs.Services.UI import qs.Widgets ColumnLayout { diff --git a/Modules/Panels/SetupWizard/SetupWizard.qml b/Modules/Panels/SetupWizard/SetupWizard.qml index f8ee4df6..e6955fb3 100644 --- a/Modules/Panels/SetupWizard/SetupWizard.qml +++ b/Modules/Panels/SetupWizard/SetupWizard.qml @@ -182,7 +182,12 @@ SmartPanel { } } - // Step 2: UI Configuration + // Step 2: Appearance - Dark mode and color source + SetupAppearanceStep { + id: step3 + } + + // Step 3: UI Configuration SetupCustomizeStep { id: step2 selectedScaleRatio: root.selectedScaleRatio @@ -197,15 +202,10 @@ SmartPanel { } } - // Step 3: Dock Setup + // Step 4: Dock Setup SetupDockStep { id: stepDock } - - // Step 4: Appearance - Dark mode and color source - SetupAppearanceStep { - id: step3 - } } } @@ -233,15 +233,15 @@ SmartPanel { }, { "icon": "image", "label": "Wallpaper" + }, { + "icon": "palette", + "label": "Appearance" }, { "icon": "settings", "label": "Customize" }, { "icon": "device-desktop", "label": "Dock" - }, { - "icon": "palette", - "label": "Appearance" }] delegate: RowLayout { spacing: Style.marginS From ed4dd34a9c56835a080e71d98dbbcca3961655e3 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sun, 9 Nov 2025 13:04:16 +0100 Subject: [PATCH 039/107] BarTab: properly relocate SettingsWindow when floating state changes (thanks MrDowntempo <3) --- Modules/Panels/Settings/SettingsPanel.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index b0b287d0..f5c6094f 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -16,6 +16,7 @@ SmartPanel { readonly property bool attachToBar: Settings.data.ui.settingsPanelAttachToBar readonly property string barPosition: Settings.data.bar.position + readonly property bool barFloating: Settings.data.bar.floating forceAttachToBar: attachToBar panelAnchorHorizontalCenter: attachToBar ? (barPosition === "top" || barPosition === "bottom") : true @@ -37,6 +38,12 @@ SmartPanel { } } + onBarFloatingChanged: { + if (isPanelOpen && attachToBar) { + Qt.callLater(root.setPosition) + } + } + // Tabs enumeration, order is NOT relevant enum Tab { About, From 567da037704bd8903261808b45c532a1381cba2c Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 09:20:59 -0500 Subject: [PATCH 040/107] Settings: always recompute position when floating changes. --- Modules/Panels/Settings/SettingsPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index f5c6094f..e7ce327d 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -39,7 +39,7 @@ SmartPanel { } onBarFloatingChanged: { - if (isPanelOpen && attachToBar) { + if (isPanelOpen) { Qt.callLater(root.setPosition) } } From 3c07054ae81d1aec19dfa7fb973bceee042f7c9b Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 09:21:14 -0500 Subject: [PATCH 041/107] Theming: Fix a console warning due to missing var --- Services/Theming/TemplateProcessor.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Theming/TemplateProcessor.qml b/Services/Theming/TemplateProcessor.qml index 86f2c3bb..f9163704 100644 --- a/Services/Theming/TemplateProcessor.qml +++ b/Services/Theming/TemplateProcessor.qml @@ -352,7 +352,7 @@ Singleton { onStreamFinished: { if (this.text) { const description = generateProcess.showError() - Logger.e("TemplateProcessor", "Process failed with exit code", exitCode, description) + Logger.e("TemplateProcessor", "Process failed", description) } } } From 49c7c0cd7280afe5cae4555b31375679652b8ace Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 09:35:27 -0500 Subject: [PATCH 042/107] Cava-Visualizer: optimizations on idle --- Commons/Settings.qml | 2 +- Services/Media/CavaService.qml | 35 ++++++++++++++++++- Widgets/AudioSpectrum/NLinearSpectrum.qml | 4 +++ Widgets/AudioSpectrum/NMirroredSpectrum.qml | 3 ++ Widgets/AudioSpectrum/NWaveSpectrum.qml | 38 ++++++++++++++++----- 5 files changed, 71 insertions(+), 11 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 404e7897..d1a811f7 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -366,7 +366,7 @@ Singleton { property JsonObject audio: JsonObject { property int volumeStep: 5 property bool volumeOverdrive: false - property int cavaFrameRate: 60 + property int cavaFrameRate: 30 property string visualizerType: "linear" property list mprisBlacklist: [] property string preferredPlayer: "" diff --git a/Services/Media/CavaService.qml b/Services/Media/CavaService.qml index 622181c8..2619229b 100644 --- a/Services/Media/CavaService.qml +++ b/Services/Media/CavaService.qml @@ -20,6 +20,11 @@ Singleton { property var values: Array(barsCount).fill(0) property int barsCount: 48 + + // Idle detection to reduce GPU usage when there's no audio + property bool isIdle: true + property int idleFrameCount: 0 + readonly property int idleThreshold: 30 // Frames of silence before considered idle (0.5s at 60fps) property var config: ({ "general": { "bars": barsCount, @@ -74,7 +79,35 @@ Singleton { } stdout: SplitParser { onRead: data => { - root.values = data.slice(0, -1).split(";").map(v => parseInt(v, 10) / 100) + const newValues = data.slice(0, -1).split(";").map(v => parseInt(v, 10) / 100) + + // Check if all values are effectively zero (< 0.01) + const allZero = newValues.every(v => v < 0.01) + + if (allZero) { + root.idleFrameCount++ + if (root.idleFrameCount >= root.idleThreshold) { + // We're idle - stop updating values to save GPU + if (!root.isIdle) { + root.isIdle = true + // Set all values to 0 one final time + root.values = Array(root.barsCount).fill(0) + Logger.d("Cava", "Idle detected - stopped rendering") + } + // Don't update values while idle + return + } + } else { + // Audio detected - resume updates + root.idleFrameCount = 0 + if (root.isIdle) { + root.isIdle = false + Logger.d("Cava", "Audio detected - resumed rendering") + } + } + + // Update values (only when not idle) + root.values = newValues } } stderr: StdioCollector { diff --git a/Widgets/AudioSpectrum/NLinearSpectrum.qml b/Widgets/AudioSpectrum/NLinearSpectrum.qml index 97231894..0224fd9e 100644 --- a/Widgets/AudioSpectrum/NLinearSpectrum.qml +++ b/Widgets/AudioSpectrum/NLinearSpectrum.qml @@ -35,10 +35,14 @@ Item { border.width: root.strokeWidth antialiasing: true + // Only update when value actually changes - reduces GPU load width: vertical ? root.width * amp : root.barSlotSize * 0.5 height: vertical ? root.barSlotSize * 0.5 : root.height * amp x: vertical ? root.width - width : index * root.barSlotSize + (root.barSlotSize * 0.25) y: vertical ? index * root.barSlotSize + (root.barSlotSize * 0.25) : root.height - height + + // Disable updates when invisible to save GPU + visible: root.visible } } } diff --git a/Widgets/AudioSpectrum/NMirroredSpectrum.qml b/Widgets/AudioSpectrum/NMirroredSpectrum.qml index ae2f87db..2d6b05ee 100644 --- a/Widgets/AudioSpectrum/NMirroredSpectrum.qml +++ b/Widgets/AudioSpectrum/NMirroredSpectrum.qml @@ -44,6 +44,9 @@ Item { height: vertical ? root.barSlotSize * 0.8 : barSize x: vertical ? root.centerX - (barSize / 2) : index * root.barSlotSize + (root.barSlotSize * 0.25) y: vertical ? index * root.barSlotSize + (root.barSlotSize * 0.25) : root.centerY - (barSize / 2) + + // Disable updates when invisible to save GPU + visible: root.visible } } } diff --git a/Widgets/AudioSpectrum/NWaveSpectrum.qml b/Widgets/AudioSpectrum/NWaveSpectrum.qml index f4533908..9211fdd1 100644 --- a/Widgets/AudioSpectrum/NWaveSpectrum.qml +++ b/Widgets/AudioSpectrum/NWaveSpectrum.qml @@ -13,15 +13,35 @@ Item { property bool showMinimumSignal: false property real minimumSignalValue: 0.05 // Default to 5% of height - // Redraw when necessary - onWidthChanged: canvas.requestPaint() - onHeightChanged: canvas.requestPaint() - onValuesChanged: canvas.requestPaint() - onFillColorChanged: canvas.requestPaint() - onStrokeColorChanged: canvas.requestPaint() - onShowMinimumSignalChanged: canvas.requestPaint() - onMinimumSignalValueChanged: canvas.requestPaint() - onVerticalChanged: canvas.requestPaint() + // Rendering active state - only redraw when visible and values are changing + property bool renderingActive: visible && values && values.length > 0 + + // Redraw when necessary - only if rendering is active + onWidthChanged: if (renderingActive) + canvas.requestPaint() + onHeightChanged: if (renderingActive) + canvas.requestPaint() + onValuesChanged: if (renderingActive) + canvas.requestPaint() + onFillColorChanged: if (renderingActive) + canvas.requestPaint() + onStrokeColorChanged: if (renderingActive) + canvas.requestPaint() + onShowMinimumSignalChanged: if (renderingActive) + canvas.requestPaint() + onMinimumSignalValueChanged: if (renderingActive) + canvas.requestPaint() + onVerticalChanged: if (renderingActive) + canvas.requestPaint() + + // Clear canvas when not rendering + onRenderingActiveChanged: { + if (!renderingActive) { + var ctx = canvas.getContext("2d") + ctx.reset() + canvas.requestPaint() + } + } Canvas { id: canvas From 2cea2d12de9c66fbebacf211cd1a8ef53f985fc4 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 10:06:50 -0500 Subject: [PATCH 043/107] AudioVisualizer: Low vs High Quality via setting. --- Assets/Translations/de.json | 9 +++++ Assets/Translations/en.json | 16 ++++++--- Assets/Translations/es.json | 9 +++++ Assets/Translations/fr.json | 9 +++++ Assets/Translations/pt.json | 9 +++++ Assets/Translations/tr.json | 9 +++++ Assets/Translations/uk-UA.json | 9 +++++ Assets/Translations/zh-CN.json | 9 +++++ Assets/settings-default.json | 1 + Commons/Settings.qml | 1 + Modules/Panels/Settings/Tabs/AudioTab.qml | 15 +++++++- Services/Media/CavaService.qml | 40 ++++++++++++++++++--- Widgets/AudioSpectrum/NLinearSpectrum.qml | 4 ++- Widgets/AudioSpectrum/NMirroredSpectrum.qml | 5 +-- Widgets/AudioSpectrum/NWaveSpectrum.qml | 4 ++- 15 files changed, 135 insertions(+), 14 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f17e667c..72d4cc9b 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -503,6 +503,10 @@ "top_left": "Oben links", "top_right": "Oben rechts" }, + "visualizer-quality": { + "high": "Hoch", + "low": "Niedrig" + }, "visualizer-types": { "linear": "Linear", "mirrored": "Gespiegelt", @@ -693,6 +697,10 @@ "description": "Bevorzugte und ignorierte Medienanwendungen festlegen.", "label": "Medienplayer" }, + "visualizer-quality": { + "description": "Hohe Qualität verbraucht mehr GPU-Ressourcen.", + "label": "Visualisierungsqualität" + }, "visualizer-type": { "description": "Visualisierungstyp für Medienwiedergabe wählen.", "label": "Visualisierungstyp" @@ -1759,6 +1767,7 @@ "wallpaper-colors": { "disabled": "Hintergrundbild-Farben deaktiviert", "enabled": "Hintergrundbild-Farben aktiviert", + "label": "Hintergrundfarbengenerierung", "not-installed": "Matugen nicht installiert - erforderlich für Hintergrundbild-Farbextraktion" }, "wifi": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 5a00ad0c..bd3e9820 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -503,6 +503,10 @@ "top_left": "Top left", "top_right": "Top right" }, + "visualizer-quality": { + "high": "High", + "low": "Low" + }, "visualizer-types": { "linear": "Linear", "mirrored": "Mirrored", @@ -682,19 +686,23 @@ "placeholder": "e.g. spotify, vlc, mpv" }, "scrolling-speed": { - "description": "Time in seconds for the title to scroll from start to end", + "description": "Time in seconds for the title to scroll from start to end.", "label": "Scrolling speed" }, "scrolling-title": { - "description": "Enable continuous scrolling for long media titles", + "description": "Enable continuous scrolling for long media titles.", "label": "Scrolling title" }, "section": { "description": "Set your preferred and ignored media applications.", "label": "Media players" }, + "visualizer-quality": { + "description": "High quality use more GPU ressources.", + "label": "Visualization quality" + }, "visualizer-type": { - "description": "Choose a visualization type for media playback", + "description": "Choose a visualization type for media playback.", "label": "Visualization type" } }, @@ -1757,9 +1765,9 @@ "title-predefined": "Predefined color cheme processing failed" }, "wallpaper-colors": { - "label": "Wallpaper colors generation", "disabled": "Wallpaper colors disabled", "enabled": "Wallpaper colors enabled", + "label": "Wallpaper colors generation", "not-installed": "Matugen not installed - required for wallpaper color extraction" }, "wifi": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 91ffaa57..a68676bb 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -503,6 +503,10 @@ "top_left": "Superior izquierda", "top_right": "Superior derecha" }, + "visualizer-quality": { + "high": "Alto/Alta", + "low": "Bajo" + }, "visualizer-types": { "linear": "Lineal", "mirrored": "Espejado", @@ -693,6 +697,10 @@ "description": "Establece tus aplicaciones multimedia preferidas e ignoradas.", "label": "Reproductores multimedia" }, + "visualizer-quality": { + "description": "Alta calidad usa más recursos de la GPU.", + "label": "Calidad de visualización" + }, "visualizer-type": { "description": "Elige un tipo de visualización para la reproducción de medios.", "label": "Tipo de visualización" @@ -1759,6 +1767,7 @@ "wallpaper-colors": { "disabled": "Colores del fondo de pantalla desactivados", "enabled": "Colores del fondo de pantalla activados", + "label": "Generación de colores para fondos de pantalla", "not-installed": "Matugen no instalado - requerido para extraer colores del fondo de pantalla" }, "wifi": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 0ce5ef9d..c4ca352f 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -503,6 +503,10 @@ "top_left": "En haut à gauche", "top_right": "En haut à droite" }, + "visualizer-quality": { + "high": "Haut/Haute", + "low": "Bas" + }, "visualizer-types": { "linear": "Linéaire", "mirrored": "Miroir", @@ -693,6 +697,10 @@ "description": "Définissez vos applications multimédias préférées et ignorées.", "label": "Lecteurs multimédias" }, + "visualizer-quality": { + "description": "L'utilisation de haute qualité nécessite plus de ressources GPU.", + "label": "Qualité de la visualisation" + }, "visualizer-type": { "description": "Choisissez un type de visualisation pour la lecture multimédia.", "label": "Type de visualisation" @@ -1759,6 +1767,7 @@ "wallpaper-colors": { "disabled": "Couleurs du fond d'écran désactivées", "enabled": "Couleurs du fond d'écran activées", + "label": "Génération de couleurs pour fonds d'écran", "not-installed": "Matugen non installé - requis pour l'extraction des couleurs du fond d'écran" }, "wifi": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index c3baf25e..46d05463 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -503,6 +503,10 @@ "top_left": "Superior esquerda", "top_right": "Superior direita" }, + "visualizer-quality": { + "high": "Alto(a)", + "low": "Baixo(a)" + }, "visualizer-types": { "linear": "Linear", "mirrored": "Espelhado", @@ -693,6 +697,10 @@ "description": "Defina seus aplicativos de mídia preferidos e ignorados.", "label": "Reprodutores de mídia" }, + "visualizer-quality": { + "description": "Alta qualidade usa mais recursos da GPU.", + "label": "Qualidade da visualização" + }, "visualizer-type": { "description": "Escolha um tipo de visualização para a reprodução de mídia", "label": "Tipo de visualização" @@ -1759,6 +1767,7 @@ "wallpaper-colors": { "disabled": "Cores do papel de parede desativadas", "enabled": "Cores do papel de parede ativadas", + "label": "Geração de cores para papel de parede", "not-installed": "Matugen não instalado - necessário para extração de cores do papel de parede" }, "wifi": { diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 43e038ce..5aa2474f 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -503,6 +503,10 @@ "top_left": "Sol üst", "top_right": "Sağ üst" }, + "visualizer-quality": { + "high": "Yüksek", + "low": "Düşük" + }, "visualizer-types": { "linear": "Doğrusal", "mirrored": "Yansıtılmış", @@ -693,6 +697,10 @@ "description": "Tercih ettiğiniz ve yoksayacağınız medya uygulamalarını ayarlayın.", "label": "Medya oynatıcılar" }, + "visualizer-quality": { + "description": "Yüksek kalite daha fazla GPU kaynağı kullanır.", + "label": "Görselleştirme kalitesi" + }, "visualizer-type": { "description": "Medya oynatımı için bir görselleştirme türü seçin", "label": "Görselleştirme türü" @@ -1759,6 +1767,7 @@ "wallpaper-colors": { "disabled": "Duvar kağıdı renkleri devre dışı", "enabled": "Duvar kağıdı renkleri etkin", + "label": "Duvar kağıdı renkleri oluşturma", "not-installed": "Matugen kurulu değil - duvar kağıdı renk çıkarmak için gerekli" }, "wifi": { diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index 3969ea44..fbcead9d 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -503,6 +503,10 @@ "top_left": "Згори ліворуч", "top_right": "Згори праворуч" }, + "visualizer-quality": { + "high": "Високий", + "low": "Низький" + }, "visualizer-types": { "linear": "Лінійний", "mirrored": "Дзеркальний", @@ -693,6 +697,10 @@ "description": "Встановіть бажані та ігноровані медіапрограми.", "label": "Медіаплеєри" }, + "visualizer-quality": { + "description": "Висока якість використовує більше ресурсів GPU.", + "label": "Якість візуалізації" + }, "visualizer-type": { "description": "Виберіть тип візуалізації для відтворення медіа", "label": "Тип візуалізації" @@ -1759,6 +1767,7 @@ "wallpaper-colors": { "disabled": "Кольори шпалер вимкнено", "enabled": "Кольори шпалер увімкнено", + "label": "Генерація кольорів шпалер", "not-installed": "Matugen не встановлено - потрібний для витягу кольорів зі шпалер" }, "wifi": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 371faa55..924dc1c9 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -503,6 +503,10 @@ "top_left": "左上", "top_right": "右上" }, + "visualizer-quality": { + "high": "高", + "low": "低" + }, "visualizer-types": { "linear": "线性", "mirrored": "镜像", @@ -693,6 +697,10 @@ "description": "设置您首选和要忽略的媒体应用程序。", "label": "媒体播放器" }, + "visualizer-quality": { + "description": "高质量使用更多 GPU 资源。", + "label": "可视化质量" + }, "visualizer-type": { "description": "选择媒体播放的可视化类型", "label": "可视化类型" @@ -1759,6 +1767,7 @@ "wallpaper-colors": { "disabled": "壁纸颜色已禁用", "enabled": "壁纸颜色已启用", + "label": "壁纸颜色生成", "not-installed": "Matugen 未安装 - 壁纸颜色提取所需" }, "wifi": { diff --git a/Assets/settings-default.json b/Assets/settings-default.json index ed319e04..70f52191 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -228,6 +228,7 @@ "volumeOverdrive": false, "cavaFrameRate": 60, "visualizerType": "linear", + "visualizerQuality": "high", "mprisBlacklist": [], "preferredPlayer": "" }, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index d1a811f7..3d7be7f1 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -368,6 +368,7 @@ Singleton { property bool volumeOverdrive: false property int cavaFrameRate: 30 property string visualizerType: "linear" + property string visualizerQuality: "high" property list mprisBlacklist: [] property string preferredPlayer: "" } diff --git a/Modules/Panels/Settings/Tabs/AudioTab.qml b/Modules/Panels/Settings/Tabs/AudioTab.qml index 96fa6dc5..d47fe949 100644 --- a/Modules/Panels/Settings/Tabs/AudioTab.qml +++ b/Modules/Panels/Settings/Tabs/AudioTab.qml @@ -331,7 +331,6 @@ ColumnLayout { } // AudioService Visualizer section NComboBox { - id: audioVisualizerCombo label: I18n.tr("settings.audio.media.visualizer-type.label") description: I18n.tr("settings.audio.media.visualizer-type.description") model: [{ @@ -351,6 +350,20 @@ ColumnLayout { onSelected: key => Settings.data.audio.visualizerType = key } + NComboBox { + label: I18n.tr("settings.audio.media.visualizer-quality.label") + description: I18n.tr("settings.audio.media.visualizer-quality.description") + model: [{ + "key": "low", + "name": I18n.tr("options.visualizer-quality.low") + }, { + "key": "high", + "name": I18n.tr("options.visualizer-quality.high") + }] + currentKey: Settings.data.audio.visualizerQuality + onSelected: key => Settings.data.audio.visualizerQuality = key + } + NComboBox { label: I18n.tr("settings.audio.media.frame-rate.label") description: I18n.tr("settings.audio.media.frame-rate.description") diff --git a/Services/Media/CavaService.qml b/Services/Media/CavaService.qml index 2619229b..8e58c92e 100644 --- a/Services/Media/CavaService.qml +++ b/Services/Media/CavaService.qml @@ -19,12 +19,20 @@ Singleton { property bool shouldRun: BarService.hasAudioVisualizer || PanelService.lockScreen?.active || (PanelService.openedPanel && PanelService.openedPanel.objectName.startsWith("controlCenterPanel")) property var values: Array(barsCount).fill(0) - property int barsCount: 48 + property int barsCount: 32 // Idle detection to reduce GPU usage when there's no audio property bool isIdle: true property int idleFrameCount: 0 readonly property int idleThreshold: 30 // Frames of silence before considered idle (0.5s at 60fps) + + // Frame skipping for GPU optimization - skip every Nth frame when playing + property int frameCounter: 0 + readonly property int frameSkip: 1 // Update every N frames (1 = no skip, 2 = every other frame, etc) + + // Change detection - only update if values changed significantly + property var lastValues: Array(barsCount).fill(0) + readonly property real changeThreshold: 0.005 // Minimum change (1%) to trigger update property var config: ({ "general": { "bars": barsCount, @@ -35,8 +43,9 @@ Singleton { "higher_cutoff_freq": 12000 }, "smoothing": { - "monstercat": 0, - "noise_reduction": 77 + "monstercat": 1, + "noise_reduction"// Enable monstercat smoothing for less jittery animation + : 77 }, "output": { "method": "raw", @@ -92,6 +101,7 @@ Singleton { root.isIdle = true // Set all values to 0 one final time root.values = Array(root.barsCount).fill(0) + root.lastValues = Array(root.barsCount).fill(0) Logger.d("Cava", "Idle detected - stopped rendering") } // Don't update values while idle @@ -106,8 +116,28 @@ Singleton { } } - // Update values (only when not idle) - root.values = newValues + // Frame skipping optimization - skip frames to reduce GPU load + root.frameCounter++ + if (root.frameSkip > 1 && root.frameCounter % root.frameSkip !== 0) { + // Skip this frame + return + } + + // Change detection - only update if values changed significantly + let hasSignificantChange = false + for (var i = 0; i < newValues.length; i++) { + const delta = Math.abs(newValues[i] - root.lastValues[i]) + if (delta > root.changeThreshold) { + hasSignificantChange = true + break + } + } + + // Update values only if there's a significant change + if (hasSignificantChange) { + root.lastValues = newValues.slice() // Copy array + root.values = newValues + } } } stderr: StdioCollector { diff --git a/Widgets/AudioSpectrum/NLinearSpectrum.qml b/Widgets/AudioSpectrum/NLinearSpectrum.qml index 0224fd9e..596f7d10 100644 --- a/Widgets/AudioSpectrum/NLinearSpectrum.qml +++ b/Widgets/AudioSpectrum/NLinearSpectrum.qml @@ -17,6 +17,7 @@ Item { readonly property int valuesCount: (values && Array.isArray(values)) ? values.length : 0 readonly property int totalBars: valuesCount * 2 readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0 + readonly property bool highQuality: (Settings.data.audio.visualizerType === "low") ? false : true Repeater { model: root.totalBars @@ -33,7 +34,8 @@ Item { color: root.fillColor border.color: root.strokeColor border.width: root.strokeWidth - antialiasing: true + antialiasing: root.highQuality + smooth: root.highQuality // Only update when value actually changes - reduces GPU load width: vertical ? root.width * amp : root.barSlotSize * 0.5 diff --git a/Widgets/AudioSpectrum/NMirroredSpectrum.qml b/Widgets/AudioSpectrum/NMirroredSpectrum.qml index 2d6b05ee..4da22d6d 100644 --- a/Widgets/AudioSpectrum/NMirroredSpectrum.qml +++ b/Widgets/AudioSpectrum/NMirroredSpectrum.qml @@ -17,7 +17,7 @@ Item { readonly property int valuesCount: (values && Array.isArray(values)) ? values.length : 0 readonly property int totalBars: valuesCount * 2 readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0 - + readonly property bool highQuality: (Settings.data.audio.visualizerType === "low") ? false : true readonly property real centerY: height / 2 readonly property real centerX: width / 2 @@ -38,7 +38,8 @@ Item { color: root.fillColor border.color: root.strokeColor border.width: root.strokeWidth - antialiasing: true + antialiasing: root.highQuality + smooth: root.highQuality width: vertical ? barSize : root.barSlotSize * 0.8 height: vertical ? root.barSlotSize * 0.8 : barSize diff --git a/Widgets/AudioSpectrum/NWaveSpectrum.qml b/Widgets/AudioSpectrum/NWaveSpectrum.qml index 9211fdd1..e2a3cfc7 100644 --- a/Widgets/AudioSpectrum/NWaveSpectrum.qml +++ b/Widgets/AudioSpectrum/NWaveSpectrum.qml @@ -46,7 +46,9 @@ Item { Canvas { id: canvas anchors.fill: parent - antialiasing: true + antialiasing: false // Disable for better performance - shape is smooth enough without it + renderStrategy: Canvas.Threaded // Render in separate thread to reduce main thread load + renderTarget: Canvas.FramebufferObject // Use FBO for better performance onPaint: { var ctx = getContext("2d") From d9ca5cdd3ad2f93169bad19f315d0b67561e3b79 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 11:09:16 -0500 Subject: [PATCH 044/107] NWaveSpectrum: avoid error if context not ready yet --- Widgets/AudioSpectrum/NWaveSpectrum.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Widgets/AudioSpectrum/NWaveSpectrum.qml b/Widgets/AudioSpectrum/NWaveSpectrum.qml index e2a3cfc7..e2a78fe9 100644 --- a/Widgets/AudioSpectrum/NWaveSpectrum.qml +++ b/Widgets/AudioSpectrum/NWaveSpectrum.qml @@ -38,7 +38,8 @@ Item { onRenderingActiveChanged: { if (!renderingActive) { var ctx = canvas.getContext("2d") - ctx.reset() + if (ctx) + ctx.reset() canvas.requestPaint() } } From 15539e1445f66934dcbdb099bbdcd7724ef81664 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 11:25:06 -0500 Subject: [PATCH 045/107] MainScreen: Bar content is in its own PanelWindow --- Assets/settings-default.json | 2 +- Modules/Bar/Bar.qml | 12 +- Modules/MainScreen/AllScreens.qml | 19 +++ .../MainScreen/Backgrounds/AllBackgrounds.qml | 1 + .../MainScreen/Backgrounds/BarBackground.qml | 21 ++- Modules/MainScreen/BarContentWindow.qml | 72 ++++++++++ Modules/MainScreen/MainScreen.qml | 125 ++++++++++++++---- shell.qml | 3 +- 8 files changed, 221 insertions(+), 34 deletions(-) create mode 100644 Modules/MainScreen/BarContentWindow.qml diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 70f52191..add3d975 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -226,7 +226,7 @@ "audio": { "volumeStep": 5, "volumeOverdrive": false, - "cavaFrameRate": 60, + "cavaFrameRate": 30, "visualizerType": "linear", "visualizerQuality": "high", "mprisBlacklist": [], diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 2d472595..f5cc180e 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -30,9 +30,6 @@ Item { readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 - // Attachment overlap to fix hairline gap with fractional scaling - readonly property real attachmentOverlap: 1 - // Fill the parent (the Loader) anchors.fill: parent @@ -68,29 +65,28 @@ Item { id: bar // Position and size the bar based on orientation and floating margins - // Extend the bar by attachmentOverlap to eliminate hairline gap x: { var baseX = (root.barPosition === "right") ? (parent.width - Style.barHeight - root.barMarginH) : root.barMarginH if (root.barPosition === "right") - return baseX - root.attachmentOverlap // Extend left towards panels + return baseX // Extend left towards panels return baseX } y: { var baseY = (root.barPosition === "bottom") ? (parent.height - Style.barHeight - root.barMarginV) : root.barMarginV if (root.barPosition === "bottom") - return baseY - root.attachmentOverlap // Extend up towards panels + return baseY // Extend up towards panels return baseY } width: { var baseWidth = root.barIsVertical ? Style.barHeight : (parent.width - root.barMarginH * 2) if (!root.barIsVertical) return baseWidth // Horizontal bars extend via height, not width - return baseWidth + root.attachmentOverlap + 1 + return baseWidth + 1 } height: { var baseHeight = root.barIsVertical ? (parent.height - root.barMarginV * 2) : Style.barHeight if (!root.barIsVertical) - return baseHeight + root.attachmentOverlap + return baseHeight return baseHeight // Vertical bars extend via width, not height } diff --git a/Modules/MainScreen/AllScreens.qml b/Modules/MainScreen/AllScreens.qml index 9758790d..7ae5a29c 100644 --- a/Modules/MainScreen/AllScreens.qml +++ b/Modules/MainScreen/AllScreens.qml @@ -41,6 +41,25 @@ Variants { } } + // Bar content in separate windows to prevent fullscreen redraws + Loader { + active: { + if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible) + return false + + // Check if bar is configured for this screen + var monitors = Settings.data.bar.monitors || [] + return monitors.length === 0 || monitors.includes(modelData?.name) + } + asynchronous: false + + sourceComponent: BarContentWindow {} + + onLoaded: { + Logger.d("Shell", "BarContentWindow created for", modelData?.name) + } + } + // BarExclusionZone - created after MainScreen has fully loaded // Disabled when bar is hidden or not configured for this screen Loader { diff --git a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml index 7d953f3d..e566aedb 100644 --- a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml +++ b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml @@ -56,6 +56,7 @@ Item { BarBackground { bar: root.bar shapeContainer: backgroundsShape + windowRoot: root.windowRoot } diff --git a/Modules/MainScreen/Backgrounds/BarBackground.qml b/Modules/MainScreen/Backgrounds/BarBackground.qml index 9399ab12..e91ca8cc 100644 --- a/Modules/MainScreen/Backgrounds/BarBackground.qml +++ b/Modules/MainScreen/Backgrounds/BarBackground.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Shapes import qs.Commons +import qs.Services.UI import qs.Modules.MainScreen.Backgrounds @@ -25,6 +26,24 @@ ShapePath { // Required reference to AllBackgrounds shapeContainer required property var shapeContainer + // Required reference to windowRoot for screen access + required property var windowRoot + + // Check if bar should be visible on this screen + readonly property bool shouldShowBar: { + // Check global bar visibility + if (!BarService.isVisible) + return false + + // Check screen-specific configuration + var monitors = Settings.data.bar.monitors || [] + var screenName = windowRoot?.screen?.name || "" + + // If no monitors specified, show on all screens + // If monitors specified, only show if this screen is in the list + return monitors.length === 0 || monitors.includes(screenName) + } + // Corner radius (from Style) readonly property real radius: Style.radiusL @@ -64,7 +83,7 @@ ShapePath { // ShapePath configuration strokeWidth: -1 // No stroke, fill only - fillColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) + fillColor: shouldShowBar ? Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) : Color.transparent // Starting position (top-left corner, after the arc) // Use mapped coordinates relative to the Shape container diff --git a/Modules/MainScreen/BarContentWindow.qml b/Modules/MainScreen/BarContentWindow.qml new file mode 100644 index 00000000..9637a410 --- /dev/null +++ b/Modules/MainScreen/BarContentWindow.qml @@ -0,0 +1,72 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services.UI +import qs.Modules.Bar + + +/** + * BarContentWindow - Separate transparent PanelWindow for bar content + * + * This window contains only the bar widgets (content), while the background + * is rendered in MainScreen's unified Shape system. This separation prevents + * fullscreen redraws when bar widgets redraw. + */ +Variants { + model: Quickshell.screens + + delegate: Loader { + id: barWindowLoader + + required property ShellScreen modelData + + // Only create window if bar should be visible on this screen + active: { + if (!modelData || !modelData.name) + return false + var monitors = Settings.data.bar.monitors || [] + return BarService.isVisible && (monitors.length === 0 || monitors.includes(modelData.name)) + } + + sourceComponent: PanelWindow { + id: barWindow + screen: modelData + + color: Color.transparent // Transparent - background is in MainScreen below + + Component.onCompleted: { + Logger.d("BarContentWindow", "Bar content window created for screen:", screen?.name) + } + + // Wayland layer configuration + WlrLayershell.namespace: "noctalia-bar-content-" + (screen?.name || "unknown") + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone in MainScreen handles that + + // Position and size to match bar location + readonly property string barPosition: Settings.data.bar.position || "top" + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property bool barFloating: Settings.data.bar.floating || false + readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 + readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 + + // Anchor to the bar's edge + anchors { + top: barPosition === "top" || barIsVertical + bottom: barPosition === "bottom" || barIsVertical + left: barPosition === "left" || !barIsVertical + right: barPosition === "right" || !barIsVertical + } + // Set to FULL screen dimensions - margins will reduce the actual window size + implicitWidth: (barIsVertical ? (Style.barHeight + 1) : screen.width) + barMarginH + implicitHeight: (barIsVertical ? screen.height : Style.barHeight) + barMarginV + + // Bar content - just the widgets, no background + Bar { + anchors.fill: parent + screen: modelData + } + } + } +} diff --git a/Modules/MainScreen/MainScreen.qml b/Modules/MainScreen/MainScreen.qml index 1d6fa665..c01b7553 100644 --- a/Modules/MainScreen/MainScreen.qml +++ b/Modules/MainScreen/MainScreen.qml @@ -101,12 +101,11 @@ PanelWindow { // Bar region - subtract bar area from mask Region { id: barMaskRegion - property var barRegion: barLoader.item?.barRegion - x: barRegion?.x ?? 0 - y: barRegion?.y ?? 0 - width: barRegion?.width ?? 0 - height: barRegion?.height ?? 0 + x: barPlaceholder.x + y: barPlaceholder.y + width: barPlaceholder.width + height: barPlaceholder.height intersection: Intersection.Subtract } @@ -133,7 +132,7 @@ PanelWindow { Backgrounds.AllBackgrounds { id: unifiedBackgrounds anchors.fill: parent - bar: barLoader.item?.barItem || null + bar: barPlaceholder.barItem || null windowRoot: root z: 0 // Behind all content } @@ -309,28 +308,108 @@ PanelWindow { } } - // Bar (always on top - rendered last in tree, so naturally on top) - Loader { - id: barLoader - asynchronous: false - sourceComponent: Bar {} - // Keep bar loaded but hide it when BarService.isVisible is false - // This allows panels to remain accessible via IPC - visible: BarService.isVisible + // Bar placeholder - just for background positioning (actual bar content is in BarContentWindow) + Item { + id: barPlaceholder - // Fill parent to provide dimensions for Bar to reference - anchors.fill: parent + // Expose self as barItem for AllBackgrounds compatibility + readonly property var barItem: barPlaceholder + // Screen reference property ShellScreen screen: root.screen - onLoaded: { - if (item) { - Logger.d("MainScreen", "Bar loaded with screen", item.screen?.name) - // Bind screen to bar component (use binding for reactivity) - item.screen = Qt.binding(function () { - return barLoader.screen - }) + // Bar positioning properties (match Bar.qml logic) + readonly property string barPosition: Settings.data.bar.position || "top" + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property bool barFloating: Settings.data.bar.floating || false + readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 + readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 + + // Expose bar dimensions directly on this Item for BarBackground + // Use screen dimensions directly + x: { + if (barPosition === "right") + return screen.width - Style.barHeight - barMarginH + return barMarginH + } + y: { + if (barPosition === "bottom") + return screen.height - Style.barHeight - barMarginV + return barMarginV + } + width: { + if (barIsVertical) { + return Style.barHeight + 1 } + return screen.width - barMarginH * 2 + } + height: { + if (barIsVertical) { + return screen.height - barMarginV * 2 + } + return Style.barHeight + } + + // Corner states (same as Bar.qml) + readonly property int topLeftCornerState: { + if (barFloating) + return 0 + if (barPosition === "top") + return -1 + if (barPosition === "left") + return -1 + if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) { + return barIsVertical ? 1 : 2 + } + return -1 + } + + readonly property int topRightCornerState: { + if (barFloating) + return 0 + if (barPosition === "top") + return -1 + if (barPosition === "right") + return -1 + if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) { + return barIsVertical ? 1 : 2 + } + return -1 + } + + readonly property int bottomLeftCornerState: { + if (barFloating) + return 0 + if (barPosition === "bottom") + return -1 + if (barPosition === "left") + return -1 + if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) { + return barIsVertical ? 1 : 2 + } + return -1 + } + + readonly property int bottomRightCornerState: { + if (barFloating) + return 0 + if (barPosition === "bottom") + return -1 + if (barPosition === "right") + return -1 + if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) { + return barIsVertical ? 1 : 2 + } + return -1 + } + + Component.onCompleted: { + Logger.d("MainScreen", "===== Bar placeholder loaded =====") + Logger.d("MainScreen", " Screen:", screen?.name, "Size:", screen?.width, "x", screen?.height) + Logger.d("MainScreen", " Bar position:", barPosition, "| isVertical:", barIsVertical) + Logger.d("MainScreen", " Bar dimensions: x=" + x, "y=" + y, "width=" + width, "height=" + height) + Logger.d("MainScreen", " Style.barHeight =", Style.barHeight) + Logger.d("MainScreen", " Margins: H=" + barMarginH, "V=" + barMarginV, "| Floating:", barFloating) } } diff --git a/shell.qml b/shell.qml index f21e6d58..666d9884 100644 --- a/shell.qml +++ b/shell.qml @@ -23,6 +23,7 @@ import qs.Services.UI // Modules import qs.Modules.Background +import qs.Modules.Bar import qs.Modules.Dock import qs.Modules.MainScreen import qs.Modules.LockScreen @@ -106,7 +107,7 @@ ShellRoot { // Item that needs to exists in the shell. IPCService {} - // MainScreen for each screen (manages bar + all panels) + // MainScreen for each screen AllScreens {} } } From 06566fbd0470ec2d1cc51fea3852bf96a67800c8 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sun, 9 Nov 2025 19:27:23 +0100 Subject: [PATCH 046/107] TemplateRegistry: make the user-templates.toml generation more robust --- Services/Theming/TemplateRegistry.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index 40d0542c..07e90078 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -206,12 +206,16 @@ Singleton { function doWriteUserTemplatesToml() { var userConfigPath = Settings.configDir + "user-templates.toml" var configContent = buildUserTemplatesToml() + var userConfigPathEsc = userConfigPath.replace(/'/g, "'\\''") // Ensure directory exists (should already exist but just in case) Quickshell.execDetached(["mkdir", "-p", Settings.configDir]) - // Write the config file - Quickshell.execDetached(["sh", "-c", `echo '${configContent.replace(/'/g, "'\\''")}' > '${userConfigPath}'`]) + // Write the config file using heredoc to avoid escaping issues + var script = `cat > '${userConfigPathEsc}' << 'EOF'\n` + script += configContent + script += "EOF\n" + Quickshell.execDetached(["sh", "-c", script]) Logger.d("TemplateRegistry", "User templates config written to:", userConfigPath) } From c3476c9f9e0539cf08206d5efa6cd62ec663d529 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 13:40:06 -0500 Subject: [PATCH 047/107] MediaCard: fix vertical centering of disc icon when nothing is playing --- .../Panels/ControlCenter/Cards/MediaCard.qml | 63 +++++-------------- 1 file changed, 15 insertions(+), 48 deletions(-) diff --git a/Modules/Panels/ControlCenter/Cards/MediaCard.qml b/Modules/Panels/ControlCenter/Cards/MediaCard.qml index 2914ee09..7b922fbb 100644 --- a/Modules/Panels/ControlCenter/Cards/MediaCard.qml +++ b/Modules/Panels/ControlCenter/Cards/MediaCard.qml @@ -183,61 +183,28 @@ NBox { } } - ColumnLayout { + // Content container that adjusts for player selector + Item { anchors.fill: parent - anchors.margins: Style.marginM + anchors.topMargin: playerSelectorButton.visible ? (playerSelectorButton.height + Style.marginXS + Style.marginM) : Style.marginM + anchors.leftMargin: Style.marginM + anchors.rightMargin: Style.marginM + anchors.bottomMargin: Style.marginM - // No media player detected - use Loader for performance - Loader { - id: fallbackLoader - Layout.fillWidth: true - Layout.fillHeight: true - active: !root.hasActivePlayer - - sourceComponent: ColumnLayout { - spacing: Style.marginS - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - - Item { - Layout.fillWidth: true - - ColumnLayout { - anchors.centerIn: parent - spacing: Style.marginL - - Item { - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: Style.fontSizeXXXL * 4 - Layout.preferredHeight: Style.fontSizeXXXL * 4 - - // Center icon (static, no animation to avoid GPU cost) - NIcon { - anchors.centerIn: parent - icon: "disc" - pointSize: Style.fontSizeXXXL * 3 - color: Color.mOnSurfaceVariant - opacity: 1.0 - } - } - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - } + // No media player detected - centered disc icon + NIcon { + anchors.centerIn: parent + visible: !root.hasActivePlayer + icon: "disc" + pointSize: Style.fontSizeXXXL * 3 + color: Color.mOnSurfaceVariant + opacity: 1.0 } // MediaPlayer Main Content - use Loader for performance Loader { id: mainLoader - Layout.fillWidth: true - Layout.fillHeight: true + anchors.fill: parent active: root.hasActivePlayer sourceComponent: ColumnLayout { From 23a548e034800be68b7a5ab2b7c4f81582847b39 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 13:50:20 -0500 Subject: [PATCH 048/107] BarContent: fix screen assignment --- Modules/MainScreen/AllScreens.qml | 4 +- Modules/MainScreen/BarContentWindow.qml | 82 ++++++++++--------------- Modules/Panels/Tray/TrayMenuPanel.qml | 1 + 3 files changed, 37 insertions(+), 50 deletions(-) diff --git a/Modules/MainScreen/AllScreens.qml b/Modules/MainScreen/AllScreens.qml index 7ae5a29c..3211a332 100644 --- a/Modules/MainScreen/AllScreens.qml +++ b/Modules/MainScreen/AllScreens.qml @@ -53,7 +53,9 @@ Variants { } asynchronous: false - sourceComponent: BarContentWindow {} + sourceComponent: BarContentWindow { + screen: modelData + } onLoaded: { Logger.d("Shell", "BarContentWindow created for", modelData?.name) diff --git a/Modules/MainScreen/BarContentWindow.qml b/Modules/MainScreen/BarContentWindow.qml index 9637a410..b83e975c 100644 --- a/Modules/MainScreen/BarContentWindow.qml +++ b/Modules/MainScreen/BarContentWindow.qml @@ -12,61 +12,45 @@ import qs.Modules.Bar * This window contains only the bar widgets (content), while the background * is rendered in MainScreen's unified Shape system. This separation prevents * fullscreen redraws when bar widgets redraw. + * + * This component should be instantiated once per screen by AllScreens.qml */ -Variants { - model: Quickshell.screens +PanelWindow { + id: barWindow - delegate: Loader { - id: barWindowLoader + // Note: screen property is inherited from PanelWindow and should be set by parent + color: Color.transparent // Transparent - background is in MainScreen below - required property ShellScreen modelData + Component.onCompleted: { + Logger.d("BarContentWindow", "Bar content window created for screen:", barWindow.screen?.name) + } - // Only create window if bar should be visible on this screen - active: { - if (!modelData || !modelData.name) - return false - var monitors = Settings.data.bar.monitors || [] - return BarService.isVisible && (monitors.length === 0 || monitors.includes(modelData.name)) - } + // Wayland layer configuration + WlrLayershell.namespace: "noctalia-bar-content-" + (barWindow.screen?.name || "unknown") + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone in MainScreen handles that - sourceComponent: PanelWindow { - id: barWindow - screen: modelData + // Position and size to match bar location + readonly property string barPosition: Settings.data.bar.position || "top" + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property bool barFloating: Settings.data.bar.floating || false + readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 + readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 - color: Color.transparent // Transparent - background is in MainScreen below + // Anchor to the bar's edge + anchors { + top: barPosition === "top" || barIsVertical + bottom: barPosition === "bottom" || barIsVertical + left: barPosition === "left" || !barIsVertical + right: barPosition === "right" || !barIsVertical + } + // Set to FULL screen dimensions - margins will reduce the actual window size + implicitWidth: (barIsVertical ? (Style.barHeight + 1) : barWindow.screen.width) + barMarginH + implicitHeight: (barIsVertical ? barWindow.screen.height : Style.barHeight) + barMarginV - Component.onCompleted: { - Logger.d("BarContentWindow", "Bar content window created for screen:", screen?.name) - } - - // Wayland layer configuration - WlrLayershell.namespace: "noctalia-bar-content-" + (screen?.name || "unknown") - WlrLayershell.layer: WlrLayer.Top - WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone in MainScreen handles that - - // Position and size to match bar location - readonly property string barPosition: Settings.data.bar.position || "top" - readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" - readonly property bool barFloating: Settings.data.bar.floating || false - readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 - readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 - - // Anchor to the bar's edge - anchors { - top: barPosition === "top" || barIsVertical - bottom: barPosition === "bottom" || barIsVertical - left: barPosition === "left" || !barIsVertical - right: barPosition === "right" || !barIsVertical - } - // Set to FULL screen dimensions - margins will reduce the actual window size - implicitWidth: (barIsVertical ? (Style.barHeight + 1) : screen.width) + barMarginH - implicitHeight: (barIsVertical ? screen.height : Style.barHeight) + barMarginV - - // Bar content - just the widgets, no background - Bar { - anchors.fill: parent - screen: modelData - } - } + // Bar content - just the widgets, no background + Bar { + anchors.fill: parent + screen: barWindow.screen } } diff --git a/Modules/Panels/Tray/TrayMenuPanel.qml b/Modules/Panels/Tray/TrayMenuPanel.qml index fa354fb7..23ed1d74 100644 --- a/Modules/Panels/Tray/TrayMenuPanel.qml +++ b/Modules/Panels/Tray/TrayMenuPanel.qml @@ -135,6 +135,7 @@ SmartPanel { maxWidth = Math.max(maxWidth, requiredWidth) } } catch (e) { + // Silently ignore errors during width calculation } } From 43441b44d8fdd4157e7f89fef9d132e7f491208e Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 14:35:00 -0500 Subject: [PATCH 049/107] Notifications: better way to avoid shadow cut-off with autoPadding --- Modules/Notification/Notification.qml | 11 ++++++----- Widgets/NDropShadows.qml | 4 ++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 8abe45f0..f09ef643 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -61,7 +61,7 @@ Variants { readonly property string barPos: Settings.data.bar.position readonly property bool isFloating: Settings.data.bar.floating - readonly property int notifWidth: Math.round(420 * Style.uiScaleRatio) + readonly property int notifWidth: Math.round(400 * Style.uiScaleRatio) // Calculate bar offsets for each edge separately readonly property int barOffsetTop: { @@ -99,10 +99,10 @@ Variants { anchors.right: isRight // Margins for PanelWindow - only apply bar offset for the specific edge where the bar is - margins.top: isTop ? Style.marginM + barOffsetTop : 0 - margins.bottom: isBottom ? Style.marginM + barOffsetBottom : 0 - margins.left: isLeft ? Style.marginM + barOffsetLeft : 0 - margins.right: isRight ? Style.marginM + barOffsetRight : 0 + margins.top: isTop ? barOffsetTop : 0 + margins.bottom: isBottom ? barOffsetBottom : 0 + margins.left: isLeft ? barOffsetLeft : 0 + margins.right: isRight ? barOffsetRight : 0 implicitWidth: notifWidth implicitHeight: notificationStack.implicitHeight + Style.marginL @@ -251,6 +251,7 @@ Variants { NDropShadows { anchors.fill: cardBackground source: cardBackground + autoPaddingEnabled: true } // Hover handling diff --git a/Widgets/NDropShadows.qml b/Widgets/NDropShadows.qml index a0fb3bb3..1182f579 100644 --- a/Widgets/NDropShadows.qml +++ b/Widgets/NDropShadows.qml @@ -6,8 +6,11 @@ import qs.Services.Power // Unified shadow system Item { id: root + required property var source + property bool autoPaddingEnabled: false + layer.enabled: Settings.data.general.enableShadows && !PowerProfileService.noctaliaPerformanceMode layer.effect: MultiEffect { source: root.source @@ -18,5 +21,6 @@ Item { shadowColor: Color.black shadowHorizontalOffset: Settings.data.general.shadowOffsetX shadowVerticalOffset: Settings.data.general.shadowOffsetY + autoPaddingEnabled: root.autoPaddingEnabled } } From 7f141df200e45eae25c94b1b9cef6e619e704484 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 14:46:10 -0500 Subject: [PATCH 050/107] OSD: better shadow management --- Modules/OSD/OSD.qml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 1c7cc2ac..d3458f2e 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -140,8 +140,8 @@ Variants { readonly property bool isCentered: (location === "top" || location === "bottom") readonly property bool verticalMode: (location === "left" || location === "right") readonly property int hWidth: Math.round(420 * Style.uiScaleRatio) - readonly property int hHeight: Math.round(84 * Style.uiScaleRatio) - readonly property int vWidth: Math.round(92 * Style.uiScaleRatio) + readonly property int hHeight: Math.round(72 * Style.uiScaleRatio) + readonly property int vWidth: Math.round(84 * Style.uiScaleRatio) readonly property int vHeight: Math.round(380 * Style.uiScaleRatio) // Ensure an even width to keep the vertical bar perfectly centered @@ -210,6 +210,10 @@ Variants { WlrLayershell.layer: (Settings.data.osd && Settings.data.osd.overlayLayer) ? WlrLayer.Overlay : WlrLayer.Top exclusionMode: PanelWindow.ExclusionMode.Ignore + // Rectangle { + // anchors.fill: parent + // color: "#4400FF00" + // } Item { id: osdItem anchors.fill: parent @@ -255,7 +259,7 @@ Variants { Rectangle { id: background anchors.fill: parent - anchors.margins: Style.marginM * 2 + anchors.margins: Style.marginM * 1.5 radius: Style.radiusL color: Color.mSurface border.color: Color.mOutline @@ -265,17 +269,17 @@ Variants { } } - // MultiEffect applied to background only NDropShadows { anchors.fill: background source: background + autoPaddingEnabled: true } // Content loader on top of the background (not affected by MultiEffect) Loader { id: contentLoader - anchors.fill: parent - anchors.margins: Style.marginL * 2 + anchors.fill: background + anchors.margins: Style.marginM active: true sourceComponent: panel.verticalMode ? verticalContent : horizontalContent } From 9153a784ae76e6e34a21919cb1e21f03eb19ef39 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 15:02:33 -0500 Subject: [PATCH 051/107] Toast: better shadows --- Modules/Toast/SimpleToast.qml | 7 ++- Modules/Toast/ToastScreen.qml | 102 ++++++++++++++++------------------ 2 files changed, 51 insertions(+), 58 deletions(-) diff --git a/Modules/Toast/SimpleToast.qml b/Modules/Toast/SimpleToast.qml index 947dae17..9dbd62be 100644 --- a/Modules/Toast/SimpleToast.qml +++ b/Modules/Toast/SimpleToast.qml @@ -16,8 +16,8 @@ Item { signal hidden - width: 480 - height: Math.round(contentLayout.implicitHeight + Style.marginL * 4) + width: Math.round(420 * Style.uiScaleRatio + Style.marginM * 1.5 * 2) + height: Math.round(contentLayout.implicitHeight + Style.marginM * 3 * 2) visible: true opacity: 0 scale: initialScale @@ -26,7 +26,7 @@ Item { Rectangle { id: background anchors.fill: parent - anchors.margins: Style.marginL + anchors.margins: Style.marginM * 1.5 radius: Style.radiusL color: Color.mSurface @@ -47,6 +47,7 @@ Item { NDropShadows { anchors.fill: background source: background + autoPaddingEnabled: true } Behavior on opacity { diff --git a/Modules/Toast/ToastScreen.qml b/Modules/Toast/ToastScreen.qml index 7ed7720c..0fd2d91c 100644 --- a/Modules/Toast/ToastScreen.qml +++ b/Modules/Toast/ToastScreen.qml @@ -133,66 +133,60 @@ Item { screen: root.screen - readonly property string location: (Settings.data.notifications && Settings.data.notifications.location) ? Settings.data.notifications.location : "top_right" - readonly property bool isTop: (location === "top") || (location.length >= 3 && location.substring(0, 3) === "top") - readonly property bool isBottom: (location === "bottom") || (location.length >= 6 && location.substring(0, 6) === "bottom") - readonly property bool isLeft: location.indexOf("_left") >= 0 - readonly property bool isRight: location.indexOf("_right") >= 0 - readonly property bool isCentered: (location === "top" || location === "bottom") + // Parse location setting + readonly property string location: Settings.data.notifications?.location || "top_right" + readonly property bool isTop: location.startsWith("top") + readonly property bool isBottom: location.startsWith("bottom") + readonly property bool isLeft: location.endsWith("_left") + readonly property bool isRight: location.endsWith("_right") + readonly property bool isCentered: location === "top" || location === "bottom" - // Anchor selection based on location (window edges) + readonly property string barPos: Settings.data.bar.position + readonly property bool isFloating: Settings.data.bar.floating + + // Calculate bar offsets for each edge separately + readonly property int barOffsetTop: { + if (barPos !== "top") + return 0 + const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 + return Style.barHeight + floatMarginV + } + + readonly property int barOffsetBottom: { + if (barPos !== "bottom") + return 0 + const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 + return Style.barHeight + floatMarginV + } + + readonly property int barOffsetLeft: { + if (barPos !== "left") + return 0 + const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 + return floatMarginH + } + + readonly property int barOffsetRight: { + if (barPos !== "right") + return 0 + const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 + return floatMarginH + } + + // Anchoring anchors.top: isTop anchors.bottom: isBottom anchors.left: isLeft anchors.right: isRight - // Margins depending on bar position and chosen location - margins.top: { - if (!(anchors.top)) - 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 - } - return base - } + // Margins for PanelWindow - only apply bar offset for the specific edge where the bar is + margins.top: isTop ? barOffsetTop : 0 + margins.bottom: isBottom ? barOffsetBottom : 0 + margins.left: isLeft ? barOffsetLeft : 0 + margins.right: isRight ? barOffsetRight : 0 - margins.bottom: { - if (!(anchors.bottom)) - 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 - } - return base - } - - margins.left: { - if (!(anchors.left)) - 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 - } - return base - } - - margins.right: { - if (!(anchors.right)) - 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 - } - return base - } - - implicitWidth: Math.round(toastItem.width + Style.marginL * 2) - implicitHeight: Math.round(toastItem.height + Style.marginL * 2) + implicitWidth: Math.round(toastItem.width) + implicitHeight: Math.round(toastItem.height) color: Color.transparent @@ -211,8 +205,6 @@ Item { SimpleToast { id: toastItem - - anchors.horizontalCenter: parent.horizontalCenter onHidden: root.onToastHidden() } } From 7c168b3de0d0240ef0750e2c6162d05cb5cf38ca Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 16:16:07 -0500 Subject: [PATCH 052/107] autoformatting --- Modules/Panels/Bluetooth/BluetoothPanel.qml | 4 ++-- Modules/Panels/WiFi/WiFiPanel.qml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Panels/Bluetooth/BluetoothPanel.qml b/Modules/Panels/Bluetooth/BluetoothPanel.qml index b1b8adc7..b74bc391 100644 --- a/Modules/Panels/Bluetooth/BluetoothPanel.qml +++ b/Modules/Panels/Bluetooth/BluetoothPanel.qml @@ -95,7 +95,7 @@ SmartPanel { Item { Layout.fillHeight: true } - + NIcon { icon: "bluetooth-off" pointSize: 48 @@ -118,7 +118,7 @@ SmartPanel { Layout.fillWidth: true wrapMode: Text.WordWrap } - + Item { Layout.fillHeight: true } diff --git a/Modules/Panels/WiFi/WiFiPanel.qml b/Modules/Panels/WiFi/WiFiPanel.qml index c5e8feea..76d6428c 100644 --- a/Modules/Panels/WiFi/WiFiPanel.qml +++ b/Modules/Panels/WiFi/WiFiPanel.qml @@ -591,4 +591,4 @@ SmartPanel { } } } -} \ No newline at end of file +} From bae3c3495b74c19f92441cf9147f526ac995b195 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 16:54:07 -0500 Subject: [PATCH 053/107] Launcher: fix transparency --- Modules/MainScreen/Backgrounds/AllBackgrounds.qml | 15 +++++++++++++++ Modules/MainScreen/Backgrounds/BarBackground.qml | 6 ++++-- .../MainScreen/Backgrounds/PanelBackground.qml | 4 +++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml index e566aedb..acb6c53a 100644 --- a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml +++ b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml @@ -57,6 +57,7 @@ Item { bar: root.bar shapeContainer: backgroundsShape windowRoot: root.windowRoot + backgroundColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) } @@ -68,84 +69,98 @@ Item { PanelBackground { panel: root.windowRoot.audioPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // Battery PanelBackground { panel: root.windowRoot.batteryPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // Bluetooth PanelBackground { panel: root.windowRoot.bluetoothPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // Calendar PanelBackground { panel: root.windowRoot.calendarPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // Control Center PanelBackground { panel: root.windowRoot.controlCenterPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // Launcher PanelBackground { panel: root.windowRoot.launcherPanel shapeContainer: backgroundsShape + backgroundColor: Qt.alpha(Color.mSurface, Settings.data.appLauncher.backgroundOpacity) } // Notification History PanelBackground { panel: root.windowRoot.notificationHistoryPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // Session Menu PanelBackground { panel: root.windowRoot.sessionMenuPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // Settings PanelBackground { panel: root.windowRoot.settingsPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // Setup Wizard PanelBackground { panel: root.windowRoot.setupWizardPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // TrayDrawer PanelBackground { panel: root.windowRoot.trayDrawerPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // TrayMenu PanelBackground { panel: root.windowRoot.trayMenuPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // Wallpaper PanelBackground { panel: root.windowRoot.wallpaperPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } // WiFi PanelBackground { panel: root.windowRoot.wifiPanel shapeContainer: backgroundsShape + backgroundColor: Color.mSurface } } diff --git a/Modules/MainScreen/Backgrounds/BarBackground.qml b/Modules/MainScreen/Backgrounds/BarBackground.qml index e91ca8cc..31e4cbc4 100644 --- a/Modules/MainScreen/Backgrounds/BarBackground.qml +++ b/Modules/MainScreen/Backgrounds/BarBackground.qml @@ -29,8 +29,10 @@ ShapePath { // Required reference to windowRoot for screen access required property var windowRoot + required property color backgroundColor + // Check if bar should be visible on this screen - readonly property bool shouldShowBar: { + readonly property bool shouldShow: { // Check global bar visibility if (!BarService.isVisible) return false @@ -83,7 +85,7 @@ ShapePath { // ShapePath configuration strokeWidth: -1 // No stroke, fill only - fillColor: shouldShowBar ? Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) : Color.transparent + fillColor: shouldShow ? backgroundColor : Color.transparent // Starting position (top-left corner, after the arc) // Use mapped coordinates relative to the Shape container diff --git a/Modules/MainScreen/Backgrounds/PanelBackground.qml b/Modules/MainScreen/Backgrounds/PanelBackground.qml index 686ac35f..bc71d473 100644 --- a/Modules/MainScreen/Backgrounds/PanelBackground.qml +++ b/Modules/MainScreen/Backgrounds/PanelBackground.qml @@ -28,6 +28,8 @@ ShapePath { // Corner radius (from Style) readonly property real radius: Style.radiusL + required property color backgroundColor + // Get the actual panelBackground Item from SmartPanel // Only access panelRegion if panel exists and is visible readonly property var panelBg: (panel && panel.visible) ? panel.panelRegion : null @@ -75,7 +77,7 @@ ShapePath { startX: panelX + tlRadius * tlMultX startY: panelY - fillColor: Color.mSurface + fillColor: (panel && panel.visible) ? backgroundColor : Color.transparent // ========== PATH DEFINITION ========== // Draws a rectangle with potentially inverted corners From 0a07c96f5b3dccc56c7f503197a1034c9db18439 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 16:55:41 -0500 Subject: [PATCH 054/107] v3.0.3 --- Services/Noctalia/UpdateService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/Noctalia/UpdateService.qml b/Services/Noctalia/UpdateService.qml index d36e57ea..4e6b76f4 100644 --- a/Services/Noctalia/UpdateService.qml +++ b/Services/Noctalia/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "3.0.2" - property bool isDevelopment: true + property string baseVersion: "3.0.3" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 689e96ea8190fb73d1740281622087b60ff814b9 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 16:56:58 -0500 Subject: [PATCH 055/107] back to -dev --- Services/Noctalia/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Noctalia/UpdateService.qml b/Services/Noctalia/UpdateService.qml index 4e6b76f4..f2046b9b 100644 --- a/Services/Noctalia/UpdateService.qml +++ b/Services/Noctalia/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "3.0.3" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 55f2a03a5008cee7dad7e4f44cf38a770ac69753 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 17:45:57 -0500 Subject: [PATCH 056/107] Theming-vicinae: improve cp syntax --- Services/Theming/TemplateRegistry.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index 07e90078..aa0b4a32 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -87,7 +87,7 @@ Singleton { "outputs": [{ "path": "~/.local/share/vicinae/themes/matugen.toml" }], - "postProcess": () => `cp -n ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae` + "postProcess": () => `cp --update=none ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae` }, { "id": "walker", "name": "Walker", From 18e9b456e2db2943d1c717addb8525810c013d7e Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 17:49:30 -0500 Subject: [PATCH 057/107] Added debug log --- Modules/MainScreen/SmartPanel.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/MainScreen/SmartPanel.qml b/Modules/MainScreen/SmartPanel.qml index 547654ed..1d06371f 100644 --- a/Modules/MainScreen/SmartPanel.qml +++ b/Modules/MainScreen/SmartPanel.qml @@ -528,6 +528,7 @@ Item { Logger.d("SmartPanel", "Position calculated:", calculatedX, calculatedY) Logger.d("SmartPanel", " Panel size:", panelWidth, "x", panelHeight) + Logger.d("SmartPanel", " Parent size:", root.width, "x", root.height) } // Watch for changes in content-driven sizes and update position From d63e6216e130505e95ec82299d4d25acfa306893 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 18:01:02 -0500 Subject: [PATCH 058/107] SetupWizard: Don't open immediately to avoid incomplete screen setup on older hardware and older Qt versions. --- shell.qml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/shell.qml b/shell.qml index 666d9884..33d6c8fd 100644 --- a/shell.qml +++ b/shell.qml @@ -112,6 +112,18 @@ ShellRoot { } } + // --------------------------------------------- + // Setup Wizard + // --------------------------------------------- + Timer { + id: setupWizardTimer + running: false + interval: 1000 + onTriggered: { + showSetupWizard() + } + } + function checkSetupWizard() { // Wait for distro service if (!DistroService.isReady) { @@ -126,7 +138,7 @@ ShellRoot { } if (Settings.data.settingsVersion >= Settings.settingsVersion) { - showSetupWizard() + setupWizardTimer.start() } else { Settings.data.setupCompleted = true } @@ -141,9 +153,7 @@ ShellRoot { setupPanel.open() } else { // If not yet loaded, ensure it loads and try again shortly - Qt.callLater(() => { - showSetupWizard() - }) + setupWizardTimer.restart() } } } From a0ebd39023231cfa762feacdd229097ac7fdab00 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 18:08:55 -0500 Subject: [PATCH 059/107] Wi-Fi: wrap env var object to avoid erroring --- Services/Networking/NetworkService.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Services/Networking/NetworkService.qml b/Services/Networking/NetworkService.qml index 61731a4c..b9bbe84a 100644 --- a/Services/Networking/NetworkService.qml +++ b/Services/Networking/NetworkService.qml @@ -516,9 +516,9 @@ Singleton { return cmd } } - environment: { - "LC_ALL": "C" - } + environment: ({ + "LC_ALL": "C" + }) stdout: StdioCollector { onStreamFinished: { From ef4c773d13af34a25d6fd98ef8e41c8638ffac31 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 18:38:15 -0500 Subject: [PATCH 060/107] ClockSettings: fix wrong import --- Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml index a214f508..3e650121 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets -import qs.Services +import qs.Services.System ColumnLayout { id: root From 6f215b4265dec51d9783ee2854d405b4f4a66662 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 18:39:15 -0500 Subject: [PATCH 061/107] BarContent: better margins-anchoring management --- Modules/Bar/Bar.qml | 4 ++-- Modules/MainScreen/BarContentWindow.qml | 15 ++++++++++++--- Modules/Panels/Settings/SettingsPanel.qml | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index f5cc180e..76d0b5a1 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -27,8 +27,8 @@ Item { readonly property string barPosition: Settings.data.bar.position || "top" readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" readonly property bool barFloating: Settings.data.bar.floating || false - readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 - readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 + readonly property real barMarginH: 0 //barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 + readonly property real barMarginV: 0 //barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 // Fill the parent (the Loader) anchors.fill: parent diff --git a/Modules/MainScreen/BarContentWindow.qml b/Modules/MainScreen/BarContentWindow.qml index b83e975c..66958b0e 100644 --- a/Modules/MainScreen/BarContentWindow.qml +++ b/Modules/MainScreen/BarContentWindow.qml @@ -44,9 +44,18 @@ PanelWindow { left: barPosition === "left" || !barIsVertical right: barPosition === "right" || !barIsVertical } - // Set to FULL screen dimensions - margins will reduce the actual window size - implicitWidth: (barIsVertical ? (Style.barHeight + 1) : barWindow.screen.width) + barMarginH - implicitHeight: (barIsVertical ? barWindow.screen.height : Style.barHeight) + barMarginV + + // Handle floating margins + margins { + top: barPosition === "top" || barIsVertical ? barMarginV : 0 + bottom: barPosition === "bottom" || barIsVertical ? barMarginV : 0 + left: barPosition === "left" || !barIsVertical ? barMarginH : 0 + right: barPosition === "right" || !barIsVertical ? barMarginH : 0 + } + + // Set a tight window size + implicitWidth: barIsVertical ? Style.barHeight : barWindow.screen.width + implicitHeight: barIsVertical ? barWindow.screen.height : Style.barHeight // Bar content - just the widgets, no background Bar { diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index e7ce327d..9d024e7d 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -17,6 +17,8 @@ SmartPanel { readonly property bool attachToBar: Settings.data.ui.settingsPanelAttachToBar readonly property string barPosition: Settings.data.bar.position readonly property bool barFloating: Settings.data.bar.floating + readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 + readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 forceAttachToBar: attachToBar panelAnchorHorizontalCenter: attachToBar ? (barPosition === "top" || barPosition === "bottom") : true @@ -44,6 +46,18 @@ SmartPanel { } } + onBarMarginHChanged: { + if (isPanelOpen) { + Qt.callLater(root.setPosition) + } + } + + onBarMarginVChanged: { + if (isPanelOpen) { + Qt.callLater(root.setPosition) + } + } + // Tabs enumeration, order is NOT relevant enum Tab { About, From b2a4a25488681fffa6200ff5491a4ee5e9a70b30 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 18:46:17 -0500 Subject: [PATCH 062/107] IPC: Added a lot of missing imports. --- Services/Control/IPCService.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Services/Control/IPCService.qml b/Services/Control/IPCService.qml index 1d753cce..6a063eb4 100644 --- a/Services/Control/IPCService.qml +++ b/Services/Control/IPCService.qml @@ -3,7 +3,13 @@ import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Widgets + import qs.Commons +import qs.Services.Hardware +import qs.Services.Media +import qs.Services.Power +import qs.Services.System +import qs.Services.Theming import qs.Services.UI Item { From 889a5875417df5e326582fa5ec2201a9494ecc95 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 18:46:50 -0500 Subject: [PATCH 063/107] 3.0.4 --- Services/Noctalia/UpdateService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/Noctalia/UpdateService.qml b/Services/Noctalia/UpdateService.qml index f2046b9b..d097195a 100644 --- a/Services/Noctalia/UpdateService.qml +++ b/Services/Noctalia/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "3.0.3" - property bool isDevelopment: true + property string baseVersion: "3.0.4" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From cd0556064bcdaa7a182c82332d2f0436b9ee338c Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 19:24:17 -0500 Subject: [PATCH 064/107] Back to -dev + fix sessionMenu lock and suspend IPC call --- Services/Control/IPCService.qml | 1 + Services/Noctalia/UpdateService.qml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Services/Control/IPCService.qml b/Services/Control/IPCService.qml index 6a063eb4..6da76420 100644 --- a/Services/Control/IPCService.qml +++ b/Services/Control/IPCService.qml @@ -5,6 +5,7 @@ import Quickshell.Wayland import Quickshell.Widgets import qs.Commons +import qs.Services.Compositor import qs.Services.Hardware import qs.Services.Media import qs.Services.Power diff --git a/Services/Noctalia/UpdateService.qml b/Services/Noctalia/UpdateService.qml index d097195a..16b85582 100644 --- a/Services/Noctalia/UpdateService.qml +++ b/Services/Noctalia/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "3.0.4" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From bf215b0728271f047d653470b25561a824da1cb1 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 19:36:57 -0500 Subject: [PATCH 065/107] BT+WiFi: increasing panel width from 340 to 400. --- Modules/Panels/Bluetooth/BluetoothPanel.qml | 2 +- Modules/Panels/WiFi/WiFiPanel.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Panels/Bluetooth/BluetoothPanel.qml b/Modules/Panels/Bluetooth/BluetoothPanel.qml index b74bc391..d5661867 100644 --- a/Modules/Panels/Bluetooth/BluetoothPanel.qml +++ b/Modules/Panels/Bluetooth/BluetoothPanel.qml @@ -12,7 +12,7 @@ import qs.Modules.MainScreen SmartPanel { id: root - preferredWidth: Math.round(340 * Style.uiScaleRatio) + preferredWidth: Math.round(400 * Style.uiScaleRatio) preferredHeight: Math.round(500 * Style.uiScaleRatio) panelContent: Rectangle { diff --git a/Modules/Panels/WiFi/WiFiPanel.qml b/Modules/Panels/WiFi/WiFiPanel.qml index 76d6428c..178e010f 100644 --- a/Modules/Panels/WiFi/WiFiPanel.qml +++ b/Modules/Panels/WiFi/WiFiPanel.qml @@ -10,7 +10,7 @@ import qs.Widgets SmartPanel { id: root - preferredWidth: Math.round(340 * Style.uiScaleRatio) + preferredWidth: Math.round(400 * Style.uiScaleRatio) preferredHeight: Math.round(500 * Style.uiScaleRatio) property string passwordSsid: "" From e13da1eaa2ee16db10c8ee24e8aa4cfa4af75d8f Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 19:40:23 -0500 Subject: [PATCH 066/107] TemplateProcessor: Toast a warning instead of error when encoutering an issue. --- Services/Theming/TemplateProcessor.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Services/Theming/TemplateProcessor.qml b/Services/Theming/TemplateProcessor.qml index f9163704..2261e95d 100644 --- a/Services/Theming/TemplateProcessor.qml +++ b/Services/Theming/TemplateProcessor.qml @@ -326,18 +326,18 @@ Singleton { // Error reporting helpers property string generator: "" - function showError() { + function showWarning() { const description = (stderr.text && stderr.text.trim() !== "") ? stderr.text.trim() : ((stdout.text && stdout.text.trim() !== "") ? stdout.text.trim() : I18n.tr("toast.theming-processor-failed.desc-generic")) const title = I18n.tr(`toast.theming-processor-failed.title-${generator}`) // Give a bit more time to the user to read, as it can contains important information for debugging user's templates. - ToastService.showError(title, description, 8000) + ToastService.showWarning(title, description, 8000) return description } onExited: function (exitCode) { if (exitCode !== 0) { - const description = generateProcess.showError() + const description = generateProcess.showWarning() Logger.e("TemplateProcessor", "Process failed with exit code", exitCode, description) } } @@ -351,7 +351,7 @@ Singleton { stderr: StdioCollector { onStreamFinished: { if (this.text) { - const description = generateProcess.showError() + const description = generateProcess.showWarning() Logger.e("TemplateProcessor", "Process failed", description) } } From 481f71b1011bf9002c833f166cbdfe6604d80f98 Mon Sep 17 00:00:00 2001 From: lycia324 Date: Mon, 10 Nov 2025 08:58:18 +0800 Subject: [PATCH 067/107] Optimizing Chinese translation in 'colors' and 'dark-mode'; Adding space between English and Chinese characters. --- Assets/Translations/zh-CN.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 6ab2bbef..b6cc489d 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -402,11 +402,11 @@ } }, "colors": { - "error": "错误", - "onSurface": "表面之上", - "primary": "主要的", - "secondary": "次要的", - "tertiary": "第三纪" + "error": "错误色", + "onSurface": "表层对应色", + "primary": "主要色", + "secondary": "次要色", + "tertiary": "第三色" }, "control-center": { "position": { @@ -820,7 +820,7 @@ "description": "启用自动切换浅色和深色模式。", "label": "深色模式计划", "location": "位置", - "manual": "手册", + "manual": "手动", "off": "关" }, "switch": { @@ -1177,7 +1177,7 @@ "hooks": { "info": { "command-info": { - "description": "• 命令通过 shell 执行 (sh -c)\n• 命令在后台运行(分离)\n• 测试按钮使用当前值执行", + "description": "• 命令通过 shell 执行 gsh -c)\n• 命令在后台运行(分离)\n• 测试按钮使用当前值执行", "label": "钩子命令信息" }, "parameters": { @@ -1574,7 +1574,7 @@ "label": "填充模式" }, "section": { - "label": "外观和感觉" + "label": "外观与体验" }, "transition-duration": { "description": "过渡动画的持续时间(秒)。", @@ -1717,7 +1717,7 @@ "enabled": "已启用" }, "kofi": { - "opened": "Ko-fi页面已在您的浏览器中打开" + "opened": "Ko-fi 页面已在您的浏览器中打开" }, "matugen": { "failed": "Matugen 处理失败", From a55b1601cbefa93bfaeef3de4977a49fc8ec4e5f Mon Sep 17 00:00:00 2001 From: lycia324 Date: Mon, 10 Nov 2025 09:21:27 +0800 Subject: [PATCH 068/107] Optimizing Chinese translation --- Assets/Translations/zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index b6cc489d..c9ed177a 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1177,7 +1177,7 @@ "hooks": { "info": { "command-info": { - "description": "• 命令通过 shell 执行 gsh -c)\n• 命令在后台运行(分离)\n• 测试按钮使用当前值执行", + "description": "• 命令通过 shell 执行 (sh -c)\n• 命令在后台运行(分离)\n• 测试按钮使用当前值执行", "label": "钩子命令信息" }, "parameters": { From a7af55a7cc1e22acdd45aa18a9d9be914fb3da81 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 20:30:55 -0500 Subject: [PATCH 069/107] Backgrounds: dont play with transparency, only use width/height to hide a shape. --- .../MainScreen/Backgrounds/BarBackground.qml | 18 +++++++++++------- .../MainScreen/Backgrounds/PanelBackground.qml | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Modules/MainScreen/Backgrounds/BarBackground.qml b/Modules/MainScreen/Backgrounds/BarBackground.qml index 31e4cbc4..11fd0404 100644 --- a/Modules/MainScreen/Backgrounds/BarBackground.qml +++ b/Modules/MainScreen/Backgrounds/BarBackground.qml @@ -53,9 +53,13 @@ ShapePath { // we can use bar.x and bar.y directly (they're already in screen coordinates) readonly property point barMappedPos: bar ? Qt.point(bar.x, bar.y) : Qt.point(0, 0) + // Effective dimensions - 0 when bar shouldn't show (similar to panel behavior) + readonly property real barWidth: (bar && shouldShow) ? bar.width : 0 + readonly property real barHeight: (bar && shouldShow) ? bar.height : 0 + // Flatten corners if bar is too small (handle null bar) - readonly property bool shouldFlatten: bar ? ShapeCornerHelper.shouldFlatten(bar.width, bar.height, radius) : false - readonly property real effectiveRadius: shouldFlatten ? (bar ? ShapeCornerHelper.getFlattenedRadius(Math.min(bar.width, bar.height), radius) : 0) : radius + readonly property bool shouldFlatten: bar ? ShapeCornerHelper.shouldFlatten(barWidth, barHeight, radius) : false + readonly property real effectiveRadius: shouldFlatten ? (bar ? ShapeCornerHelper.getFlattenedRadius(Math.min(barWidth, barHeight), radius) : 0) : radius // Helper function for getting corner radius based on state function getCornerRadius(cornerState) { @@ -85,7 +89,7 @@ ShapePath { // ShapePath configuration strokeWidth: -1 // No stroke, fill only - fillColor: shouldShow ? backgroundColor : Color.transparent + fillColor: backgroundColor // Starting position (top-left corner, after the arc) // Use mapped coordinates relative to the Shape container @@ -105,7 +109,7 @@ ShapePath { // Top edge (moving right) PathLine { - relativeX: (bar ? bar.width : 0) - root.tlRadius * root.tlMultX - root.trRadius * root.trMultX + relativeX: root.barWidth - root.tlRadius * root.tlMultX - root.trRadius * root.trMultX relativeY: 0 } @@ -121,7 +125,7 @@ ShapePath { // Right edge (moving down) PathLine { relativeX: 0 - relativeY: (bar ? bar.height : 0) - root.trRadius * root.trMultY - root.brRadius * root.brMultY + relativeY: root.barHeight - root.trRadius * root.trMultY - root.brRadius * root.brMultY } // Bottom-right corner arc @@ -135,7 +139,7 @@ ShapePath { // Bottom edge (moving left) PathLine { - relativeX: -((bar ? bar.width : 0) - root.brRadius * root.brMultX - root.blRadius * root.blMultX) + relativeX: -(root.barWidth - root.brRadius * root.brMultX - root.blRadius * root.blMultX) relativeY: 0 } @@ -151,7 +155,7 @@ ShapePath { // Left edge (moving up) - closes the path back to start PathLine { relativeX: 0 - relativeY: -((bar ? bar.height : 0) - root.blRadius * root.blMultY - root.tlRadius * root.tlMultY) + relativeY: -(root.barHeight - root.blRadius * root.blMultY - root.tlRadius * root.tlMultY) } // Top-left corner arc (back to start) diff --git a/Modules/MainScreen/Backgrounds/PanelBackground.qml b/Modules/MainScreen/Backgrounds/PanelBackground.qml index bc71d473..e91bface 100644 --- a/Modules/MainScreen/Backgrounds/PanelBackground.qml +++ b/Modules/MainScreen/Backgrounds/PanelBackground.qml @@ -77,7 +77,7 @@ ShapePath { startX: panelX + tlRadius * tlMultX startY: panelY - fillColor: (panel && panel.visible) ? backgroundColor : Color.transparent + fillColor: backgroundColor // ========== PATH DEFINITION ========== // Draws a rectangle with potentially inverted corners From 0724e31cf8897778f95c021adfebd041fa41a647 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 20:33:18 -0500 Subject: [PATCH 070/107] AppTheming: fix a warning when generating for predefined color schemes. --- Services/Theming/AppThemeService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Theming/AppThemeService.qml b/Services/Theming/AppThemeService.qml index 74c95099..5bbffc82 100644 --- a/Services/Theming/AppThemeService.qml +++ b/Services/Theming/AppThemeService.qml @@ -38,8 +38,8 @@ Singleton { if (Settings.data.colorSchemes.useWallpaperColors) { generateFromWallpaper() } else { + // applyScheme will trigger template generation via schemeReader.onLoaded ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) - generateFromPredefinedScheme() } } From ba00da67c59b82730c041e117db22bf9f9d90178 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 21:02:14 -0500 Subject: [PATCH 071/107] v3.0.5 --- Services/Noctalia/UpdateService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/Noctalia/UpdateService.qml b/Services/Noctalia/UpdateService.qml index 16b85582..c36c3a92 100644 --- a/Services/Noctalia/UpdateService.qml +++ b/Services/Noctalia/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "3.0.4" - property bool isDevelopment: true + property string baseVersion: "3.0.5" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From b7f071d31e356b9e48ace8a11fc5445873486b6a Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sun, 9 Nov 2025 21:02:58 -0500 Subject: [PATCH 072/107] Back to -dev --- Services/Noctalia/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Noctalia/UpdateService.qml b/Services/Noctalia/UpdateService.qml index c36c3a92..5cbcba5e 100644 --- a/Services/Noctalia/UpdateService.qml +++ b/Services/Noctalia/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "3.0.5" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From b4ac786e8b3b4c2a127218bb359005d0cbb4ccb8 Mon Sep 17 00:00:00 2001 From: Aiser <2912778691@qq.com> Date: Mon, 10 Nov 2025 13:20:51 +0800 Subject: [PATCH 073/107] Matugen: add spicetify theme for Spotify --- Assets/MatugenTemplates/spicetify.ini | 27 +++++++++++++++++++ Assets/Translations/de.json | 4 +++ Assets/Translations/en.json | 4 +++ Assets/Translations/es.json | 4 +++ Assets/Translations/fr.json | 4 +++ Assets/Translations/pt.json | 4 +++ Assets/Translations/tr.json | 4 +++ Assets/Translations/uk-UA.json | 4 +++ Assets/Translations/zh-CN.json | 4 +++ Commons/Settings.qml | 1 + .../Panels/Settings/Tabs/ColorSchemeTab.qml | 19 ++++++++++++- Services/System/ProgramCheckerService.qml | 4 ++- Services/Theming/TemplateRegistry.qml | 10 +++++++ 13 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 Assets/MatugenTemplates/spicetify.ini diff --git a/Assets/MatugenTemplates/spicetify.ini b/Assets/MatugenTemplates/spicetify.ini new file mode 100644 index 00000000..bff30bf4 --- /dev/null +++ b/Assets/MatugenTemplates/spicetify.ini @@ -0,0 +1,27 @@ +[Comfy] +text = {{ colors.on_surface.default.hex_stripped }} +subtext = {{ colors.on_surface_variant.default.hex_stripped }} +main = {{ colors.surface.default.hex_stripped }} +main-elevated = {{ colors.surface_bright.default.hex_stripped }} +main-transition = {{ colors.surface_container_lowest.default.hex_stripped }} +highlight = {{ colors.surface_container_low.default.hex_stripped }} +highlight-elevated = {{ colors.surface_container_highest.default.hex_stripped }} +sidebar = {{ colors.surface.default.hex_stripped }} +player = {{ colors.scrim.default.hex_stripped }} +card = {{ colors.scrim.default.hex_stripped }} +shadow = {{ colors.scrim.default.hex_stripped }} +selected-row = {{ colors.on_surface.default.hex_stripped }} +button = {{ colors.primary.default.hex_stripped }} +button-active = {{ colors.primary_fixed.default.hex_stripped }} +button-disabled = {{ colors.primary_fixed_dim.default.hex_stripped }} +tab-active = {{ colors.surface.default.hex_stripped }} +notification = {{ colors.tertiary.default.hex_stripped }} +notification-error = {{ colors.error.default.hex_stripped }} +misc = {{ colors.scrim.default.hex_stripped }} +play-button = {{ colors.secondary.default.hex_stripped }} +play-button-active = {{ colors.secondary_fixed.default.hex_stripped }} +progress-fg = {{ colors.primary.default.hex_stripped }} +progress-bg = {{ colors.surface.default.hex_stripped }} +heart = {{ colors.error.default.hex_stripped }} +pagelink-active = {{ colors.on_tertiary_container.default.hex_stripped }} +radio-btn-active = {{ colors.on_tertiary_container.default.hex_stripped }} diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f1be4512..a7f33d97 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -860,6 +860,10 @@ "description": "Schreibe {Dateipfad}. Das Hyprluna-Theme muss manuell installiert und aktiviert werden", "description-missing": "Benötigt die Installation von {app}" }, + "spicetify": { + "description": "Schreibe {Dateipfad}. Das Comfy-Theme muss manuell installiert und aktiviert werden", + "description-missing": "Benötigt die Installation von {app}" + }, "description": "Anwendungsspezifisches Theming.", "discord": { "description": "Schreibt {filepath} für {client}", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index bd3e9820..818438cc 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -860,6 +860,10 @@ "description": "Write {filepath}. Hyprluna theme needs to be installed and activated manually.", "description-missing": "Requires {app} to be installed" }, + "spicetify": { + "description": "Write {filepath}. Comfy theme needs to be installed and activated manually.", + "description-missing": "Requires {app} to be installed" + }, "description": "Application-specific theming.", "discord": { "description": "Write {filepath} for {client}. Hyprluna theme needs to be activated manually.", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index a68676bb..c5ad8291 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -860,6 +860,10 @@ "description": "Escribe {filepath}. El tema Hyprluna debe ser instalado y activado manualmente.", "description-missing": "Requiere que {app} esté instalado/a." }, + "spicetify": { + "description": "Escribe {filepath}. El tema Comfy debe ser instalado y activado manualmente.", + "description-missing": "Requiere que {app} esté instalado/a." + }, "description": "Tematización específica de aplicaciones.", "discord": { "description": "Escribir {filepath} para {client}", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index c4ca352f..937d7299 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -860,6 +860,10 @@ "description": "Écrire {filepath}. Le thème Hyprluna doit être installé et activé manuellement.", "description-missing": "Nécessite l'installation de {app}" }, + "spicetify": { + "description": "Écrire {filepath}. Le thème Comfy doit être installé et activé manuellement.", + "description-missing": "Nécessite l'installation de {app}" + }, "description": "Thématisation spécifique aux applications.", "discord": { "description": "Écrire {filepath} pour {client}", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 46d05463..fc3283bf 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -860,6 +860,10 @@ "description": "Escreva em {filepath}. O tema Hyprluna precisa ser instalado e ativado manualmente.", "description-missing": "Requer que o {app} esteja instalado." }, + "spicetify": { + "description": "Escreva em {filepath}. O tema Comfy precisa ser instalado e ativado manualmente.", + "description-missing": "Requer que o {app} esteja instalado." + }, "description": "Tematização específica de aplicativos.", "discord": { "description": "Escrever {filepath} para {client}", diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 5aa2474f..e235b715 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -860,6 +860,10 @@ "description": "{filepath} dosyasına yaz. Hyprluna temasının kurulu ve manuel olarak etkinleştirilmiş olması gerekir.", "description-missing": "Kurulum için {app} gereklidir" }, + "spicetify": { + "description": "{filepath} dosyasına yaz. Comfy temasının kurulu ve manuel olarak etkinleştirilmiş olması gerekir.", + "description-missing": "Kurulum için {app} gereklidir" + }, "description": "Uygulamaya özel temalandırma.", "discord": { "description": "{client} için {filepath} dosyasına yaz. Hyprluna temasının manuel olarak etkinleştirilmesi gerekir.", diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index fbcead9d..009bf24d 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -860,6 +860,10 @@ "description": "Записати {filepath}. Тему Hyprluna потрібно встановити та активувати вручну.", "description-missing": "Потрібна установка {app}" }, + "spicetify": { + "description": "Записати {filepath}. Тему Comfy потрібно встановити та активувати вручну.", + "description-missing": "Потрібна установка {app}" + }, "description": "Оформлення окремих програм.", "discord": { "description": "Записати {filepath} для {client}. Тему Hyprluna потрібно активувати вручну.", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 3b899fec..71c9b99f 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -860,6 +860,10 @@ "description": "写入 {filepath}。Hyprluna 主题需要手动安装和激活。", "description-missing": "需要安装 {app}" }, + "spicetify": { + "description": "写入 {filepath}。Comfy 主题需要手动安装和激活。", + "description-missing": "需要安装 {app}" + }, "description": "应用程序特定主题。", "discord": { "description": "为 {client} 写入 {filepath}", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 3d7be7f1..9600871b 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -407,6 +407,7 @@ Singleton { property bool vicinae: false property bool walker: false property bool code: false + property bool spicetify: false property bool enableUserTemplates: false property bool discord_vesktop: false // To be deleted soon diff --git a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml index b4059379..69df2e5a 100644 --- a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml @@ -757,8 +757,25 @@ ColumnLayout { } } } - } + NCheckbox { + label: "Spicetify" + description: ProgramCheckerService.spicetifyAvailable ? I18n.tr("settings.color-scheme.templates.programs.spicetify.description", { + "filepath": "~/.config/spicetify/Themes/Comfy/color.ini" + }) : I18n.tr("settings.color-scheme.templates.programs.spicetify.description-missing", { + "app": "spicetify" + }) + checked: Settings.data.templates.spicetify + enabled: ProgramCheckerService.spicetifyAvailable + opacity: ProgramCheckerService.spicetifyAvailable ? 1.0 : 0.6 + onToggled: checked => { + if (ProgramCheckerService.spicetifyAvailable) { + Settings.data.templates.spicetify = checked + AppThemeService.generate() + } + } + } + } // Miscellaneous NCollapsible { Layout.fillWidth: true diff --git a/Services/System/ProgramCheckerService.qml b/Services/System/ProgramCheckerService.qml index 13b2bc34..10a2eff0 100644 --- a/Services/System/ProgramCheckerService.qml +++ b/Services/System/ProgramCheckerService.qml @@ -26,6 +26,7 @@ Singleton { property bool app2unitAvailable: false property bool codeAvailable: false property bool gnomeCalendarAvailable: false + property bool spicetifyAvailable: false // Discord client auto-detection property var availableDiscordClients: [] @@ -112,7 +113,8 @@ Singleton { "gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"], "wlsunsetAvailable": ["which", "wlsunset"], "codeAvailable": ["which", "code"], - "gnomeCalendarAvailable": ["which", "gnome-calendar"] + "gnomeCalendarAvailable": ["which", "gnome-calendar"], + "spicetifyAvailable": ["which", "spicetify"] }) // Internal tracking diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index aa0b4a32..f249144e 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -151,6 +151,16 @@ Singleton { "outputs": [{ "path": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" }] + }, + { + "id": "spicetify", + "name": "Spicetify", + "category": "applications", + "input": "spicetify.ini", + "outputs": [{ + "path": "~/.config/spicetify/Themes/Comfy/color.ini" + }], + "postProcess": () => `spicetify -q apply --no-restart` }] // Extract Discord clients for ProgramCheckerService compatibility From 85ff0532fa17f57340139e7781734b2799bef0da Mon Sep 17 00:00:00 2001 From: Aiser <2912778691@qq.com> Date: Mon, 10 Nov 2025 14:39:54 +0800 Subject: [PATCH 074/107] Matugen: fix ghostty's posthook --- Services/Theming/TemplateRegistry.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index f249144e..ba09a558 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -21,7 +21,7 @@ Singleton { "name": "Ghostty", "matugenPath": "Terminal/ghostty", "outputPath": "~/.config/ghostty/themes/noctalia", - "postHook": "pkill -SIGUSR2 ghostty" + "postHook": "pgrep -x ghostty >/dev/null && pkill -SIGUSR2 ghostty; true" }, { "id": "kitty", "name": "Kitty", From 162c5febda0163fd102069a620d8dba4e562b4b5 Mon Sep 17 00:00:00 2001 From: Sighthesia Date: Mon, 10 Nov 2025 17:32:03 +0800 Subject: [PATCH 075/107] Background: add startup wallpaper transition --- Assets/settings-default.json | 1 + Commons/Settings.qml | 1 + Modules/Background/Background.qml | 59 ++++++++++++++++++- Modules/Panels/Settings/Tabs/WallpaperTab.qml | 9 +++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index add3d975..88e7c4fd 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -120,6 +120,7 @@ "randomIntervalSec": 300, "transitionDuration": 1500, "transitionType": "random", + "startupTransitionType": "disc", "transitionEdgeSmoothness": 0.05, "monitors": [], "panelPosition": "follow_bar" diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 3d7be7f1..861be375 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -258,6 +258,7 @@ Singleton { property int randomIntervalSec: 300 // 5 min property int transitionDuration: 1500 // 1500 ms property string transitionType: "random" + property string startupTransitionType: "disc" property real transitionEdgeSmoothness: 0.05 property list monitors: [] property string panelPosition: "follow_bar" diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index abea1806..40fd81ea 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -21,6 +21,7 @@ Variants { // Internal state management property string transitionType: "fade" property real transitionProgress: 0 + property bool isStartupTransition: true readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness readonly property var allTransitions: WallpaperService.allTransitions @@ -77,7 +78,11 @@ Variants { Connections { target: CompositorService function onDisplayScalesChanged() { - setWallpaperInitial() + // Recalculate image sizes without interrupting startup transition + if (isStartupTransition) { + return + } + recalculateImageSizes() } } @@ -360,7 +365,10 @@ Variants { return } - setWallpaperImmediate(WallpaperService.getWallpaper(modelData.name)) + const wallpaperPath = WallpaperService.getWallpaper(modelData.name) + + futureWallpaper = wallpaperPath + performStartupTransition() } // ------------------------------------------------------ @@ -453,6 +461,53 @@ Variants { break } } + + // ------------------------------------------------------ + // Dedicated function for startup animation + function performStartupTransition() { + // Get the transitionType from the settings + var transitionSettingType = Settings.data.wallpaper.startupTransitionType + + if (transitionSettingType == "random") { + var idx = Math.floor(Math.random() * allTransitions.length) + transitionSettingType = allTransitions[idx] + } + + // Ensure the transition type really exists + if (transitionSettingType !== "none" && !allTransitions.includes(transitionSettingType)) { + transitionSettingType = "fade" + } + + // Apply transitionType so the shader loader picks the correct shader + this.transitionType = transitionSettingType + + // Perform the chosen transition; for startup prefer centered disc when disc selected + switch (transitionSettingType) { + case "none": + setWallpaperImmediate(futureWallpaper) + break + case "wipe": + wipeDirection = Math.random() * 4 + setWallpaperWithTransition(futureWallpaper) + break + case "disc": + // Force center origin for elegant startup animation + discCenterX = 0.5 + discCenterY = 0.5 + setWallpaperWithTransition(futureWallpaper) + break + case "stripes": + stripesCount = Math.round(Math.random() * 20 + 4) + stripesAngle = Math.random() * 360 + setWallpaperWithTransition(futureWallpaper) + break + default: + setWallpaperWithTransition(futureWallpaper) + break + } + // Mark startup transition complete + isStartupTransition = false + } } } } diff --git a/Modules/Panels/Settings/Tabs/WallpaperTab.qml b/Modules/Panels/Settings/Tabs/WallpaperTab.qml index ec3995a0..fd128fea 100644 --- a/Modules/Panels/Settings/Tabs/WallpaperTab.qml +++ b/Modules/Panels/Settings/Tabs/WallpaperTab.qml @@ -205,6 +205,15 @@ ColumnLayout { onSelected: key => Settings.data.wallpaper.transitionType = key } + // Startup Transition Type + NComboBox { + label: I18n.tr("settings.wallpaper.look-feel.startup-transition-type.label") + description: I18n.tr("settings.wallpaper.look-feel.startup-transition-type.description") + model: WallpaperService.transitionsModel + currentKey: Settings.data.wallpaper.startupTransitionType + onSelected: key => Settings.data.wallpaper.startupTransitionType = key + } + // Transition Duration ColumnLayout { NLabel { From dc5292dadbdfb0012c18eeae07f0c5e0fa8aa8e8 Mon Sep 17 00:00:00 2001 From: Sighthesia Date: Mon, 10 Nov 2025 17:34:17 +0800 Subject: [PATCH 076/107] Background: optimize wallpaper source size calculation --- Modules/Background/Background.qml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 40fd81ea..8a44e49c 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -349,11 +349,23 @@ Variants { // ------------------------------------------------------ function recalculateImageSizes() { + // Re-evaluate and apply optimal sourceSize for both images when ready if (currentWallpaper.status === Image.Ready) { - currentWallpaper.calculateSourceSize() + const optimal = calculateOptimalWallpaperSize(currentWallpaper.implicitWidth, currentWallpaper.implicitHeight) + if (optimal !== undefined && optimal !== false) { + currentWallpaper.sourceSize = optimal + } else { + currentWallpaper.sourceSize = undefined + } } + if (nextWallpaper.status === Image.Ready) { - nextWallpaper.calculateSourceSize() + const optimal2 = calculateOptimalWallpaperSize(nextWallpaper.implicitWidth, nextWallpaper.implicitHeight) + if (optimal2 !== undefined && optimal2 !== false) { + nextWallpaper.sourceSize = optimal2 + } else { + nextWallpaper.sourceSize = undefined + } } } From 02d7a52dd710ad8c679693244ddd456028021ba4 Mon Sep 17 00:00:00 2001 From: Sighthesia Date: Mon, 10 Nov 2025 17:43:05 +0800 Subject: [PATCH 077/107] i18n: add ai translations of startup transition - Added "startup-transition-type" key to translations for multiple languages. - Updated descriptions and labels for the new transition type in German, English, Spanish, French, Portuguese, Turkish, Ukrainian, and Chinese. --- Assets/Translations/de.json | 6 +++++- Assets/Translations/en.json | 6 +++++- Assets/Translations/es.json | 6 +++++- Assets/Translations/fr.json | 8 ++++++-- Assets/Translations/pt.json | 6 +++++- Assets/Translations/tr.json | 6 +++++- Assets/Translations/uk-UA.json | 6 +++++- Assets/Translations/zh-CN.json | 6 +++++- 8 files changed, 41 insertions(+), 9 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f1be4512..7ffc3526 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1591,6 +1591,10 @@ "transition-type": { "description": "Animationstyp beim Wechsel zwischen Hintergrundbildern.", "label": "Übergangstyp" + }, + "startup-transition-type": { + "description": "Animationstyp der Hintergrundbildübergänge, der beim Start von Noctalia verwendet wird.", + "label": "Startup-Übergangstyp" } }, "settings": { @@ -1991,4 +1995,4 @@ "title": "WLAN" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index bd3e9820..686d1394 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1591,6 +1591,10 @@ "transition-type": { "description": "Animation type when switching between wallpapers.", "label": "Transition type" + }, + "startup-transition-type": { + "description": "Wallpaper transition animation type used when Noctalia starts.", + "label": "Startup transition type" } }, "settings": { @@ -1991,4 +1995,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index a68676bb..27b24540 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1591,6 +1591,10 @@ "transition-type": { "description": "Tipo de animación al cambiar entre fondos de pantalla.", "label": "Tipo de transición" + }, + "startup-transition-type": { + "description": "Tipo de animación de transición de fondo de pantalla que se usa cuando se inicia Noctalia.", + "label": "Tipo de transición al iniciar" } }, "settings": { @@ -1991,4 +1995,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index c4ca352f..c3cb4319 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1589,8 +1589,12 @@ "label": "Durée de la transition" }, "transition-type": { - "description": "Type d'animation lors du changement de fond d'écran.", + "description": "Type d'animation lors du changement de fonds d'écran.", "label": "Type de transition" + }, + "startup-transition-type": { + "description": "Type d'animation de transition de papier peint utilisée au démarrage de Noctalia.", + "label": "Type de transition au démarrage" } }, "settings": { @@ -1991,4 +1995,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 46d05463..0578eb75 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1591,6 +1591,10 @@ "transition-type": { "description": "Tipo de animação ao alternar entre papéis de parede.", "label": "Tipo de transição" + }, + "startup-transition-type": { + "description": "Tipo de animação de transição de papel de parede usado quando o Noctalia é iniciado.", + "label": "Tipo de transição na inicialização" } }, "settings": { @@ -1991,4 +1995,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 5aa2474f..5abbf692 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -1591,6 +1591,10 @@ "transition-type": { "description": "Duvar kağıtları arasında geçiş yaparken animasyon türü.", "label": "Geçiş türü" + }, + "startup-transition-type": { + "description": "Noctalia başlatıldığında kullanılan duvar kağıdı geçiş animasyonu türü.", + "label": "Başlangıç geçiş türü" } }, "settings": { @@ -1991,4 +1995,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index fbcead9d..bf294d22 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -1591,6 +1591,10 @@ "transition-type": { "description": "Тип анімації при перемиканні між шпалерами.", "label": "Тип переходу" + }, + "startup-transition-type": { + "description": "Тип анімації переходу шпалер, що використовується при запуску Noctalia.", + "label": "Тип переходу при запуску" } }, "settings": { @@ -1991,4 +1995,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 3b899fec..0db72330 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1591,6 +1591,10 @@ "transition-type": { "description": "切换壁纸时的动画类型。", "label": "过渡类型" + }, + "startup-transition-type": { + "description": "在 Noctalia 启动时使用的壁纸过渡动画类型。", + "label": "启动时过渡类型" } }, "settings": { @@ -1991,4 +1995,4 @@ "title": "Wi-Fi" } } -} +} \ No newline at end of file From 1df1e1119ea292a95626ea0d087bd86b2760c1ea Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Mon, 10 Nov 2025 18:32:23 +0800 Subject: [PATCH 078/107] fix(tray): import ui service --- Modules/Panels/Tray/TrayMenuPanel.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Panels/Tray/TrayMenuPanel.qml b/Modules/Panels/Tray/TrayMenuPanel.qml index 23ed1d74..2a3f31cb 100644 --- a/Modules/Panels/Tray/TrayMenuPanel.qml +++ b/Modules/Panels/Tray/TrayMenuPanel.qml @@ -5,6 +5,7 @@ import Quickshell import qs.Commons import qs.Widgets import qs.Modules.MainScreen +import qs.Services.UI SmartPanel { id: root From 110bbdf7a3da5b7705121f677e20ecc6678d6d45 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 5 Nov 2025 07:16:59 +0800 Subject: [PATCH 079/107] fix(custom button): Adjust the custom button text display when the bar is vertical --- Modules/Bar/Extras/BarPillVertical.qml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Modules/Bar/Extras/BarPillVertical.qml b/Modules/Bar/Extras/BarPillVertical.qml index ea1001a7..421d8402 100644 --- a/Modules/Bar/Extras/BarPillVertical.qml +++ b/Modules/Bar/Extras/BarPillVertical.qml @@ -48,8 +48,8 @@ Item { readonly property int pillHeight: buttonSize readonly property int pillPaddingVertical: 3 * 2 // Very precise adjustment don't replace by Style.margin readonly property int pillOverlap: Math.round(buttonSize * 0.5) - readonly property int maxPillWidth: buttonSize - readonly property int maxPillHeight: Math.max(1, Math.round(textItem.implicitHeight + pillPaddingVertical * 4)) + readonly property int maxPillWidth: Math.max(buttonSize, Math.round(textItem.implicitHeight + pillPaddingVertical * 2)) + readonly property int maxPillHeight: Math.max(1, Math.round(textItem.implicitWidth + pillPaddingVertical * 2 + Math.round(iconCircle.height / 4))) readonly property real iconSize: { switch (root.density) { @@ -109,14 +109,8 @@ Item { id: textItem anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: { - var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75) - if (forceOpen) { - // If its force open, the icon disc background is the same color as the bg pill move text slightly - offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS - } - return offset - } + anchors.verticalCenterOffset: Math.round(iconCircle.height / 4) + rotation: -90 text: root.text + root.suffix family: Settings.data.ui.fontFixed pointSize: textSize @@ -127,7 +121,6 @@ Item { color: forceOpen ? Color.mOnSurface : Color.mPrimary visible: revealed } - Behavior on width { enabled: showAnim.running || hideAnim.running NumberAnimation { From 2f853e3eca2f34cbbdcafccea8a2c34efce29c6d Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 5 Nov 2025 07:39:40 +0800 Subject: [PATCH 080/107] feat(bar): Add option to hide CustomButton text in vertical bar --- Assets/Translations/en.json | 4 ++++ Modules/Bar/Widgets/CustomButton.qml | 12 ++++++++---- .../Bar/WidgetSettings/CustomButtonSettings.qml | 9 +++++++++ Services/UI/BarWidgetRegistry.qml | 3 ++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 818438cc..e258290e 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -119,6 +119,10 @@ "stream-description": "Enter a command to run continuously." }, "dynamic-text": "Dynamic text", + "hide-vertical": { + "description": "If enabled, the text from the command output will not be shown when the bar is in a vertical layout (left or right).", + "label": "Hide text in vertical bar" + }, "icon": { "description": "Select an icon from the library.", "label": "Icon" diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 7e474fde..1d59f329 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -30,7 +30,8 @@ Item { return {} } - // Use settings or defaults from BarWidgetRegistry + readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec @@ -40,8 +41,11 @@ Item { readonly property int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000) readonly property string textCollapse: widgetSettings.textCollapse !== undefined ? widgetSettings.textCollapse : (widgetMetadata.textCollapse || "") readonly property bool parseJson: widgetSettings.parseJson !== undefined ? widgetSettings.parseJson : (widgetMetadata.parseJson || false) + readonly property bool hideTextInVerticalBar: widgetSettings.hideTextInVerticalBar !== undefined ? widgetSettings.hideTextInVerticalBar : (widgetMetadata.hideTextInVerticalBar || false) readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec) + readonly property bool shouldShowText: !isVerticalBar || !hideTextInVerticalBar + implicitWidth: pill.width implicitHeight: pill.height @@ -50,7 +54,7 @@ Item { oppositeDirection: BarService.getPillDirection(root) icon: _dynamicIcon !== "" ? _dynamicIcon : customIcon - text: _dynamicText + text: shouldShowText ? _dynamicText : "" density: Settings.data.bar.density autoHide: false forceOpen: _dynamicText !== "" @@ -86,7 +90,7 @@ Item { id: refreshTimer interval: Math.max(250, textIntervalMs) repeat: true - running: !textStream && textCommand && textCommand.length > 0 + running: shouldShowText && !textStream && textCommand && textCommand.length > 0 triggeredOnStart: true onTriggered: root.runTextCommand() } @@ -95,7 +99,7 @@ Item { Timer { id: restartTimer interval: 1000 - running: textStream && !textProc.running + running: shouldShowText && textStream && !textProc.running onTriggered: root.runTextCommand() } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml index 5d11c8de..715e4ef9 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml @@ -15,6 +15,7 @@ ColumnLayout { property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon property bool valueTextStream: widgetData.textStream !== undefined ? widgetData.textStream : widgetMetadata.textStream property bool valueParseJson: widgetData.parseJson !== undefined ? widgetData.parseJson : widgetMetadata.parseJson + property bool valueHideTextInVerticalBar: widgetData.hideTextInVerticalBar !== undefined ? widgetData.hideTextInVerticalBar : widgetMetadata.hideTextInVerticalBar function saveSettings() { var settings = Object.assign({}, widgetData || {}) @@ -26,6 +27,7 @@ ColumnLayout { settings.textCollapse = textCollapseInput.text settings.textStream = valueTextStream settings.parseJson = valueParseJson + settings.hideTextInVerticalBar = valueHideTextInVerticalBar settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10) return settings } @@ -94,6 +96,13 @@ ColumnLayout { label: I18n.tr("bar.widget-settings.custom-button.dynamic-text") } + NToggle { + label: I18n.tr("bar.widget-settings.custom-button.hide-vertical.label", "Hide text in vertical bar") + description: I18n.tr("bar.widget-settings.custom-button.hide-vertical.description", "If enabled, the text from the command output will not be shown when the bar is in a vertical layout (left or right).") + checked: valueHideTextInVerticalBar + onToggled: checked => valueHideTextInVerticalBar = checked + } + NToggle { id: textStreamInput label: I18n.tr("bar.widget-settings.custom-button.text-stream.label") diff --git a/Services/UI/BarWidgetRegistry.qml b/Services/UI/BarWidgetRegistry.qml index 5cd91190..89ac49c1 100644 --- a/Services/UI/BarWidgetRegistry.qml +++ b/Services/UI/BarWidgetRegistry.qml @@ -98,7 +98,8 @@ Singleton { "textStream": false, "textIntervalMs": 3000, "textCollapse": "", - "parseJson": false + "parseJson": false, + "hideTextInVerticalBar": false }, "KeyboardLayout": { "allowUserSettings": true, From 0c0e3533083e204ef3f675060861a390dc88ab60 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 5 Nov 2025 07:44:34 +0800 Subject: [PATCH 081/107] i18n(de): Translate CustomButton settings --- Assets/Translations/de.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a7f33d97..1354ab71 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -119,6 +119,10 @@ "stream-description": "Geben Sie einen Befehl ein, der kontinuierlich ausgeführt werden soll." }, "dynamic-text": "Dynamischer Text", + "hide-vertical": { + "description": "Wenn aktiviert, wird der Text aus der Befehlsausgabe nicht angezeigt, wenn sich die Leiste in einem vertikalen Layout befindet (links oder rechts).", + "label": "Text in vertikaler Leiste ausblenden" + }, "icon": { "description": "Symbol aus der Bibliothek auswählen.", "label": "Symbol" From ba38031b5fa1b28829a07b10a320c99c309f921b Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 5 Nov 2025 07:46:29 +0800 Subject: [PATCH 082/107] i18n(es): Translate CustomButton settings --- Assets/Translations/es.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index c5ad8291..36c0dbd8 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -119,6 +119,10 @@ "stream-description": "Introduce un comando para ejecutar continuamente." }, "dynamic-text": "Texto dinámico", + "hide-vertical": { + "description": "Si está activado, el texto de la salida del comando no se mostrará cuando la barra esté en un diseño vertical (izquierda o derecha).", + "label": "Ocultar texto en barra vertical" + }, "icon": { "description": "Selecciona un icono de la biblioteca.", "label": "Icono" From 0432ef7b9bbf1a4676292fe8a704b89cb942fded Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 5 Nov 2025 07:48:11 +0800 Subject: [PATCH 083/107] i18n(fr): Translate CustomButton settings --- Assets/Translations/fr.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 937d7299..be8c1eda 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -119,6 +119,10 @@ "stream-description": "Entrez une commande à exécuter en continu." }, "dynamic-text": "Texte dynamique", + "hide-vertical": { + "description": "Si activé, le texte de la sortie de la commande ne sera pas affiché lorsque la barre est en disposition verticale (gauche ou droite).", + "label": "Masquer le texte dans la barre verticale" + }, "icon": { "description": "Sélectionnez une icône dans la bibliothèque.", "label": "Icône" From 10b694596640195ea4e1d042efbc9658566eb918 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 5 Nov 2025 07:52:08 +0800 Subject: [PATCH 084/107] i18n(pt): Translate CustomButton settings --- Assets/Translations/pt.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index fc3283bf..081a52ba 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -119,6 +119,10 @@ "stream-description": "Insira um comando para executar continuamente." }, "dynamic-text": "Texto dinâmico", + "hide-vertical": { + "description": "Se ativado, o texto da saída do comando não será exibido quando a barra estiver em um layout vertical (esquerda ou direita).", + "label": "Ocultar texto na barra vertical" + }, "icon": { "description": "Selecione um ícone da biblioteca.", "label": "Ícone" From 97a9e5a184f857f8f19b58e186b64322d0ec8f3a Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 5 Nov 2025 07:53:48 +0800 Subject: [PATCH 085/107] i18n(tr): Translate CustomButton settings --- Assets/Translations/tr.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index e235b715..eafc3472 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -119,6 +119,10 @@ "stream-description": "Sürekli çalıştırılacak bir komut girin." }, "dynamic-text": "Dinamik metin", + "hide-vertical": { + "description": "Etkinleştirilirse, komut çıktısındaki metin, çubuk dikey düzende (sol veya sağ) olduğunda gösterilmeyecektir.", + "label": "Dikey çubukta metni gizle" + }, "icon": { "description": "Kütüphaneden bir ikon seçin.", "label": "İkon" From 8a5704ab824330f668eebf6daf8365b77e9751f8 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 5 Nov 2025 07:55:16 +0800 Subject: [PATCH 086/107] i18n(uk-UA): Translate CustomButton settings --- Assets/Translations/uk-UA.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index 009bf24d..3b35e3d6 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -119,6 +119,10 @@ "stream-description": "Введіть команду для безперервного запуску." }, "dynamic-text": "Динамічний текст", + "hide-vertical": { + "description": "Якщо увімкнено, текст з виводу команди не відображатиметься, коли панель знаходиться у вертикальному розташуванні (ліворуч або праворуч).", + "label": "Приховати текст у вертикальній панелі" + }, "icon": { "description": "Вибрати значок з бібліотеки.", "label": "Значок" From ebfd50b20ad47c6fd66e33c128bda68c6fddcecc Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 5 Nov 2025 07:56:13 +0800 Subject: [PATCH 087/107] i18n(zh-CN): Translate CustomButton settings --- Assets/Translations/zh-CN.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 71c9b99f..d0d2d154 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -119,6 +119,10 @@ "stream-description": "输入一个要持续运行的命令。" }, "dynamic-text": "动态文本", + "hide-vertical": { + "description": "如果启用,当栏处于垂直布局(左或右)时,将不显示命令输出的文本。", + "label": "在垂直栏中隐藏文本" + }, "icon": { "description": "从库中选择图标。", "label": "图标" From b37f4d4268bd90ca23ce95a4612241a753753377 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 8 Nov 2025 08:27:35 +0800 Subject: [PATCH 088/107] feat: Add dynamic tooltip parsing to CustomButton --- Modules/Bar/Widgets/CustomButton.qml | 55 ++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 1d59f329..741eb786 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -59,20 +59,31 @@ Item { autoHide: false forceOpen: _dynamicText !== "" tooltipText: { - if (!hasExec) { - return "Custom button, configure in settings." - } else { - var lines = [] + var tooltipLines = [] + + if (hasExec) { if (leftClickExec !== "") { - lines.push(`Left click: ${leftClickExec}.`) + tooltipLines.push(`Left click: ${leftClickExec}.`) } if (rightClickExec !== "") { - lines.push(`Right click: ${rightClickExec}.`) + tooltipLines.push(`Right click: ${rightClickExec}.`) } if (middleClickExec !== "") { - lines.push(`Middle click: ${middleClickExec}.`) + tooltipLines.push(`Middle click: ${middleClickExec}.`) } - return lines.join("\n") + } + + if (_dynamicTooltip !== "") { + if (tooltipLines.length > 0) { + tooltipLines.push("") + } + tooltipLines.push(_dynamicTooltip) + } + + if (tooltipLines.length === 0) { + return "Custom button, configure in settings." + } else { + return tooltipLines.join("\n") } } @@ -84,6 +95,7 @@ Item { // Internal state for dynamic text property string _dynamicText: "" property string _dynamicIcon: "" + property string _dynamicTooltip: "" // Periodically run the text command (if set) Timer { @@ -127,38 +139,49 @@ Item { function parseDynamicContent(content) { var contentStr = String(content || "").trim() - if (contentStr.indexOf("\n") !== -1) { - contentStr = contentStr.split("\n")[0] - } if (parseJson) { + var lineToParse = contentStr + + if (!textStream && contentStr.includes('\n')) { + const lines = contentStr.split('\n').filter(line => line.trim() !== '') + if (lines.length > 0) { + lineToParse = lines[lines.length - 1] + } + } + try { - var parsed = JSON.parse(contentStr) - var text = parsed.text || "" + const parsed = JSON.parse(lineToParse) + const text = parsed.text || "" + const icon = parsed.icon || "" + const tooltip = parsed.tooltip || "" if (checkCollapse(text)) { _dynamicText = "" _dynamicIcon = "" + _dynamicTooltip = "" return } _dynamicText = text - _dynamicIcon = parsed.icon || "" + _dynamicIcon = icon + _dynamicTooltip = tooltip return } catch (e) { - - // Not a valid JSON, treat as plain text + Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`) } } if (checkCollapse(contentStr)) { _dynamicText = "" _dynamicIcon = "" + _dynamicTooltip = "" return } _dynamicText = contentStr _dynamicIcon = "" + _dynamicTooltip = "" } function checkCollapse(text) { From 54dad12a70bf0ff00e212cac2a5872585f53fca0 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 8 Nov 2025 12:13:18 +0800 Subject: [PATCH 089/107] feat(bar): Add conditional text rotation for BarPillVertical --- Modules/Bar/Extras/BarPill.qml | 2 ++ Modules/Bar/Extras/BarPillVertical.qml | 17 +++++++++++++---- Modules/Bar/Widgets/CustomButton.qml | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Modules/Bar/Extras/BarPill.qml b/Modules/Bar/Extras/BarPill.qml index d5befb21..a8c097cf 100644 --- a/Modules/Bar/Extras/BarPill.qml +++ b/Modules/Bar/Extras/BarPill.qml @@ -17,6 +17,7 @@ Item { property bool forceClose: false property bool oppositeDirection: false property bool hovered: false + property bool rotateText: false readonly property string barPosition: Settings.data.bar.position readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" @@ -52,6 +53,7 @@ Item { oppositeDirection: root.oppositeDirection hovered: root.hovered density: root.density + rotateText: root.rotateText onShown: root.shown() onHidden: root.hidden() onEntered: root.entered() diff --git a/Modules/Bar/Extras/BarPillVertical.qml b/Modules/Bar/Extras/BarPillVertical.qml index 421d8402..ecd170f1 100644 --- a/Modules/Bar/Extras/BarPillVertical.qml +++ b/Modules/Bar/Extras/BarPillVertical.qml @@ -18,6 +18,7 @@ Item { property bool forceClose: false property bool oppositeDirection: false property bool hovered: false + property bool rotateText: false // Bar position detection for pill direction readonly property string barPosition: Settings.data.bar.position @@ -48,8 +49,8 @@ Item { readonly property int pillHeight: buttonSize readonly property int pillPaddingVertical: 3 * 2 // Very precise adjustment don't replace by Style.margin readonly property int pillOverlap: Math.round(buttonSize * 0.5) - readonly property int maxPillWidth: Math.max(buttonSize, Math.round(textItem.implicitHeight + pillPaddingVertical * 2)) - readonly property int maxPillHeight: Math.max(1, Math.round(textItem.implicitWidth + pillPaddingVertical * 2 + Math.round(iconCircle.height / 4))) + readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + pillPaddingVertical * 2)) : buttonSize + readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + pillPaddingVertical * 2 + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + pillPaddingVertical * 4)) readonly property real iconSize: { switch (root.density) { @@ -109,8 +110,8 @@ Item { id: textItem anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: Math.round(iconCircle.height / 4) - rotation: -90 + anchors.verticalCenterOffset: rotateText ? Math.round(iconCircle.height / 4) : getVerticalCenterOffset() + rotation: rotateText ? -90 : 0 text: root.text + root.suffix family: Settings.data.ui.fontFixed pointSize: textSize @@ -120,6 +121,14 @@ Item { verticalAlignment: Text.AlignVCenter color: forceOpen ? Color.mOnSurface : Color.mPrimary visible: revealed + + function getVerticalCenterOffset() { + var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75) + if (forceOpen) { + offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS + } + return offset + } } Behavior on width { enabled: showAnim.running || hideAnim.running diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 741eb786..3e67fb0c 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -56,6 +56,7 @@ Item { icon: _dynamicIcon !== "" ? _dynamicIcon : customIcon text: shouldShowText ? _dynamicText : "" density: Settings.data.bar.density + rotateText: isVerticalBar && !hideTextInVerticalBar autoHide: false forceOpen: _dynamicText !== "" tooltipText: { From 5ec97ac7501808cdfe9c4a0fa29057629a41a466 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 10 Nov 2025 12:32:00 +0100 Subject: [PATCH 090/107] ControlCenter: add missing import --- Modules/Bar/Widgets/ControlCenter.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index fc19e980..2d4e84cf 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -5,6 +5,7 @@ import QtQuick.Effects import qs.Commons import qs.Widgets import qs.Services.UI +import qs.Services.System NIconButton { id: root From c6bd84dfaa35be9625f468351b6a447847ec21c3 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 10 Nov 2025 12:47:49 +0100 Subject: [PATCH 091/107] ControlCenter: add colorize distro toggle --- Assets/Translations/de.json | 4 ++++ Assets/Translations/en.json | 4 ++++ Assets/Translations/es.json | 4 ++++ Assets/Translations/fr.json | 4 ++++ Assets/Translations/pt.json | 4 ++++ Assets/Translations/tr.json | 4 ++++ Assets/Translations/uk-UA.json | 4 ++++ Assets/Translations/zh-CN.json | 4 ++++ Modules/Bar/Widgets/ControlCenter.qml | 12 ++++++++++++ .../WidgetSettings/ControlCenterSettings.qml | 12 ++++++++++++ Modules/Panels/Settings/Tabs/ColorSchemeTab.qml | 8 ++++---- Services/Theming/TemplateRegistry.qml | 17 ++++++++--------- Services/UI/BarWidgetRegistry.qml | 3 ++- 13 files changed, 70 insertions(+), 14 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a7f33d97..823bedd6 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -105,6 +105,10 @@ "use-distro-logo": { "description": "Logo Ihrer Distribution anstelle eines benutzerdefinierten Symbols verwenden.", "label": "Distro-Logo anstelle von Symbol verwenden" + }, + "colorize-distro-logo": { + "description": "Wende die Designfarben auf das Logo deiner Distribution an.", + "label": "Distro-Logo einfärben" } }, "custom-button": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 818438cc..599e4afd 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -105,6 +105,10 @@ "use-distro-logo": { "description": "Use your distribution's logo instead of a custom icon.", "label": "Use distro logo instead of icon" + }, + "colorize-distro-logo": { + "description": "Apply theme colors to your distribution logo.", + "label": "Colorize distro logo" } }, "custom-button": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index c5ad8291..884938bc 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -105,6 +105,10 @@ "use-distro-logo": { "description": "Usar el logo de tu distribución en lugar de un icono personalizado.", "label": "Usar logo de la distribución en lugar de icono" + }, + "colorize-distro-logo": { + "description": "Aplica los colores del tema a tu logotipo de distribución.", + "label": "Colorear logotipo de la distribución" } }, "custom-button": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 937d7299..3ac75897 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -105,6 +105,10 @@ "use-distro-logo": { "description": "Utiliser le logo de votre distribution au lieu d'une icône personnalisée.", "label": "Utiliser le logo de la distribution au lieu de l'icône" + }, + "colorize-distro-logo": { + "description": "Appliquez les couleurs du thème au logo de votre distribution.", + "label": "Coloriser le logo de la distribution" } }, "custom-button": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index fc3283bf..8b63b951 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -105,6 +105,10 @@ "use-distro-logo": { "description": "Usar o logo da sua distribuição em vez de um ícone personalizado.", "label": "Usar logo da distribuição em vez de ícone" + }, + "colorize-distro-logo": { + "description": "Aplicar as cores do tema ao logotipo da sua distribuição.", + "label": "Colorir logo da distribuição" } }, "custom-button": { diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index e235b715..93dfa869 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -105,6 +105,10 @@ "use-distro-logo": { "description": "Özel ikon yerine dağıtımınızın logosunu kullanın.", "label": "Dağıtım logosunu ikon yerine kullan" + }, + "colorize-distro-logo": { + "description": "Dağıtım logonuza tema renklerini uygula.", + "label": "Dağıtım logosunu renklendir" } }, "custom-button": { diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index 009bf24d..b522d3ec 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -105,6 +105,10 @@ "use-distro-logo": { "description": "Використовувати логотип вашого дистрибутива замість власного значка.", "label": "Використовувати логотип дистрибутива замість значка" + }, + "colorize-distro-logo": { + "description": "Застосувати кольори теми до логотипа вашого дистрибутива.", + "label": "Розфарбовувати логотип дистрибутива" } }, "custom-button": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 71c9b99f..93faf8bf 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -105,6 +105,10 @@ "use-distro-logo": { "description": "使用您的发行版徽标而不是自定义图标。", "label": "使用发行版徽标而不是图标" + }, + "colorize-distro-logo": { + "description": "将主题颜色应用到您的发行版徽标。", + "label": "为发行版徽标着色" } }, "custom-button": { diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index 2d4e84cf..11daae13 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -32,6 +32,11 @@ NIconButton { readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon readonly property bool useDistroLogo: (widgetSettings.useDistroLogo !== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo readonly property string customIconPath: widgetSettings.customIconPath || "" + readonly property bool colorizeDistroLogo: { + if (widgetSettings.colorizeDistroLogo !== undefined) + return widgetSettings.colorizeDistroLogo + return widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false + } // If we have a custom path or distro logo, don't use the theme icon. icon: (customIconPath === "" && !useDistroLogo) ? customIcon : "" @@ -72,5 +77,12 @@ NIconButton { visible: source !== "" smooth: true asynchronous: true + layer.enabled: useDistroLogo && colorizeDistroLogo + layer.effect: ShaderEffect { + property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant + property real colorizeMode: 1.0 + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml index 043ec23c..1b423d9c 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml @@ -17,12 +17,14 @@ ColumnLayout { property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo property string valueCustomIconPath: widgetData.customIconPath !== undefined ? widgetData.customIconPath : "" + property bool valueColorizeDistroLogo: widgetData.colorizeDistroLogo !== undefined ? widgetData.colorizeDistroLogo : (widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false) function saveSettings() { var settings = Object.assign({}, widgetData || {}) settings.icon = valueIcon settings.useDistroLogo = valueUseDistroLogo settings.customIconPath = valueCustomIconPath + settings.colorizeDistroLogo = valueColorizeDistroLogo return settings } @@ -39,6 +41,16 @@ ColumnLayout { } } + NToggle { + visible: valueUseDistroLogo + label: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.label") + description: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.description") + checked: valueColorizeDistroLogo + onToggled: function (checked) { + valueColorizeDistroLogo = checked + } + } + RowLayout { spacing: Style.marginM diff --git a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml index 69df2e5a..d11e6e68 100644 --- a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml @@ -761,10 +761,10 @@ ColumnLayout { NCheckbox { label: "Spicetify" description: ProgramCheckerService.spicetifyAvailable ? I18n.tr("settings.color-scheme.templates.programs.spicetify.description", { - "filepath": "~/.config/spicetify/Themes/Comfy/color.ini" - }) : I18n.tr("settings.color-scheme.templates.programs.spicetify.description-missing", { - "app": "spicetify" - }) + "filepath": "~/.config/spicetify/Themes/Comfy/color.ini" + }) : I18n.tr("settings.color-scheme.templates.programs.spicetify.description-missing", { + "app": "spicetify" + }) checked: Settings.data.templates.spicetify enabled: ProgramCheckerService.spicetifyAvailable opacity: ProgramCheckerService.spicetifyAvailable ? 1.0 : 0.6 diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index ba09a558..0ebf7836 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -151,16 +151,15 @@ Singleton { "outputs": [{ "path": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" }] - }, - { - "id": "spicetify", - "name": "Spicetify", - "category": "applications", - "input": "spicetify.ini", - "outputs": [{ - "path": "~/.config/spicetify/Themes/Comfy/color.ini" + }, { + "id": "spicetify", + "name": "Spicetify", + "category": "applications", + "input": "spicetify.ini", + "outputs": [{ + "path": "~/.config/spicetify/Themes/Comfy/color.ini" }], - "postProcess": () => `spicetify -q apply --no-restart` + "postProcess": () => `spicetify -q apply --no-restart` }] // Extract Discord clients for ProgramCheckerService compatibility diff --git a/Services/UI/BarWidgetRegistry.qml b/Services/UI/BarWidgetRegistry.qml index 5cd91190..18aeb382 100644 --- a/Services/UI/BarWidgetRegistry.qml +++ b/Services/UI/BarWidgetRegistry.qml @@ -86,7 +86,8 @@ Singleton { "allowUserSettings": true, "useDistroLogo": false, "icon": "noctalia", - "customIconPath": "" + "customIconPath": "", + "colorizeDistroLogo": false }, "CustomButton": { "allowUserSettings": true, From bb8ab1f7ebe1b340a2180c8a93b306f59cf6e859 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 10 Nov 2025 12:56:01 +0100 Subject: [PATCH 092/107] SystemTrayService: add service to init tray faster (#689) --- Services/UI/SystemTrayService.qml | 40 +++++++++++++++++++++++++++++++ shell.qml | 2 ++ 2 files changed, 42 insertions(+) create mode 100644 Services/UI/SystemTrayService.qml diff --git a/Services/UI/SystemTrayService.qml b/Services/UI/SystemTrayService.qml new file mode 100644 index 00000000..4f2e74ce --- /dev/null +++ b/Services/UI/SystemTrayService.qml @@ -0,0 +1,40 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Services.SystemTray +import qs.Commons + + +/** + * SystemTrayService + * This service ensures that Quickshell's SystemTray service is initialized + * early in the shell startup to avoid programs that should stay in tray, not having access to one (let's hope this works). + */ +Singleton { + id: root + + property bool initialized: false + + Component.onCompleted: { + if (SystemTray && SystemTray.items) { + Logger.i("SystemTrayService", "SystemTray service initialized") + initialized = true + + // Monitor for tray items to confirm it's working + if (SystemTray.items.valuesChanged) { + Logger.d("SystemTrayService", "SystemTray is ready and monitoring for items") + } + } else { + Logger.w("SystemTrayService", "SystemTray service not available") + } + } + + function init() { + // Explicit initialization function + if (!initialized && SystemTray && SystemTray.items) { + Logger.i("SystemTrayService", "SystemTray service initialized via init()") + initialized = true + } + } +} diff --git a/shell.qml b/shell.qml index 33d6c8fd..35152cd7 100644 --- a/shell.qml +++ b/shell.qml @@ -9,6 +9,7 @@ // Qt & Quickshell Core import QtQuick import Quickshell +import Quickshell.Services.SystemTray // Commons & Services import qs.Commons @@ -68,6 +69,7 @@ ShellRoot { sourceComponent: Item { Component.onCompleted: { Logger.i("Shell", "---------------------------") + SystemTrayService.init() WallpaperService.init() AppThemeService.init() ColorSchemeService.init() From f24d2f758a50b00543e500ac001ecba085bb629b Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 10 Nov 2025 08:29:34 -0500 Subject: [PATCH 093/107] NImageCached: restored to its original glory. Warnings are expected and are acceptable. --- Widgets/NImageCached.qml | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/Widgets/NImageCached.qml b/Widgets/NImageCached.qml index 2645d479..82be77bb 100644 --- a/Widgets/NImageCached.qml +++ b/Widgets/NImageCached.qml @@ -31,9 +31,9 @@ Image { } onCachePathChanged: { if (imageHash && cachePath) { - // Check if cache file exists before trying to load it - cacheChecker.command = ["test", "-f", cachePath] - cacheChecker.running = true + // Try to load the cached version, failure will be detected below in onStatusChanged + // Failure is expected and warnings are ok in the console. Don't try to improve without consulting. + source = cachePath } } onStatusChanged: { @@ -49,19 +49,4 @@ Image { }) } } - - // Check if cache file exists to avoid warnings - Process { - id: cacheChecker - running: false - onExited: function (exitCode) { - if (exitCode === 0 && root.cachePath) { - // Cache file exists, load it - root.source = root.cachePath - } else if (root.imagePath) { - // Cache doesn't exist, load original directly - root.source = root.imagePath - } - } - } } From e76b3429d1ca9695f380be6b053a7747f4726a05 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 10 Nov 2025 08:35:36 -0500 Subject: [PATCH 094/107] Autoformatting --- Modules/Bar/Extras/BarPillVertical.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/Bar/Extras/BarPillVertical.qml b/Modules/Bar/Extras/BarPillVertical.qml index ecd170f1..5718c619 100644 --- a/Modules/Bar/Extras/BarPillVertical.qml +++ b/Modules/Bar/Extras/BarPillVertical.qml @@ -122,13 +122,13 @@ Item { color: forceOpen ? Color.mOnSurface : Color.mPrimary visible: revealed - function getVerticalCenterOffset() { - var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75) - if (forceOpen) { - offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS + function getVerticalCenterOffset() { + var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75) + if (forceOpen) { + offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS + } + return offset } - return offset - } } Behavior on width { enabled: showAnim.running || hideAnim.running From ff8e19d573ad83ee4820608f9ae1175efb9c9586 Mon Sep 17 00:00:00 2001 From: Sighthesia Date: Mon, 10 Nov 2025 21:48:23 +0800 Subject: [PATCH 095/107] Revert "i18n: add ai translations of startup transition" This reverts commit 02d7a52dd710ad8c679693244ddd456028021ba4. --- Assets/Translations/de.json | 6 +----- Assets/Translations/en.json | 6 +----- Assets/Translations/es.json | 6 +----- Assets/Translations/fr.json | 8 ++------ Assets/Translations/pt.json | 6 +----- Assets/Translations/tr.json | 6 +----- Assets/Translations/uk-UA.json | 6 +----- Assets/Translations/zh-CN.json | 6 +----- 8 files changed, 9 insertions(+), 41 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 7ffc3526..f1be4512 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1591,10 +1591,6 @@ "transition-type": { "description": "Animationstyp beim Wechsel zwischen Hintergrundbildern.", "label": "Übergangstyp" - }, - "startup-transition-type": { - "description": "Animationstyp der Hintergrundbildübergänge, der beim Start von Noctalia verwendet wird.", - "label": "Startup-Übergangstyp" } }, "settings": { @@ -1995,4 +1991,4 @@ "title": "WLAN" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 686d1394..bd3e9820 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1591,10 +1591,6 @@ "transition-type": { "description": "Animation type when switching between wallpapers.", "label": "Transition type" - }, - "startup-transition-type": { - "description": "Wallpaper transition animation type used when Noctalia starts.", - "label": "Startup transition type" } }, "settings": { @@ -1995,4 +1991,4 @@ "title": "Wi-Fi" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 27b24540..a68676bb 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1591,10 +1591,6 @@ "transition-type": { "description": "Tipo de animación al cambiar entre fondos de pantalla.", "label": "Tipo de transición" - }, - "startup-transition-type": { - "description": "Tipo de animación de transición de fondo de pantalla que se usa cuando se inicia Noctalia.", - "label": "Tipo de transición al iniciar" } }, "settings": { @@ -1995,4 +1991,4 @@ "title": "Wi-Fi" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index c3cb4319..c4ca352f 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1589,12 +1589,8 @@ "label": "Durée de la transition" }, "transition-type": { - "description": "Type d'animation lors du changement de fonds d'écran.", + "description": "Type d'animation lors du changement de fond d'écran.", "label": "Type de transition" - }, - "startup-transition-type": { - "description": "Type d'animation de transition de papier peint utilisée au démarrage de Noctalia.", - "label": "Type de transition au démarrage" } }, "settings": { @@ -1995,4 +1991,4 @@ "title": "Wi-Fi" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 0578eb75..46d05463 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1591,10 +1591,6 @@ "transition-type": { "description": "Tipo de animação ao alternar entre papéis de parede.", "label": "Tipo de transição" - }, - "startup-transition-type": { - "description": "Tipo de animação de transição de papel de parede usado quando o Noctalia é iniciado.", - "label": "Tipo de transição na inicialização" } }, "settings": { @@ -1995,4 +1991,4 @@ "title": "Wi-Fi" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 5abbf692..5aa2474f 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -1591,10 +1591,6 @@ "transition-type": { "description": "Duvar kağıtları arasında geçiş yaparken animasyon türü.", "label": "Geçiş türü" - }, - "startup-transition-type": { - "description": "Noctalia başlatıldığında kullanılan duvar kağıdı geçiş animasyonu türü.", - "label": "Başlangıç geçiş türü" } }, "settings": { @@ -1995,4 +1991,4 @@ "title": "Wi-Fi" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index bf294d22..fbcead9d 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -1591,10 +1591,6 @@ "transition-type": { "description": "Тип анімації при перемиканні між шпалерами.", "label": "Тип переходу" - }, - "startup-transition-type": { - "description": "Тип анімації переходу шпалер, що використовується при запуску Noctalia.", - "label": "Тип переходу при запуску" } }, "settings": { @@ -1995,4 +1991,4 @@ "title": "Wi-Fi" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 0db72330..3b899fec 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1591,10 +1591,6 @@ "transition-type": { "description": "切换壁纸时的动画类型。", "label": "过渡类型" - }, - "startup-transition-type": { - "description": "在 Noctalia 启动时使用的壁纸过渡动画类型。", - "label": "启动时过渡类型" } }, "settings": { @@ -1995,4 +1991,4 @@ "title": "Wi-Fi" } } -} \ No newline at end of file +} From 4801716c368cd8a748439899b0f909e739ae65b1 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 10 Nov 2025 14:52:20 +0100 Subject: [PATCH 096/107] LockScreen: add missing import (fixes #699) --- Modules/LockScreen/LockScreen.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 92a99941..ceb832f5 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -12,6 +12,7 @@ import qs.Commons import qs.Services.Hardware import qs.Services.Location import qs.Services.Media +import qs.Services.Compositor import qs.Services.UI import qs.Widgets import qs.Widgets.AudioSpectrum From 71ad86dd6fc190c21b9d3557b567e15e42c6a527 Mon Sep 17 00:00:00 2001 From: Sighthesia Date: Mon, 10 Nov 2025 22:03:57 +0800 Subject: [PATCH 097/107] Background: remove the setting of startup wallpaper transition --- Assets/settings-default.json | 1 - Commons/Settings.qml | 1 - Modules/Background/Background.qml | 17 ++++++++--------- Modules/Panels/Settings/Tabs/WallpaperTab.qml | 9 --------- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 88e7c4fd..add3d975 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -120,7 +120,6 @@ "randomIntervalSec": 300, "transitionDuration": 1500, "transitionType": "random", - "startupTransitionType": "disc", "transitionEdgeSmoothness": 0.05, "monitors": [], "panelPosition": "follow_bar" diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 861be375..3d7be7f1 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -258,7 +258,6 @@ Singleton { property int randomIntervalSec: 300 // 5 min property int transitionDuration: 1500 // 1500 ms property string transitionType: "random" - property string startupTransitionType: "disc" property real transitionEdgeSmoothness: 0.05 property list monitors: [] property string panelPosition: "follow_bar" diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 8a44e49c..14907cc7 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -478,23 +478,22 @@ Variants { // Dedicated function for startup animation function performStartupTransition() { // Get the transitionType from the settings - var transitionSettingType = Settings.data.wallpaper.startupTransitionType + transitionType = Settings.data.wallpaper.transitionType - if (transitionSettingType == "random") { - var idx = Math.floor(Math.random() * allTransitions.length) - transitionSettingType = allTransitions[idx] + if (transitionType == "random") { + var index = Math.floor(Math.random() * allTransitions.length) + transitionType = allTransitions[index] } // Ensure the transition type really exists - if (transitionSettingType !== "none" && !allTransitions.includes(transitionSettingType)) { - transitionSettingType = "fade" + if (transitionType !== "none" && !allTransitions.includes(transitionType)) { + transitionType = "fade" } // Apply transitionType so the shader loader picks the correct shader - this.transitionType = transitionSettingType + this.transitionType = transitionType - // Perform the chosen transition; for startup prefer centered disc when disc selected - switch (transitionSettingType) { + switch (transitionType) { case "none": setWallpaperImmediate(futureWallpaper) break diff --git a/Modules/Panels/Settings/Tabs/WallpaperTab.qml b/Modules/Panels/Settings/Tabs/WallpaperTab.qml index fd128fea..ec3995a0 100644 --- a/Modules/Panels/Settings/Tabs/WallpaperTab.qml +++ b/Modules/Panels/Settings/Tabs/WallpaperTab.qml @@ -205,15 +205,6 @@ ColumnLayout { onSelected: key => Settings.data.wallpaper.transitionType = key } - // Startup Transition Type - NComboBox { - label: I18n.tr("settings.wallpaper.look-feel.startup-transition-type.label") - description: I18n.tr("settings.wallpaper.look-feel.startup-transition-type.description") - model: WallpaperService.transitionsModel - currentKey: Settings.data.wallpaper.startupTransitionType - onSelected: key => Settings.data.wallpaper.startupTransitionType = key - } - // Transition Duration ColumnLayout { NLabel { From c40e39708b32e72f64ee4abe68d5d210351685a4 Mon Sep 17 00:00:00 2001 From: onelocked Date: Mon, 10 Nov 2025 14:22:16 +0000 Subject: [PATCH 098/107] Fixing Matugen Ghostty posthook Ensure that the command runs in a Bash shell (fixes for people running nu shell) pgrep -f matches the entire command line, so it catches variants like .ghostty-wrapped. --- Services/Theming/TemplateRegistry.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index 0ebf7836..ebb67a01 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -21,7 +21,7 @@ Singleton { "name": "Ghostty", "matugenPath": "Terminal/ghostty", "outputPath": "~/.config/ghostty/themes/noctalia", - "postHook": "pgrep -x ghostty >/dev/null && pkill -SIGUSR2 ghostty; true" + "postHook": "bash -c 'pgrep -f ghostty >/dev/null && pkill -SIGUSR2 ghostty || true'" }, { "id": "kitty", "name": "Kitty", From e3aa6e3429a8ea82c4cc4e2dd78f56479aa04e6f Mon Sep 17 00:00:00 2001 From: Aiser <2912778691@qq.com> Date: Mon, 10 Nov 2025 22:30:22 +0800 Subject: [PATCH 099/107] Matugen: fix spicetify theme work for predefined colors && fix `hex_stripped` does not work --- Assets/MatugenTemplates/spicetify.ini | 52 +++++++++++++------------- Services/Theming/TemplateProcessor.qml | 18 ++++++--- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Assets/MatugenTemplates/spicetify.ini b/Assets/MatugenTemplates/spicetify.ini index bff30bf4..55c68652 100644 --- a/Assets/MatugenTemplates/spicetify.ini +++ b/Assets/MatugenTemplates/spicetify.ini @@ -1,27 +1,27 @@ [Comfy] -text = {{ colors.on_surface.default.hex_stripped }} -subtext = {{ colors.on_surface_variant.default.hex_stripped }} -main = {{ colors.surface.default.hex_stripped }} -main-elevated = {{ colors.surface_bright.default.hex_stripped }} -main-transition = {{ colors.surface_container_lowest.default.hex_stripped }} -highlight = {{ colors.surface_container_low.default.hex_stripped }} -highlight-elevated = {{ colors.surface_container_highest.default.hex_stripped }} -sidebar = {{ colors.surface.default.hex_stripped }} -player = {{ colors.scrim.default.hex_stripped }} -card = {{ colors.scrim.default.hex_stripped }} -shadow = {{ colors.scrim.default.hex_stripped }} -selected-row = {{ colors.on_surface.default.hex_stripped }} -button = {{ colors.primary.default.hex_stripped }} -button-active = {{ colors.primary_fixed.default.hex_stripped }} -button-disabled = {{ colors.primary_fixed_dim.default.hex_stripped }} -tab-active = {{ colors.surface.default.hex_stripped }} -notification = {{ colors.tertiary.default.hex_stripped }} -notification-error = {{ colors.error.default.hex_stripped }} -misc = {{ colors.scrim.default.hex_stripped }} -play-button = {{ colors.secondary.default.hex_stripped }} -play-button-active = {{ colors.secondary_fixed.default.hex_stripped }} -progress-fg = {{ colors.primary.default.hex_stripped }} -progress-bg = {{ colors.surface.default.hex_stripped }} -heart = {{ colors.error.default.hex_stripped }} -pagelink-active = {{ colors.on_tertiary_container.default.hex_stripped }} -radio-btn-active = {{ colors.on_tertiary_container.default.hex_stripped }} +text = {{colors.on_surface.default.hex_stripped}} +subtext = {{colors.on_surface_variant.default.hex_stripped}} +main = {{colors.surface.default.hex_stripped}} +main-elevated = {{colors.surface.default.hex_stripped}} +main-transition = {{colors.surface_container_lowest.default.hex_stripped}} +highlight = {{colors.surface_container_low.default.hex_stripped}} +highlight-elevated = {{colors.surface_container_highest.default.hex_stripped}} +sidebar = {{colors.surface.default.hex_stripped}} +player = {{colors.surface.default.hex_stripped}} +card = {{colors.surface.default.hex_stripped}} +shadow = {{colors.surface.default.hex_stripped}} +selected-row = {{colors.on_surface.default.hex_stripped}} +button = {{colors.primary.default.hex_stripped}} +button-active = {{colors.primary.default.hex_stripped}} +button-disabled = {{colors.primary.default.hex_stripped}} +tab-active = {{colors.surface.default.hex_stripped}} +notification = {{colors.tertiary.default.hex_stripped}} +notification-error = {{colors.error.default.hex_stripped}} +misc = {{colors.surface.default.hex_stripped}} +play-button = {{colors.secondary.default.hex_stripped}} +play-button-active = {{colors.secondary.default.hex_stripped}} +progress-fg = {{colors.primary.default.hex_stripped}} +progress-bg = {{colors.surface.default.hex_stripped}} +heart = {{colors.error.default.hex_stripped}} +pagelink-active = {{colors.on_tertiary_container.default.hex_stripped}} +radio-btn-active = {{colors.on_tertiary_container.default.hex_stripped}} diff --git a/Services/Theming/TemplateProcessor.qml b/Services/Theming/TemplateProcessor.qml index 2261e95d..07595d99 100644 --- a/Services/Theming/TemplateProcessor.qml +++ b/Services/Theming/TemplateProcessor.qml @@ -222,14 +222,20 @@ Singleton { } function replaceColorsInFile(filePath, colors) { - // This handles both ".hex" and ".hex_stripped" the EXACT same way. Our predefined color schemes are - // always RRGGBB without alpha so this is fine and keeps compatibility with matugen. let script = "" Object.keys(colors).forEach(colorKey => { - const colorValue = colors[colorKey].default.hex - const escapedColor = colorValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex\\(_stripped\\)\\?}}/${escapedColor}/g' '${filePath}'\n` - }) + const hexValue = colors[colorKey].default.hex + const hexStrippedValue = colors[colorKey].default.hex_stripped + + const escapedHex = hexValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const escapedHexStripped = hexStrippedValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + + // replace hex_stripped + script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex_stripped}}/${escapedHexStripped}/g' '${filePath}'\n` + + // replace hex + script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex}}/${escapedHex}/g' '${filePath}'\n` + }) return script } From 141f971a1da28049d8cd3609e0b18459aa07aa79 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 10 Nov 2025 10:03:12 -0500 Subject: [PATCH 100/107] Notifications: Fixed Toast when toggling DND --- Modules/Toast/ToastScreen.qml | 4 ++-- Services/System/NotificationService.qml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/Toast/ToastScreen.qml b/Modules/Toast/ToastScreen.qml index 0fd2d91c..e782a3ce 100644 --- a/Modules/Toast/ToastScreen.qml +++ b/Modules/Toast/ToastScreen.qml @@ -43,11 +43,11 @@ Item { function enqueueToast(toastData) { // Safe logging - fix the substring bug var descPreview = (toastData.description || "").substring(0, 100).replace(/\n/g, " ") - Logger.i("ToastScreen", "Queuing", toastData.type, ":", toastData.message, descPreview) + Logger.d("ToastScreen", "Queuing", toastData.type, ":", toastData.message, descPreview) // Bounded queue to prevent unbounded memory growth if (messageQueue.length >= maxQueueSize) { - Logger.i("ToastScreen", "Queue full, dropping oldest toast") + Logger.d("ToastScreen", "Queue full, dropping oldest toast") messageQueue.shift() } diff --git a/Services/System/NotificationService.qml b/Services/System/NotificationService.qml index 1442e5fb..dcc78f20 100644 --- a/Services/System/NotificationService.qml +++ b/Services/System/NotificationService.qml @@ -7,6 +7,7 @@ import Quickshell.Io import Quickshell.Services.Notifications import qs.Commons import qs.Services.Power +import qs.Services.UI import "../../Helpers/sha256.js" as Checksum Singleton { From 8c01158bad3ee6531eddb64a5f8fa6d563691467 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 10 Nov 2025 10:03:23 -0500 Subject: [PATCH 101/107] Autoformat --- Services/Theming/TemplateProcessor.qml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Services/Theming/TemplateProcessor.qml b/Services/Theming/TemplateProcessor.qml index 07595d99..c1f6ae33 100644 --- a/Services/Theming/TemplateProcessor.qml +++ b/Services/Theming/TemplateProcessor.qml @@ -224,18 +224,18 @@ Singleton { function replaceColorsInFile(filePath, colors) { let script = "" Object.keys(colors).forEach(colorKey => { - const hexValue = colors[colorKey].default.hex - const hexStrippedValue = colors[colorKey].default.hex_stripped - - const escapedHex = hexValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - const escapedHexStripped = hexStrippedValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - - // replace hex_stripped - script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex_stripped}}/${escapedHexStripped}/g' '${filePath}'\n` - - // replace hex - script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex}}/${escapedHex}/g' '${filePath}'\n` - }) + const hexValue = colors[colorKey].default.hex + const hexStrippedValue = colors[colorKey].default.hex_stripped + + const escapedHex = hexValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const escapedHexStripped = hexStrippedValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + + // replace hex_stripped + script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex_stripped}}/${escapedHexStripped}/g' '${filePath}'\n` + + // replace hex + script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex}}/${escapedHex}/g' '${filePath}'\n` + }) return script } From b81ce9819dc9959eb6e487a2a75e57dcf494f49b Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 10 Nov 2025 10:09:39 -0500 Subject: [PATCH 102/107] Default settings: added spicetify + autoformatting --- Assets/settings-default.json | 1 + Modules/Background/Background.qml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index add3d975..27dd0e3b 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -262,6 +262,7 @@ "vicinae": false, "walker": false, "code": false, + "spicetify": false, "enableUserTemplates": false, "discord_vesktop": false, "discord_webcord": false, diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 14907cc7..4cf8446b 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -378,7 +378,7 @@ Variants { } const wallpaperPath = WallpaperService.getWallpaper(modelData.name) - + futureWallpaper = wallpaperPath performStartupTransition() } From 97e24ac9b3bbe1ddfd81694d85d2e053aca3b681 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 10 Nov 2025 10:30:02 -0500 Subject: [PATCH 103/107] Compositor: Fix lockAndSuspend - missing import --- Services/Compositor/CompositorService.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Services/Compositor/CompositorService.qml b/Services/Compositor/CompositorService.qml index 17275b34..0ce10ffa 100644 --- a/Services/Compositor/CompositorService.qml +++ b/Services/Compositor/CompositorService.qml @@ -4,6 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons +import qs.Services.UI Singleton { id: root From 5402ae32f54d014355a7c5c3a6db775cd152e04b Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 10 Nov 2025 17:12:14 +0100 Subject: [PATCH 104/107] Notification: make doNotDisturb non persistent (fixes #665) --- Assets/settings-default.json | 1 - Commons/Settings.qml | 1 - Modules/Bar/Widgets/NotificationHistory.qml | 6 +++--- .../Panels/ControlCenter/Widgets/Notifications.qml | 6 +++--- .../NotificationHistory/NotificationHistoryPanel.qml | 6 +++--- Modules/Panels/Settings/Tabs/NotificationsTab.qml | 4 ++-- Services/Control/IPCService.qml | 6 +++--- Services/System/NotificationService.qml | 12 +++++------- 8 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 27dd0e3b..94b3dfe2 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -206,7 +206,6 @@ }, "notifications": { "enabled": true, - "doNotDisturb": false, "monitors": [], "location": "top_right", "overlayLayer": true, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 9600871b..81063cf2 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -342,7 +342,6 @@ Singleton { // notifications property JsonObject notifications: JsonObject { property bool enabled: true - property bool doNotDisturb: false property list monitors: [] property string location: "top_right" property bool overlayLayer: true diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index dbd9bea9..1dd21717 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -48,8 +48,8 @@ NIconButton { baseSize: Style.capsuleHeight applyUiScale: false density: Settings.data.bar.density - icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell" - tooltipText: Settings.data.notifications.doNotDisturb ? I18n.tr("tooltips.open-notification-history-disable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd") + icon: NotificationService.doNotDisturb ? "bell-off" : "bell" + tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.open-notification-history-disable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd") tooltipDirection: BarService.getTooltipDirection() colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) colorFg: Color.mOnSurface @@ -61,7 +61,7 @@ NIconButton { panel?.toggle(this) } - onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb + onRightClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb Loader { anchors.right: parent.right diff --git a/Modules/Panels/ControlCenter/Widgets/Notifications.qml b/Modules/Panels/ControlCenter/Widgets/Notifications.qml index 54a8d4fc..1877130e 100644 --- a/Modules/Panels/ControlCenter/Widgets/Notifications.qml +++ b/Modules/Panels/ControlCenter/Widgets/Notifications.qml @@ -8,9 +8,9 @@ import qs.Widgets NIconButtonHot { property ShellScreen screen - icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell" - hot: Settings.data.notifications.doNotDisturb + icon: NotificationService.doNotDisturb ? "bell-off" : "bell" + hot: NotificationService.doNotDisturb tooltipText: I18n.tr("quickSettings.notifications.tooltip.action") onClicked: PanelService.getPanel("notificationHistoryPanel", screen)?.toggle(this) - onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb + onRightClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb } diff --git a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml index 53faaa8a..d0833879 100644 --- a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml +++ b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml @@ -56,10 +56,10 @@ SmartPanel { } NIconButton { - icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell" - tooltipText: Settings.data.notifications.doNotDisturb ? I18n.tr("tooltips.do-not-disturb-enabled") : I18n.tr("tooltips.do-not-disturb-disabled") + icon: NotificationService.doNotDisturb ? "bell-off" : "bell" + tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.do-not-disturb-enabled") : I18n.tr("tooltips.do-not-disturb-disabled") baseSize: Style.baseWidgetSize * 0.8 - onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb + onClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb } NIconButton { diff --git a/Modules/Panels/Settings/Tabs/NotificationsTab.qml b/Modules/Panels/Settings/Tabs/NotificationsTab.qml index 6c0a9e37..189e7038 100644 --- a/Modules/Panels/Settings/Tabs/NotificationsTab.qml +++ b/Modules/Panels/Settings/Tabs/NotificationsTab.qml @@ -42,8 +42,8 @@ ColumnLayout { NToggle { label: I18n.tr("settings.notifications.settings.do-not-disturb.label") description: I18n.tr("settings.notifications.settings.do-not-disturb.description") - checked: Settings.data.notifications.doNotDisturb - onToggled: checked => Settings.data.notifications.doNotDisturb = checked + checked: NotificationService.doNotDisturb + onToggled: checked => NotificationService.doNotDisturb = checked } NComboBox { diff --git a/Services/Control/IPCService.qml b/Services/Control/IPCService.qml index 6da76420..eea7eea0 100644 --- a/Services/Control/IPCService.qml +++ b/Services/Control/IPCService.qml @@ -62,13 +62,13 @@ Item { }) } function toggleDND() { - Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb + NotificationService.doNotDisturb = !NotificationService.doNotDisturb } function enableDND() { - Settings.data.notifications.doNotDisturb = true + NotificationService.doNotDisturb = true } function disableDND() { - Settings.data.notifications.doNotDisturb = false + NotificationService.doNotDisturb = false } function clear() { NotificationService.clearHistory() diff --git a/Services/System/NotificationService.qml b/Services/System/NotificationService.qml index dcc78f20..277938a1 100644 --- a/Services/System/NotificationService.qml +++ b/Services/System/NotificationService.qml @@ -21,6 +21,8 @@ Singleton { // State property real lastSeenTs: 0 + // Volatile property that doesn't persist to settings (similar to noctaliaPerformanceMode) + property bool doNotDisturb: false // Models property ListModel activeList: ListModel {} @@ -161,7 +163,7 @@ Singleton { const data = createData(notification) addToHistory(data) - if (Settings.data.notifications?.doNotDisturb || PowerProfileService.noctaliaPerformanceMode) + if (root.doNotDisturb || PowerProfileService.noctaliaPerformanceMode) return // Check if this is a replacement notification @@ -728,11 +730,7 @@ Singleton { // Signals signal animateAndRemove(string notificationId) - Connections { - target: Settings.data.notifications - function onDoNotDisturbChanged() { - const enabled = Settings.data.notifications.doNotDisturb - ToastService.showNotice(enabled ? I18n.tr("toast.do-not-disturb.enabled") : I18n.tr("toast.do-not-disturb.disabled"), enabled ? I18n.tr("toast.do-not-disturb.enabled-desc") : I18n.tr("toast.do-not-disturb.disabled-desc"), enabled ? "bell-off" : "bell") - } + onDoNotDisturbChanged: { + ToastService.showNotice(doNotDisturb ? I18n.tr("toast.do-not-disturb.enabled") : I18n.tr("toast.do-not-disturb.disabled"), doNotDisturb ? I18n.tr("toast.do-not-disturb.enabled-desc") : I18n.tr("toast.do-not-disturb.disabled-desc"), doNotDisturb ? "bell-off" : "bell") } } From 20e10860606a759362cd64c4d93138e9760048ce Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 10 Nov 2025 17:17:31 +0100 Subject: [PATCH 105/107] Launcher: add CTRL+J/N keybinds (fixes #694) --- Modules/MainScreen/MainScreen.qml | 20 ++++++++++++++++++++ Modules/Panels/Launcher/Launcher.qml | 8 ++++++++ 2 files changed, 28 insertions(+) diff --git a/Modules/MainScreen/MainScreen.qml b/Modules/MainScreen/MainScreen.qml index c01b7553..6586c59c 100644 --- a/Modules/MainScreen/MainScreen.qml +++ b/Modules/MainScreen/MainScreen.qml @@ -554,6 +554,26 @@ PanelWindow { } } + Shortcut { + sequence: "Ctrl+N" + enabled: root.isPanelOpen + onActivated: { + if (PanelService.openedPanel && PanelService.openedPanel.onCtrlNPressed) { + PanelService.openedPanel.onCtrlNPressed() + } + } + } + + Shortcut { + sequence: "Ctrl+P" + enabled: root.isPanelOpen + onActivated: { + if (PanelService.openedPanel && PanelService.openedPanel.onCtrlPPressed) { + PanelService.openedPanel.onCtrlPPressed() + } + } + } + Shortcut { sequence: "Left" enabled: root.isPanelOpen diff --git a/Modules/Panels/Launcher/Launcher.qml b/Modules/Panels/Launcher/Launcher.qml index 7cc1248a..9747b3ee 100644 --- a/Modules/Panels/Launcher/Launcher.qml +++ b/Modules/Panels/Launcher/Launcher.qml @@ -97,6 +97,14 @@ SmartPanel { selectPreviousWrapped() } + function onCtrlNPressed() { + selectNextWrapped() + } + + function onCtrlPPressed() { + selectPreviousWrapped() + } + // Public API for plugins function setSearchText(text) { searchText = text From c290869869ae791396544bd9f41bef78611595da Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 10 Nov 2025 11:20:11 -0500 Subject: [PATCH 106/107] OSD: reduced main axis size to 75% --- Modules/OSD/OSD.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index d3458f2e..248f6ac8 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -139,10 +139,10 @@ Variants { readonly property bool isRight: (location.indexOf("_right") >= 0) || (location === "right") readonly property bool isCentered: (location === "top" || location === "bottom") readonly property bool verticalMode: (location === "left" || location === "right") - readonly property int hWidth: Math.round(420 * Style.uiScaleRatio) + readonly property int hWidth: Math.round(320 * Style.uiScaleRatio) readonly property int hHeight: Math.round(72 * Style.uiScaleRatio) - readonly property int vWidth: Math.round(84 * Style.uiScaleRatio) - readonly property int vHeight: Math.round(380 * Style.uiScaleRatio) + readonly property int vWidth: Math.round(72 * Style.uiScaleRatio) + readonly property int vHeight: Math.round(280 * Style.uiScaleRatio) // Ensure an even width to keep the vertical bar perfectly centered readonly property int barThickness: { From 84eaa98dd6f7551e604a87614d4a64bb8d1b1cb9 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 10 Nov 2025 17:30:31 +0100 Subject: [PATCH 107/107] Update PR template --- .github/pull_request_template.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 757038cc..8b1ad10a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -15,7 +15,10 @@ Mark the relevant option with an "x". ## Testing Describe how you tested your changes and mark the relevant items. -- [ ] Tested on compositor: (e.g., Hyprland, Sway, Niri) + +- [ ] Tested on niri +- [ ] Tested on Hyprland +- [ ] Tested on sway - [ ] Tested with different bar positions and density settings - [ ] Tested with multiple monitors (if applicable)