From ebd0bdd37090827f1046e3b928771d29b8ec7f1a Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Wed, 15 Oct 2025 11:24:26 -0400 Subject: [PATCH 001/109] feat: Add option to load control center panel in the center of the screen --- Modules/ControlCenter/ControlCenterPanel.qml | 4 ++-- Modules/Settings/Tabs/ControlCenterTab.qml | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 599dddb6..45cf6a9c 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -46,8 +46,8 @@ NPanel { // Positioning readonly property string controlCenterPosition: Settings.data.controlCenter.position - panelAnchorHorizontalCenter: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_center") - panelAnchorVerticalCenter: false + panelAnchorHorizontalCenter: controlCenterPosition !== "close_to_bar_button" && (controlCenterPosition.endsWith("_center") || controlCenterPosition === "center") + panelAnchorVerticalCenter: controlCenterPosition === "center" panelAnchorLeft: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_left") panelAnchorRight: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_right") panelAnchorBottom: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.startsWith("bottom_") diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml index 4bf62cfb..5fbbbf9e 100644 --- a/Modules/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -143,6 +143,9 @@ ColumnLayout { }, { "key": "top_center", "name": I18n.tr("options.control-center.position.top_center") + }, { + "key": "center", + "name": I18n.tr("options.control-center.position.center") }] currentKey: Settings.data.controlCenter.position onSelected: function (key) { From 4cf79d9ed7f9633b8369235f3c268c9ab1831246 Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Wed, 15 Oct 2025 11:27:56 -0400 Subject: [PATCH 002/109] Include english string --- Assets/Translations/en.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 7f6cf5c7..9052de86 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1305,7 +1305,8 @@ "bottom_left": "Bottom left", "bottom_right": "Bottom right", "bottom_center": "Bottom center", - "top_center": "Top center" + "top_center": "Top center", + "center": "Center" }, "quickSettingsStyle": { "modern": "Modern", @@ -1623,4 +1624,4 @@ "disabled": "Battery manager disabled" } } -} \ No newline at end of file +} From 1d1c5ec01648e219a7614ac8fc6a638f676e3561 Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Wed, 15 Oct 2025 11:48:13 -0400 Subject: [PATCH 003/109] Include additional translation strings --- Assets/Translations/de.json | 5 +++-- Assets/Translations/es.json | 5 +++-- Assets/Translations/fr.json | 5 +++-- Assets/Translations/pt.json | 5 +++-- Assets/Translations/zh-CN.json | 5 +++-- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 6a5bb0bd..b75e0079 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1322,7 +1322,8 @@ "bottom_left": "Unten links", "bottom_right": "Unten rechts", "bottom_center": "Unten mittig", - "top_center": "Oben mittig" + "top_center": "Oben mittig", + "center": "Mitte" }, "quickSettingsStyle": { "modern": "Modern", @@ -1623,4 +1624,4 @@ "description": "Das Control-Center-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." } } -} \ No newline at end of file +} diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 9ced10d2..fe0fd6fa 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1305,7 +1305,8 @@ "bottom_left": "Inferior izquierda", "bottom_right": "Inferior derecha", "bottom_center": "Inferior central", - "top_center": "Superior central" + "top_center": "Superior central", + "center": "Centro" }, "quickSettingsStyle": { "modern": "Moderno", @@ -1623,4 +1624,4 @@ "disabled": "Administrador de batería deshabilitado" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 1d2d9568..e8ab41bc 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1305,7 +1305,8 @@ "bottom_left": "En bas à gauche", "bottom_right": "En bas à droite", "bottom_center": "En bas au centre", - "top_center": "En haut au centre" + "top_center": "En haut au centre", + "center": "Centre" }, "quickSettingsStyle": { "modern": "Moderne", @@ -1623,4 +1624,4 @@ "disabled": "Gestionnaire de batterie désactivé" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 118939a8..ac27a3b5 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1303,7 +1303,8 @@ "bottom_left": "Inferior esquerdo", "bottom_right": "Inferior direito", "bottom_center": "Centro inferior", - "top_center": "Centro superior" + "top_center": "Centro superior", + "center": "Centro" }, "quickSettingsStyle": { "modern": "Moderno", @@ -1629,4 +1630,4 @@ "uninstall-failed": "Falha na desinstalação" } } -} \ No newline at end of file +} diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index d64db2ff..fd255395 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1305,7 +1305,8 @@ "bottom_left": "左下角", "bottom_right": "右下角", "bottom_center": "底部居中", - "top_center": "顶部居中" + "top_center": "顶部居中", + "center": "居中" }, "quickSettingsStyle": { "modern": "现代", @@ -1623,4 +1624,4 @@ "disabled": "电池管理器已禁用" } } -} \ No newline at end of file +} From 514fdaa4ccb3421f501e0dc869ccabcfc25172e1 Mon Sep 17 00:00:00 2001 From: lysec Date: Wed, 15 Oct 2025 18:01:08 +0200 Subject: [PATCH 004/109] SetupWizard: initial commit --- Assets/Translations/de.json | 30 ++ Assets/Translations/en.json | 29 + Assets/Translations/es.json | 30 ++ Assets/Translations/fr.json | 31 ++ Assets/Translations/pt.json | 31 ++ Assets/Translations/zh-CN.json | 31 ++ Assets/settings-default.json | 3 +- Commons/Settings.qml | 1 + Modules/SetupWizard/SetupAppearanceStep.qml | 555 ++++++++++++++++++++ Modules/SetupWizard/SetupCustomizeStep.qml | 522 ++++++++++++++++++ Modules/SetupWizard/SetupWallpaperStep.qml | 507 ++++++++++++++++++ Modules/SetupWizard/SetupWizard.qml | 414 +++++++++++++++ Services/AppThemeService.qml | 2 +- Widgets/NImageCached.qml | 20 +- shell.qml | 26 + 15 files changed, 2228 insertions(+), 4 deletions(-) create mode 100644 Modules/SetupWizard/SetupAppearanceStep.qml create mode 100644 Modules/SetupWizard/SetupCustomizeStep.qml create mode 100644 Modules/SetupWizard/SetupWallpaperStep.qml create mode 100644 Modules/SetupWizard/SetupWizard.qml diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 6a5bb0bd..3bfd4dd6 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1621,6 +1621,36 @@ "missing-control-center": { "label": "Letztes Control-Center-Widget entfernt", "description": "Das Control-Center-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." + }, + "setup": { + "customize": { + "header": "Erlebnis anpassen", + "subheader": "Leistenposition, Dichte, Skalierung und mehr einstellen." + }, + "appearance": { + "header": "Erscheinungsbild", + "subheader": "Dunkelmodus und Farbquellen wählen (Matugen oder vordefiniert)." + }, + "wallpaper": { + "header": "Wähle dein Hintergrundbild", + "subheader": "Bestimme die Stimmung mit einem schönen Hintergrund.", + "select-prompt": "Wähle unten ein Hintergrundbild", + "preview-error": "Bild konnte nicht geladen werden", + "none-in-dir": "Keine Hintergrundbilder im Verzeichnis gefunden", + "no-dir": "Kein Hintergrundbild-Verzeichnis ausgewählt", + "no-valid": "Keine gültigen Bilddateien gefunden in: {dir}", + "choose-dir": "Wähle ein Verzeichnis mit deinen Hintergrundbildern", + "dir": { + "label": "Hintergrundbild-Verzeichnis", + "description": "Wähle den Ordner mit deinen Hintergrundbildern", + "browse": "Ordner auswählen", + "select-title": "Hintergrundbild-Ordner wählen" + } } + , + "welcome": { + "note": "Nur ein paar Grundeinstellungen – alle Optionen findest du in den Einstellungen" } + } +} } \ No newline at end of file diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 7f6cf5c7..bd8545e4 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1622,5 +1622,34 @@ "lifespan": "Extended lifespan ({percent}%)", "disabled": "Battery manager disabled" } + }, + "setup": { + "customize": { + "header": "Customize your experience", + "subheader": "Adjust bar position, density, scaling and more." + }, + "appearance": { + "header": "Appearance", + "subheader": "Choose dark mode and color sources (Matugen or predefined)." + }, + "wallpaper": { + "header": "Choose your wallpaper", + "subheader": "Set the mood with a beautiful background.", + "select-prompt": "Select a wallpaper below", + "preview-error": "Failed to load image", + "none-in-dir": "No wallpapers found in directory", + "no-dir": "No wallpaper directory selected", + "no-valid": "No valid image files found in: {dir}", + "choose-dir": "Choose a directory containing your wallpaper images", + "dir": { + "label": "Wallpaper directory", + "description": "Choose the folder containing your wallpapers", + "browse": "Browse for wallpaper folder", + "select-title": "Select wallpaper folder" + } + }, + "welcome": { + "note": "Just a few basics to get you started - full options are in Settings" + } } } \ No newline at end of file diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 9ced10d2..e33bbb3b 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1622,5 +1622,35 @@ "lifespan": "Vida útil prolongada ({percent}%)", "disabled": "Administrador de batería deshabilitado" } + }, + "setup": { + "customize": { + "header": "Personaliza tu experiencia", + "subheader": "Ajusta la posición de la barra, densidad, escala y más." + }, + "appearance": { + "header": "Apariencia", + "subheader": "Elige modo oscuro y fuentes de color (Matugen o predefinido)." + }, + "wallpaper": { + "header": "Elige tu fondo", + "subheader": "Define el ambiente con un bonito fondo.", + "select-prompt": "Selecciona un fondo abajo", + "preview-error": "No se pudo cargar la imagen", + "none-in-dir": "No se encontraron fondos en el directorio", + "no-dir": "No se seleccionó un directorio de fondos", + "no-valid": "No se encontraron imágenes válidas en: {dir}", + "choose-dir": "Elige un directorio que contenga tus fondos", + "dir": { + "label": "Directorio de fondos", + "description": "Elige la carpeta que contiene tus fondos", + "browse": "Seleccionar carpeta", + "select-title": "Seleccionar carpeta de fondos" + } + , + "welcome": { + "note": "Solo algunos ajustes básicos para empezar - el resto está en Configuración" } + } +} } \ No newline at end of file diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 1d2d9568..c613046d 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1623,4 +1623,35 @@ "disabled": "Gestionnaire de batterie désactivé" } } + , + "setup": { + "customize": { + "header": "Personnaliser votre expérience", + "subheader": "Ajustez la position de la barre, la densité, l'échelle et plus encore." + }, + "appearance": { + "header": "Apparence", + "subheader": "Choisissez le mode sombre et la source des couleurs (Matugen ou prédéfinie)." + }, + "wallpaper": { + "header": "Choisissez votre fond d'écran", + "subheader": "Définissez l'ambiance avec un joli fond.", + "select-prompt": "Sélectionnez un fond ci-dessous", + "preview-error": "Échec du chargement de l'image", + "none-in-dir": "Aucun fond d'écran trouvé dans le répertoire", + "no-dir": "Aucun répertoire de fonds d'écran sélectionné", + "no-valid": "Aucun fichier image valide trouvé dans : {dir}", + "choose-dir": "Choisissez un répertoire contenant vos fonds d'écran", + "dir": { + "label": "Répertoire des fonds d'écran", + "description": "Choisissez le dossier contenant vos fonds d'écran", + "browse": "Parcourir le dossier", + "select-title": "Sélectionner le dossier des fonds d'écran" + } + } + , + "welcome": { + "note": "Quelques réglages de base pour démarrer — toutes les options sont dans Paramètres" + } + } } \ No newline at end of file diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 118939a8..e825b83e 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1629,4 +1629,35 @@ "uninstall-failed": "Falha na desinstalação" } } + , + "setup": { + "customize": { + "header": "Personalizar a sua experiência", + "subheader": "Ajuste a posição da barra, densidade, escala e mais." + }, + "appearance": { + "header": "Aparência", + "subheader": "Escolha o modo escuro e as fontes de cores (Matugen ou predefinidas)." + }, + "wallpaper": { + "header": "Escolha o seu papel de parede", + "subheader": "Defina o ambiente com um belo fundo.", + "select-prompt": "Selecione um papel de parede abaixo", + "preview-error": "Falha ao carregar a imagem", + "none-in-dir": "Nenhum papel de parede encontrado no diretório", + "no-dir": "Nenhum diretório de papéis de parede selecionado", + "no-valid": "Nenhuma imagem válida encontrada em: {dir}", + "choose-dir": "Escolha um diretório contendo seus papéis de parede", + "dir": { + "label": "Diretório de papéis de parede", + "description": "Escolha a pasta que contém seus papéis de parede", + "browse": "Procurar pasta", + "select-title": "Selecionar pasta de papéis de parede" + } + } + , + "welcome": { + "note": "Apenas alguns ajustes básicos para começar - o restante está em Configurações" + } + } } \ No newline at end of file diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index d64db2ff..9aa29719 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1623,4 +1623,35 @@ "disabled": "电池管理器已禁用" } } + , + "setup": { + "customize": { + "header": "自定义体验", + "subheader": "调整状态栏位置、密度、缩放等。" + }, + "appearance": { + "header": "外观", + "subheader": "选择深色模式与配色来源(Matugen 或预设)。" + }, + "wallpaper": { + "header": "选择你的壁纸", + "subheader": "用精美壁纸营造氛围。", + "select-prompt": "在下方选择一张壁纸", + "preview-error": "图片加载失败", + "none-in-dir": "目录中未找到壁纸", + "no-dir": "未选择壁纸目录", + "no-valid": "在 {dir} 中未找到有效图片文件", + "choose-dir": "选择包含壁纸图片的目录", + "dir": { + "label": "壁纸目录", + "description": "选择存放壁纸的文件夹", + "browse": "浏览文件夹", + "select-title": "选择壁纸文件夹" + } + } + , + "welcome": { + "note": "先进行一些基础设置——更多选项可在“设置”中找到" + } + } } \ No newline at end of file diff --git a/Assets/settings-default.json b/Assets/settings-default.json index b8a82259..99ef9c6d 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -64,7 +64,8 @@ "screenRadiusRatio": 1, "animationSpeed": 1, "animationDisabled": false, - "compactLockScreen": false + "compactLockScreen": false, + "setupCompleted": false }, "location": { "name": "Tokyo", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index d0308487..2349a292 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -185,6 +185,7 @@ Singleton { property real animationSpeed: 1.0 property bool animationDisabled: false property bool compactLockScreen: false + property bool setupCompleted: false } // location diff --git a/Modules/SetupWizard/SetupAppearanceStep.qml b/Modules/SetupWizard/SetupAppearanceStep.qml new file mode 100644 index 00000000..15c8c1ee --- /dev/null +++ b/Modules/SetupWizard/SetupAppearanceStep.qml @@ -0,0 +1,555 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell.Io +import qs.Commons +import qs.Services +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + function extractSchemeName(path) { + var basename = path.split('/').pop() + return basename.replace('.json', '') + } + + // Cache for scheme colors (mirrors ColorSchemeTab approach) + property var schemeColorsCache: ({}) + property int cacheVersion: 0 + + function getSchemeColor(schemeName, key) { + try { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var data = schemeColorsCache[schemeName] + if (data && data[mode] && data[mode][key]) + return data[mode][key] + } catch (e) { + + } + return Color.mSurfaceVariant + } + + // Match ColorSchemeTab helpers + function schemeLoaded(schemeName, jsonData) { + var value = jsonData || {} + schemeColorsCache[schemeName] = value + cacheVersion++ + Logger.log("SetupAppearanceStep", `Loaded scheme ${schemeName}`) + } + + Connections { + target: ColorSchemeService + function onSchemesChanged() { + Logger.log("SetupAppearanceStep", `Color schemes changed: ${ColorSchemeService.schemes.length}`) + schemeColorsCache = {} + cacheVersion++ + } + } + + // Beautiful header with icon + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "palette" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("setup.appearance.header") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("setup.appearance.subheader") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + contentWidth: availableWidth + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded + + ColumnLayout { + width: parent.width + spacing: Style.marginM + + // Dark Mode Toggle + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "moon" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.color-scheme.color-source.dark-mode.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.color-scheme.color-source.dark-mode.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + NToggle { + checked: Settings.data.colorSchemes.darkMode + onToggled: checked => Settings.data.colorSchemes.darkMode = checked + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Wallpaper Colors Toggle + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "color-picker" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + NToggle { + enabled: ProgramCheckerService.matugenAvailable + opacity: ProgramCheckerService.matugenAvailable ? 1.0 : 0.6 + checked: Settings.data.colorSchemes.useWallpaperColors && ProgramCheckerService.matugenAvailable + onToggled: checked => { + if (!ProgramCheckerService.matugenAvailable) + return + if (checked) { + Settings.data.colorSchemes.useWallpaperColors = true + AppThemeService.generate() + } else { + Settings.data.colorSchemes.useWallpaperColors = false + if (Settings.data.colorSchemes.predefinedScheme) { + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) + } + } + } + } + } + + // Matugen not available notice + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + visible: !ProgramCheckerService.matugenAvailable + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "alert-triangle" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + NText { + text: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.description") + // Reuse description; availability is visually indicated + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + // Matugen scheme type (visible when wallpaper colors enabled and matugen available) + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + visible: Settings.data.colorSchemes.useWallpaperColors && ProgramCheckerService.matugenAvailable + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "wand" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Matugen scheme options styled like bar position buttons + GridLayout { + Layout.fillWidth: true + columns: 2 + rowSpacing: Style.marginS + columnSpacing: Style.marginS + + Repeater { + model: [{ + "key": "scheme-content", + "name": "Content" + }, { + "key": "scheme-expressive", + "name": "Expressive" + }, { + "key": "scheme-fidelity", + "name": "Fidelity" + }, { + "key": "scheme-fruit-salad", + "name": "Fruit Salad" + }, { + "key": "scheme-monochrome", + "name": "Monochrome" + }, { + "key": "scheme-neutral", + "name": "Neutral" + }, { + "key": "scheme-rainbow", + "name": "Rainbow" + }, { + "key": "scheme-tonal-spot", + "name": "Tonal Spot" + }] + delegate: Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 48 + radius: Style.radiusM + border.width: 1 + + property bool isActive: Settings.data.colorSchemes.matugenSchemeType === modelData.key + + color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant + border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline + opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8 + + NText { + text: modelData.name + pointSize: Style.fontSizeM + font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium + color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface + anchors.centerIn: parent + } + + HoverHandler { + id: hoverHandler + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.colorSchemes.matugenSchemeType = modelData.key + AppThemeService.generate() + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + visible: !Settings.data.colorSchemes.useWallpaperColors + } + + // Predefined schemes section (visible when wallpaper colors disabled) + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + visible: !Settings.data.colorSchemes.useWallpaperColors + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "palette" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.color-scheme.predefined.section.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.color-scheme.predefined.section.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Predefined schemes Grid (matches ColorSchemeTab) + GridLayout { + id: schemesGrid + columns: Math.max(2, Math.floor((parent.width - Style.marginM * 2) / 180)) + rowSpacing: Style.marginM + columnSpacing: Style.marginM + Layout.fillWidth: true + + Repeater { + model: ColorSchemeService.schemes + + delegate: Rectangle { + id: schemeItem + + property string schemePath: modelData + property string schemeName: root.extractSchemeName(modelData) + + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + height: 50 + radius: Style.radiusS + color: (root.cacheVersionroot.getSchemeColor(schemeName, "mSurface")) + border.width: Math.max(1, Style.borderL) + border.color: itemMouseArea.containsMouse ? Color.mTertiary : (Settings.data.colorSchemes.predefinedScheme === schemeName ? Color.mSecondary : Color.mOutline) + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginXS + + NText { + text: schemeItem.schemeName + pointSize: Style.fontSizeS + font.weight: Style.fontWeightMedium + color: Color.mOnSurface + Layout.fillWidth: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + maximumLineCount: 1 + } + + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: (root.cacheVersionfunction () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mPrimary) || root.getSchemeColor(schemeItem.schemeName, "mPrimary") + })() + } + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: (root.cacheVersionfunction () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mSecondary) || root.getSchemeColor(schemeItem.schemeName, "mSecondary") + })() + } + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: (root.cacheVersionfunction () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mTertiary) || root.getSchemeColor(schemeItem.schemeName, "mTertiary") + })() + } + Rectangle { + width: 14 + height: 14 + radius: width * 0.5 + color: (root.cacheVersionfunction () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mError) || root.getSchemeColor(schemeItem.schemeName, "mError") + })() + } + } + + MouseArea { + id: itemMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.colorSchemes.useWallpaperColors = false + Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) + } + } + } + } + } + } + + // Bottom spacer + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + } + } + + // Hidden loader to populate schemeColorsCache from files + Item { + visible: false + Repeater { + model: ColorSchemeService.schemes + delegate: Item { + FileView { + path: modelData + blockLoading: false + onLoaded: { + var schemeName = root.extractSchemeName(path) + try { + var jsonData = JSON.parse(text()) + root.schemeLoaded(schemeName, jsonData) + } catch (e) { + root.schemeLoaded(schemeName, null) + } + } + } + } + } + } +} diff --git a/Modules/SetupWizard/SetupCustomizeStep.qml b/Modules/SetupWizard/SetupCustomizeStep.qml new file mode 100644 index 00000000..a1854f5f --- /dev/null +++ b/Modules/SetupWizard/SetupCustomizeStep.qml @@ -0,0 +1,522 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +ColumnLayout { + id: root + + property real selectedScaleRatio: 1.0 + property string selectedBarPosition: "top" + property bool selectedDimDesktop: true + + signal scaleRatioChanged(real ratio) + signal barPositionChanged(string position) + signal dimDesktopChanged(bool dim) + + spacing: Style.marginM + + // Beautiful header with icon + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "palette" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NText { + text: I18n.tr("setup.customize.header") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("setup.customize.subheader") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + contentWidth: availableWidth + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded + + ColumnLayout { + width: parent.width + spacing: Style.marginM + + // Bar Position section + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + Rectangle { + width: 28 + height: 28 + radius: Style.radiusM + color: Color.mSurface + + NIcon { + icon: "layout-2" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + NText { + text: I18n.tr("settings.bar.appearance.position.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("settings.bar.appearance.position.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + GridLayout { + Layout.fillWidth: true + columns: 2 + rowSpacing: Style.marginS + columnSpacing: Style.marginS + + Repeater { + model: [{ + "key": "top", + "name": I18n.tr("options.bar.position.top"), + "icon": "arrow-up" + }, { + "key": "bottom", + "name": I18n.tr("options.bar.position.bottom"), + "icon": "arrow-down" + }, { + "key": "left", + "name": I18n.tr("options.bar.position.left"), + "icon": "arrow-left" + }, { + "key": "right", + "name": I18n.tr("options.bar.position.right"), + "icon": "arrow-right" + }] + delegate: Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 48 + radius: Style.radiusM + border.width: 1 + + property bool isActive: selectedBarPosition === modelData.key + + color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant + border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline + opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8 + + NText { + text: modelData.name + pointSize: Style.fontSizeM + font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium + color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface + anchors.centerIn: parent + } + + HoverHandler { + id: hoverHandler + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + selectedBarPosition = modelData.key + barPositionChanged(modelData.key) + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Dim Desktop section + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "moon" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("settings.user-interface.dim-desktop.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("settings.user-interface.dim-desktop.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + NToggle { + checked: selectedDimDesktop + onToggled: function (checked) { + selectedDimDesktop = checked + dimDesktopChanged(checked) + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Bar Density section + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "minimize" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("settings.bar.appearance.density.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("settings.bar.appearance.density.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + + RowLayout { + spacing: Style.marginS + Repeater { + model: [{ + "key": "mini", + "name": I18n.tr("options.bar.density.mini") + }, { + "key": "compact", + "name": I18n.tr("options.bar.density.compact") + }, { + "key": "default", + "name": I18n.tr("options.bar.density.default") + }, { + "key": "comfortable", + "name": I18n.tr("options.bar.density.comfortable") + }] + delegate: Rectangle { + radius: 16 + border.width: 1 + Layout.preferredHeight: 32 + Layout.preferredWidth: Math.max(72, densityText.implicitWidth + Style.marginM) + + property bool isActive: Settings.data.bar.density === modelData.key + + color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant + border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline + opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8 + + NText { + id: densityText + text: modelData.name + pointSize: Style.fontSizeS + font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium + color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface + anchors.centerIn: parent + } + + HoverHandler { + id: hoverHandler + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Settings.data.bar.density = modelData.key + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // UI Scale section + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "maximize" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("settings.user-interface.scaling.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("settings.user-interface.scaling.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + NText { + text: "80%" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + NValueSlider { + Layout.fillWidth: true + from: 0.8 + to: 1.2 + stepSize: 0.05 + value: selectedScaleRatio + onMoved: function (value) { + selectedScaleRatio = value + scaleRatioChanged(value) + } + text: Math.floor(selectedScaleRatio * 100) + "%" + } + NText { + text: "120%" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Bar Floating toggle + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + Rectangle { + width: 32 + height: 32 + radius: Style.radiusM + color: Color.mSurface + NIcon { + icon: "layout-2" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + NText { + text: I18n.tr("settings.bar.appearance.floating.label") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: I18n.tr("settings.bar.appearance.floating.description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + NToggle { + checked: Settings.data.bar.floating + onToggled: function (checked) { + Settings.data.bar.floating = checked + } + } + } + + // Divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + Layout.topMargin: Style.marginS + Layout.bottomMargin: Style.marginS + } + + // Bar Background Opacity + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + NLabel { + label: I18n.tr("settings.bar.appearance.background-opacity.label") + description: I18n.tr("settings.bar.appearance.background-opacity.description") + } + NValueSlider { + Layout.fillWidth: true + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.bar.backgroundOpacity + onMoved: function (value) { + Settings.data.bar.backgroundOpacity = value + } + text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%" + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.marginL + } + } + } +} diff --git a/Modules/SetupWizard/SetupWallpaperStep.qml b/Modules/SetupWizard/SetupWallpaperStep.qml new file mode 100644 index 00000000..fa1225c2 --- /dev/null +++ b/Modules/SetupWizard/SetupWallpaperStep.qml @@ -0,0 +1,507 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Effects +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services +import qs.Widgets +import "../../Helpers/FuzzySort.js" as FuzzySort + +ColumnLayout { + id: root + + property string selectedDirectory: "" + property string selectedWallpaper: "" + + signal directoryChanged(string directory) + signal wallpaperChanged(string wallpaper) + + spacing: Style.marginL + + // Beautiful header with icon + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + RowLayout { + spacing: Style.marginM + + Rectangle { + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + ColumnLayout { + spacing: Style.marginXS + + NText { + text: I18n.tr("setup.wallpaper.header") + pointSize: Style.fontSizeXL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: I18n.tr("setup.wallpaper.subheader") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + } + } + } + + // Large preview with rounded corners and shadow effect + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 180 + color: Color.mSurfaceVariant + radius: Style.radiusL + border.color: selectedWallpaper !== "" ? Color.mPrimary : Color.mOutline + border.width: selectedWallpaper !== "" ? 2 : 1 + clip: true + + // Mirror WallpaperPanel approach with rounded shader mask + NImageCached { + id: previewCached + anchors.fill: parent + anchors.margins: 4 + maxCacheDimension: 512 + cacheFolder: Settings.cacheDirImagesWallpapers + imagePath: selectedWallpaper !== "" ? "file://" + selectedWallpaper : "" + visible: false // used as texture source for the shader + } + + ShaderEffect { + anchors.fill: parent + anchors.margins: 4 + property var source: ShaderEffectSource { + sourceItem: previewCached + hideSource: true + live: true + recursive: false + format: ShaderEffectSource.RGBA + } + property real itemWidth: width + property real itemHeight: height + property real cornerRadius: Style.radiusL + property real imageOpacity: 1.0 + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb") + supportsAtlasTextures: false + blending: true + } + + // Loading placeholder + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: Style.radiusL + visible: (previewCached.status === Image.Loading || previewCached.status === Image.Null) && selectedWallpaper !== "" + + NIcon { + icon: "image" + pointSize: Style.fontSizeXXL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + + // Error placeholder + Rectangle { + anchors.fill: parent + color: Color.mError + opacity: 0.1 + radius: Style.radiusL + visible: previewCached.status === Image.Error && selectedWallpaper !== "" + + ColumnLayout { + anchors.centerIn: parent + spacing: Style.marginS + + NIcon { + icon: "alert-circle" + pointSize: Style.fontSizeXXL + color: Color.mError + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: I18n.tr("setup.wallpaper.preview-error") + pointSize: Style.fontSizeS + color: Color.mError + Layout.alignment: Qt.AlignHCenter + } + } + } + + NBusyIndicator { + anchors.centerIn: parent + visible: (previewCached.status === Image.Loading || previewCached.status === Image.Null) && selectedWallpaper !== "" + running: visible + size: 28 + } + + ColumnLayout { + anchors.centerIn: parent + spacing: Style.marginL + visible: selectedWallpaper === "" + opacity: 0.6 + + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: 64 + height: 64 + radius: width / 2 + color: Color.mPrimary + opacity: 0.15 + + NIcon { + icon: "sparkles" + pointSize: Style.fontSizeXXL + color: Color.mPrimary + anchors.centerIn: parent + } + } + + NText { + text: I18n.tr("setup.wallpaper.select-prompt") + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignHCenter + font.weight: Style.fontWeightMedium + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + // Wallpaper gallery strip + Item { + Layout.fillWidth: true + Layout.preferredHeight: 90 + visible: filteredWallpapers.length > 0 + + ScrollView { + anchors.fill: parent + clip: true + ScrollBar.horizontal.policy: ScrollBar.AsNeeded + ScrollBar.vertical.policy: ScrollBar.AlwaysOff + + RowLayout { + spacing: Style.marginM + height: parent.height + + Repeater { + model: filteredWallpapers + delegate: Rectangle { + Layout.preferredWidth: 120 + Layout.preferredHeight: 80 + color: Color.mSurface + radius: Style.radiusM + border.color: selectedWallpaper === modelData ? Color.mPrimary : Color.mOutline + border.width: selectedWallpaper === modelData ? 2 : 1 + clip: true + + // Cached thumbnail (used as shader source) + NImageCached { + id: thumbCached + anchors.fill: parent + anchors.margins: 3 + maxCacheDimension: 256 + cacheFolder: Settings.cacheDirImagesWallpapers + imagePath: "file://" + modelData + visible: false + } + + ShaderEffect { + anchors.fill: parent + anchors.margins: 3 + property var source: ShaderEffectSource { + sourceItem: thumbCached + hideSource: true + live: true + recursive: false + format: ShaderEffectSource.RGBA + } + property real itemWidth: width + property real itemHeight: height + property real cornerRadius: Style.radiusM - 3 + property real imageOpacity: 1.0 + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb") + supportsAtlasTextures: false + blending: true + } + + // Loading state + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: Style.radiusM + visible: thumbCached.status === Image.Loading + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + + // Error state + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: Style.radiusM + visible: thumbCached.status === Image.Error + + NIcon { + icon: "image" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + anchors.centerIn: parent + } + } + + NBusyIndicator { + anchors.centerIn: parent + visible: thumbCached.status === Image.Loading || thumbCached.status === Image.Null + running: visible + size: 18 + } + + Rectangle { + anchors.fill: parent + color: Color.mPrimary + opacity: hoverHandler.hovered ? 0.1 : 0 + radius: Style.radiusM + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + Rectangle { + visible: selectedWallpaper === modelData + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 6 + width: 24 + height: 24 + radius: width / 2 + color: Color.mPrimary + + NIcon { + icon: "check" + pointSize: Style.fontSizeS + color: Color.mOnPrimary + anchors.centerIn: parent + } + } + + HoverHandler { + id: hoverHandler + } + + TapHandler { + onTapped: { + selectedWallpaper = modelData + wallpaperChanged(modelData) + } + } + } + } + } + } + } + + // Helpful info card + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 80 + color: Color.mSurfaceVariant + radius: Style.radiusM + opacity: 0.4 + visible: filteredWallpapers.length === 0 + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + NIcon { + icon: "folder-open" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS + NText { + text: filteredWallpapers.length === 0 && selectedDirectory !== "" ? I18n.tr("setup.wallpaper.none-in-dir") : I18n.tr("setup.wallpaper.no-dir") + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: Color.mOnSurfaceVariant + } + NText { + text: selectedDirectory !== "" ? I18n.tr("setup.wallpaper.no-valid", { + "dir": selectedDirectory + }) : I18n.tr("setup.wallpaper.choose-dir") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + opacity: 0.8 + } + } + } + } + + // Directory selection + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NTextInputButton { + id: wallpaperPathInput + label: I18n.tr("setup.wallpaper.dir.label") + description: I18n.tr("setup.wallpaper.dir.description") + text: selectedDirectory + buttonIcon: "folder-open" + buttonTooltip: I18n.tr("setup.wallpaper.dir.browse") + Layout.fillWidth: true + onInputEditingFinished: { + selectedDirectory = text + directoryChanged(text) + } + onButtonClicked: directoryPicker.open() + } + } + + // Internal properties and functions + property list wallpapersList: [] + property list filteredWallpapers: [] + + function updateFilteredWallpapers() { + filteredWallpapers = wallpapersList + } + + function refreshWallpapers() { + if (!selectedDirectory || selectedDirectory === "") { + wallpapersList = [] + filteredWallpapers = [] + return + } + if (typeof WallpaperService !== "undefined" && WallpaperService.getWallpapersList) { + var wallpapers = WallpaperService.getWallpapersList(Screen.name) + wallpapersList = wallpapers + updateFilteredWallpapers() + if (wallpapersList.length > 0 && selectedWallpaper === "") { + selectedWallpaper = wallpapersList[0] + } + } else { + readDirectoryImages(selectedDirectory) + } + } + + function readDirectoryImages(directoryPath) { + directoryScanner.command = ["find", directoryPath, "-type", "f", "\\(-iname", "*.jpg", "-o", "-iname", "*.jpeg", "-o", "-iname", "*.png", "-o", "-iname", "*.bmp", "-o", "-iname", "*.webp", "-o", "-iname", "*.svg", "\\)"] + directoryScanner.running = true + return [] + } + + onSelectedDirectoryChanged: { + if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper) { + Settings.data.wallpaper.directory = selectedDirectory + } + if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) { + WallpaperService.refreshWallpapersList() + } + Qt.callLater(refreshWallpapers) + } + + Connections { + target: WallpaperService + enabled: typeof WallpaperService !== "undefined" + function onWallpaperListChanged(screenName, count) { + if (screenName === Screen.name) { + Qt.callLater(refreshWallpapers) + } + } + } + + Timer { + id: initialRefreshTimer + interval: 1000 + running: false + repeat: false + onTriggered: refreshWallpapers() + } + + Component.onCompleted: { + if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper && Settings.data.wallpaper.directory) { + selectedDirectory = Settings.data.wallpaper.directory + } else { + selectedDirectory = Quickshell.env("HOME") + "/Pictures/Wallpapers" + } + if (typeof WallpaperService !== "undefined" && WallpaperService.currentWallpaper) { + selectedWallpaper = WallpaperService.currentWallpaper + } + initialRefreshTimer.start() + } + + NFilePicker { + id: directoryPicker + selectionMode: "folders" + title: I18n.tr("setup.wallpaper.dir.select-title") + initialPath: selectedDirectory || Quickshell.env("HOME") + "/Pictures" + onAccepted: paths => { + if (paths.length > 0) { + selectedDirectory = paths[0] + directoryChanged(paths[0]) + } + } + } + + Process { + id: directoryScanner + command: ["find", "", "-type", "f", "\\(-iname", "*.jpg", "-o", "-iname", "*.jpeg", "-o", "-iname", "*.png", "-o", "-iname", "*.bmp", "-o", "-iname", "*.webp", "-o", "-iname", "*.svg", "\\)"] + running: false + stdout: StdioCollector {} + stderr: StdioCollector {} + onExited: function (exitCode) { + if (exitCode === 0) { + var lines = stdout.text.split('\n') + var images = [] + for (var i = 0; i < lines.length; i++) { + var line = lines[i].trim() + if (line !== '') { + images.push(line) + } + } + wallpapersList = images + updateFilteredWallpapers() + if (wallpapersList.length > 0 && selectedWallpaper === "") { + selectedWallpaper = wallpapersList[0] + } + } + } + } +} diff --git a/Modules/SetupWizard/SetupWizard.qml b/Modules/SetupWizard/SetupWizard.qml new file mode 100644 index 00000000..d34d6013 --- /dev/null +++ b/Modules/SetupWizard/SetupWizard.qml @@ -0,0 +1,414 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services +import qs.Widgets + +NPanel { + id: root + + preferredWidth: 520 + preferredHeight: 600 + preferredWidthRatio: 0.4 + preferredHeightRatio: 0.6 + panelAnchorHorizontalCenter: true + panelAnchorVerticalCenter: true + panelKeyboardFocus: true + + // Prevent closing during setup + backgroundClickEnabled: false + draggable: false + + property int currentStep: 0 + property int totalSteps: 4 + + // Setup wizard data + property string selectedWallpaperDirectory: Settings.defaultWallpapersDirectory + property string selectedWallpaper: "" + property real selectedScaleRatio: 1.0 + property string selectedBarPosition: "top" + property bool selectedDimDesktop: true + + panelContent: Component { + Item { + id: container + anchors.fill: parent + + ColumnLayout { + id: wizardContent + anchors.fill: parent + anchors.margins: Style.marginXL + spacing: Style.marginL + + // Override ESC key to prevent closing during setup + Shortcut { + sequences: ["Escape"] + enabled: root.active + onActivated: { + + // Do nothing - prevent ESC from closing the setup wizard + } + context: Qt.WindowShortcut + } + + // Step content - takes most of the space + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 300 + + StackLayout { + id: stepStack + anchors.fill: parent + currentIndex: currentStep + + // Step 0: Welcome - Beautiful centered design + Item { + ColumnLayout { + anchors.centerIn: parent + width: Math.min(parent.width - Style.marginXL * 2, 420) + spacing: Style.marginXL + + // Logo with subtle glow effect + Item { + Layout.fillWidth: true + Layout.preferredHeight: 120 + Layout.alignment: Qt.AlignHCenter + + Rectangle { + anchors.centerIn: parent + width: 120 + height: 120 + radius: width / 2 + color: Color.mPrimary + opacity: 0.08 + scale: 1.3 + } + + Image { + anchors.centerIn: parent + width: 110 + height: 110 + source: "https://assets.noctalia.dev/noctalia-logo.svg" + fillMode: Image.PreserveAspectFit + smooth: true + + Rectangle { + anchors.fill: parent + color: Color.mSurfaceVariant + radius: width / 2 + border.color: Color.mOutline + border.width: 2 + visible: parent.status === Image.Error + + NIcon { + icon: "sparkles" + pointSize: Style.fontSizeXXL * 1.5 + color: Color.mPrimary + anchors.centerIn: parent + } + } + + // Subtle pulse animation + SequentialAnimation on scale { + running: true + loops: Animation.Infinite + NumberAnimation { + from: 1.0 + to: 1.05 + duration: 2000 + easing.type: Easing.InOutQuad + } + NumberAnimation { + from: 1.05 + to: 1.0 + duration: 2000 + easing.type: Easing.InOutQuad + } + } + } + } + + // Welcome text with gradient feel + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + spacing: Style.marginM + + NText { + text: "Welcome to Noctalia! ✨" + pointSize: Style.fontSizeXXL * 1.4 + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + NText { + text: "Let's make your desktop uniquely yours" + pointSize: Style.fontSizeL + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + + // Friendly subtext + Rectangle { + Layout.fillWidth: true + Layout.topMargin: Style.marginL + Layout.preferredHeight: childrenRect.height + Style.marginM * 2 + color: Color.mSurfaceVariant + radius: Style.radiusL + opacity: 0.4 + + NText { + anchors.centerIn: parent + width: parent.width - Style.marginL * 2 + text: I18n.tr("setup.welcome.note") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + } + } + } + } + + // Step 1: Wallpaper Setup + SetupWallpaperStep { + id: step1 + selectedDirectory: root.selectedWallpaperDirectory + selectedWallpaper: root.selectedWallpaper + onDirectoryChanged: function (directory) { + root.selectedWallpaperDirectory = directory + root.applyWallpaperSettings() + } + onWallpaperChanged: function (wallpaper) { + root.selectedWallpaper = wallpaper + root.applyWallpaperSettings() + } + } + + // Step 2: UI Configuration + SetupCustomizeStep { + id: step2 + selectedScaleRatio: root.selectedScaleRatio + selectedBarPosition: root.selectedBarPosition + selectedDimDesktop: root.selectedDimDesktop + onScaleRatioChanged: function (ratio) { + root.selectedScaleRatio = ratio + root.applyUISettings() + } + onBarPositionChanged: function (position) { + root.selectedBarPosition = position + root.applyUISettings() + } + onDimDesktopChanged: function (dim) { + root.selectedDimDesktop = dim + root.applyUISettings() + } + } + + // Step 3: Appearance - Dark mode and color source + SetupAppearanceStep { + id: step3 + } + } + } + + // Elegant divider + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + opacity: 0.2 + } + + // Modern progress indicator with labels + Item { + Layout.fillWidth: true + Layout.preferredHeight: 32 + + RowLayout { + anchors.centerIn: parent + spacing: Style.marginM + + Repeater { + model: [{ + "icon": "sparkles", + "label": "Welcome" + }, { + "icon": "image", + "label": "Wallpaper" + }, { + "icon": "settings", + "label": "Customize" + }, { + "icon": "palette", + "label": "Appearance" + }] + delegate: RowLayout { + spacing: Style.marginS + + Rectangle { + width: 24 + height: 24 + radius: width / 2 + color: index <= currentStep ? Color.mPrimary : Color.mSurfaceVariant + border.color: index === currentStep ? Color.mPrimary : "transparent" + border.width: index === currentStep ? 2 : 0 + + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeS + color: index <= currentStep ? Color.mOnPrimary : Color.mOnSurfaceVariant + anchors.centerIn: parent + } + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + + NText { + text: modelData.label + pointSize: Style.fontSizeS + color: index <= currentStep ? Color.mPrimary : Color.mOnSurfaceVariant + font.weight: index === currentStep ? Style.fontWeightBold : Style.fontWeightRegular + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + + // Connector line + Rectangle { + width: 40 + height: 2 + radius: 1 + color: index < currentStep ? Color.mPrimary : Color.mSurfaceVariant + visible: index < totalSteps - 1 + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + } + } + } + } + } + + // Smooth navigation buttons + Item { + Layout.fillWidth: true + Layout.preferredHeight: 44 + Layout.topMargin: Style.marginS + + RowLayout { + anchors.fill: parent + spacing: Style.marginM + + NButton { + text: "Skip Setup" + outlined: true + visible: currentStep === 0 + Layout.preferredHeight: 44 + onClicked: { + root.completeSetup() + } + } + + Item { + Layout.fillWidth: true + } + + NButton { + text: "← Back" + outlined: true + visible: currentStep > 0 + Layout.preferredHeight: 44 + onClicked: { + if (currentStep > 0) { + currentStep-- + } + } + } + + NButton { + text: currentStep === totalSteps - 1 ? "🎉 All Done!" : "Continue →" + Layout.preferredHeight: 44 + onClicked: { + if (currentStep < totalSteps - 1) { + currentStep++ + } else { + root.completeSetup() + } + } + } + } + } + } + } + } + + function completeSetup() { + Logger.log("SetupWizard", "Completing setup with selected options") + + if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) { + Settings.data.wallpaper.directory = selectedWallpaperDirectory + WallpaperService.refreshWallpapersList() + } + + if (selectedWallpaper !== "") { + WallpaperService.changeWallpaper(selectedWallpaper, undefined) + } + + Settings.data.general.scaleRatio = selectedScaleRatio + Settings.data.bar.position = selectedBarPosition + Settings.data.general.dimDesktop = selectedDimDesktop + Settings.data.general.setupCompleted = true + + Settings.saveImmediate() + Logger.log("SetupWizard", "Setup completed successfully") + root.close() + } + + function applyWallpaperSettings() { + if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) { + Settings.data.wallpaper.directory = selectedWallpaperDirectory + WallpaperService.refreshWallpapersList() + } + + if (selectedWallpaper !== "") { + WallpaperService.changeWallpaper(selectedWallpaper, undefined) + } + } + + function applyUISettings() { + Settings.data.general.scaleRatio = selectedScaleRatio + Settings.data.bar.position = selectedBarPosition + Settings.data.general.dimDesktop = selectedDimDesktop + } + + Component.onCompleted: { + Logger.log("SetupWizard", "Setup wizard opened") + // Initialize selections from existing settings to avoid overwriting user config + if (Settings && Settings.data) { + selectedScaleRatio = Settings.data.general.scaleRatio + selectedBarPosition = Settings.data.bar.position + selectedDimDesktop = Settings.data.general.dimDesktop + selectedWallpaperDirectory = Settings.data.wallpaper.directory || Settings.defaultWallpapersDirectory + } + } +} diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index 53f7fe75..d8fa9589 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -105,8 +105,8 @@ Singleton { // Wallpaper Colors Generation // -------------------------------------------------------------------------------- function generateFromWallpaper() { - Logger.log("AppThemeService", "Generating from wallpaper on screen:", Screen.name) + // Logger.log("AppThemeService", "Generating from wallpaper on screen:", Screen.name) const wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''") if (!wp) { Logger.error("AppThemeService", "No wallpaper found") diff --git a/Widgets/NImageCached.qml b/Widgets/NImageCached.qml index 1343828b..edeb4baf 100644 --- a/Widgets/NImageCached.qml +++ b/Widgets/NImageCached.qml @@ -31,8 +31,9 @@ Image { } onCachePathChanged: { if (imageHash && cachePath) { - // Try to load the cached version, failure will be detected below in onStatusChanged - source = cachePath + // Check if cache file exists before trying to load it + cacheChecker.command = ["test", "-f", cachePath] + cacheChecker.running = true } } onStatusChanged: { @@ -48,4 +49,19 @@ 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 + } + } + } } diff --git a/shell.qml b/shell.qml index 39b76224..46edcf47 100644 --- a/shell.qml +++ b/shell.qml @@ -40,6 +40,7 @@ import qs.Modules.OSD import qs.Modules.Settings import qs.Modules.Toast import qs.Modules.Wallpaper +import qs.Modules.SetupWizard ShellRoot { id: shellRoot @@ -89,6 +90,10 @@ ShellRoot { HooksService.init() BluetoothService.init() BatteryService.init() + + if (Settings && Settings.data && Settings.data.general && !Settings.data.general.setupCompleted) { + setupWizardLoader.active = true + } } Background {} @@ -166,6 +171,27 @@ ShellRoot { id: batteryPanel objectName: "batteryPanel" } + + // Lazy-load Setup Wizard to save memory + Component { + id: setupWizardComponent + SetupWizard { + id: setupWizardPanel + objectName: "setupWizardPanel" + } + } + + Loader { + id: setupWizardLoader + active: false + asynchronous: true + sourceComponent: setupWizardComponent + onLoaded: { + if (setupWizardLoader.item && setupWizardLoader.item.open) { + setupWizardLoader.item.open() + } + } + } } } } From ff057355dafbebea23562ab8ddf831b2a373d127 Mon Sep 17 00:00:00 2001 From: lysec Date: Wed, 15 Oct 2025 18:10:39 +0200 Subject: [PATCH 005/109] SetupWizard: small fixes --- Modules/SetupWizard/SetupAppearanceStep.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/SetupWizard/SetupAppearanceStep.qml b/Modules/SetupWizard/SetupAppearanceStep.qml index 15c8c1ee..a85f2d59 100644 --- a/Modules/SetupWizard/SetupAppearanceStep.qml +++ b/Modules/SetupWizard/SetupAppearanceStep.qml @@ -443,7 +443,7 @@ ColumnLayout { Layout.alignment: Qt.AlignHCenter height: 50 radius: Style.radiusS - color: (root.cacheVersionroot.getSchemeColor(schemeName, "mSurface")) + color: (root.cacheVersion, root.getSchemeColor(schemeName, "mSurface")) border.width: Math.max(1, Style.borderL) border.color: itemMouseArea.containsMouse ? Color.mTertiary : (Settings.data.colorSchemes.predefinedScheme === schemeName ? Color.mSecondary : Color.mOutline) @@ -468,7 +468,7 @@ ColumnLayout { width: 14 height: 14 radius: width * 0.5 - color: (root.cacheVersionfunction () { + color: (root.cacheVersion, function () { var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" var cached = root.schemeColorsCache[schemeItem.schemeName] return (cached && cached[mode] && cached[mode].mPrimary) || root.getSchemeColor(schemeItem.schemeName, "mPrimary") @@ -478,7 +478,7 @@ ColumnLayout { width: 14 height: 14 radius: width * 0.5 - color: (root.cacheVersionfunction () { + color: (root.cacheVersion, function () { var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" var cached = root.schemeColorsCache[schemeItem.schemeName] return (cached && cached[mode] && cached[mode].mSecondary) || root.getSchemeColor(schemeItem.schemeName, "mSecondary") @@ -488,7 +488,7 @@ ColumnLayout { width: 14 height: 14 radius: width * 0.5 - color: (root.cacheVersionfunction () { + color: (root.cacheVersion, function () { var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" var cached = root.schemeColorsCache[schemeItem.schemeName] return (cached && cached[mode] && cached[mode].mTertiary) || root.getSchemeColor(schemeItem.schemeName, "mTertiary") @@ -498,7 +498,7 @@ ColumnLayout { width: 14 height: 14 radius: width * 0.5 - color: (root.cacheVersionfunction () { + color: (root.cacheVersion, function () { var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" var cached = root.schemeColorsCache[schemeItem.schemeName] return (cached && cached[mode] && cached[mode].mError) || root.getSchemeColor(schemeItem.schemeName, "mError") From f67551688d264187ba9b29d15a8216886d983ad4 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Wed, 15 Oct 2025 18:12:42 +0200 Subject: [PATCH 006/109] MediaService: add a check if blacklisted before adding to specific or generic players --- Services/MediaService.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Services/MediaService.qml b/Services/MediaService.qml index 006285c9..b0a4eb16 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -38,12 +38,19 @@ Singleton { let allPlayers = Mpris.players.values let finalPlayers = [] const genericBrowsers = ["firefox", "chromium", "chrome"] + const blacklist = (Settings.data.audio && Settings.data.audio.mprisBlacklist) ? Settings.data.audio.mprisBlacklist : [] // Separate players into specific and generic lists let specificPlayers = [] let genericPlayers = [] for (var i = 0; i < allPlayers.length; i++) { const identity = String(allPlayers[i].identity || "").toLowerCase() + const match = blacklist.find(b => { + const s = String(b || "").toLowerCase() + return s && (identity.includes(s)) + }) + if (match) + continue if (genericBrowsers.some(b => identity.includes(b))) { genericPlayers.push(allPlayers[i]) } else { From f20cd85e7977711a9bd052d982293b5937b44194 Mon Sep 17 00:00:00 2001 From: lysec Date: Wed, 15 Oct 2025 18:24:10 +0200 Subject: [PATCH 007/109] SetupWizard: layout changes Autoformat --- Modules/SetupWizard/SetupAppearanceStep.qml | 54 +++++++++++---------- Modules/SetupWizard/SetupCustomizeStep.qml | 54 ++++++++------------- Modules/SetupWizard/SetupWallpaperStep.qml | 3 +- Modules/SetupWizard/SetupWizard.qml | 2 +- 4 files changed, 51 insertions(+), 62 deletions(-) diff --git a/Modules/SetupWizard/SetupAppearanceStep.qml b/Modules/SetupWizard/SetupAppearanceStep.qml index a85f2d59..32903902 100644 --- a/Modules/SetupWizard/SetupAppearanceStep.qml +++ b/Modules/SetupWizard/SetupAppearanceStep.qml @@ -51,13 +51,15 @@ ColumnLayout { // Beautiful header with icon RowLayout { Layout.fillWidth: true + Layout.bottomMargin: Style.marginL spacing: Style.marginM Rectangle { - width: 28 - height: 28 - radius: Style.radiusM - color: Color.mSurface + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 NIcon { icon: "palette" @@ -75,7 +77,7 @@ ColumnLayout { text: I18n.tr("setup.appearance.header") pointSize: Style.fontSizeXL font.weight: Style.fontWeightBold - color: Color.mOnSurface + color: Color.mPrimary } NText { @@ -443,7 +445,7 @@ ColumnLayout { Layout.alignment: Qt.AlignHCenter height: 50 radius: Style.radiusS - color: (root.cacheVersion, root.getSchemeColor(schemeName, "mSurface")) + color: root.cacheVersion >= 0 ? root.getSchemeColor(schemeName, "mSurface") : root.getSchemeColor(schemeName, "mSurface") border.width: Math.max(1, Style.borderL) border.color: itemMouseArea.containsMouse ? Color.mTertiary : (Settings.data.colorSchemes.predefinedScheme === schemeName ? Color.mSecondary : Color.mOutline) @@ -468,41 +470,41 @@ ColumnLayout { width: 14 height: 14 radius: width * 0.5 - color: (root.cacheVersion, function () { - var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - var cached = root.schemeColorsCache[schemeItem.schemeName] - return (cached && cached[mode] && cached[mode].mPrimary) || root.getSchemeColor(schemeItem.schemeName, "mPrimary") - })() + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mPrimary) || root.getSchemeColor(schemeItem.schemeName, "mPrimary") + })() : Color.mPrimary } Rectangle { width: 14 height: 14 radius: width * 0.5 - color: (root.cacheVersion, function () { - var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - var cached = root.schemeColorsCache[schemeItem.schemeName] - return (cached && cached[mode] && cached[mode].mSecondary) || root.getSchemeColor(schemeItem.schemeName, "mSecondary") - })() + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mSecondary) || root.getSchemeColor(schemeItem.schemeName, "mSecondary") + })() : Color.mSecondary } Rectangle { width: 14 height: 14 radius: width * 0.5 - color: (root.cacheVersion, function () { - var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - var cached = root.schemeColorsCache[schemeItem.schemeName] - return (cached && cached[mode] && cached[mode].mTertiary) || root.getSchemeColor(schemeItem.schemeName, "mTertiary") - })() + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mTertiary) || root.getSchemeColor(schemeItem.schemeName, "mTertiary") + })() : Color.mTertiary } Rectangle { width: 14 height: 14 radius: width * 0.5 - color: (root.cacheVersion, function () { - var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - var cached = root.schemeColorsCache[schemeItem.schemeName] - return (cached && cached[mode] && cached[mode].mError) || root.getSchemeColor(schemeItem.schemeName, "mError") - })() + color: root.cacheVersion >= 0 ? (function () { + var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + var cached = root.schemeColorsCache[schemeItem.schemeName] + return (cached && cached[mode] && cached[mode].mError) || root.getSchemeColor(schemeItem.schemeName, "mError") + })() : Color.mError } } diff --git a/Modules/SetupWizard/SetupCustomizeStep.qml b/Modules/SetupWizard/SetupCustomizeStep.qml index a1854f5f..e5e0dcc1 100644 --- a/Modules/SetupWizard/SetupCustomizeStep.qml +++ b/Modules/SetupWizard/SetupCustomizeStep.qml @@ -22,13 +22,15 @@ ColumnLayout { // Beautiful header with icon RowLayout { Layout.fillWidth: true + Layout.bottomMargin: Style.marginL spacing: Style.marginM Rectangle { - width: 28 - height: 28 - radius: Style.radiusM - color: Color.mSurface + width: 40 + height: 40 + radius: Style.radiusL + color: Color.mSurfaceVariant + opacity: 0.6 NIcon { icon: "palette" @@ -46,7 +48,7 @@ ColumnLayout { text: I18n.tr("setup.customize.header") pointSize: Style.fontSizeXL font.weight: Style.fontWeightBold - color: Color.mOnSurface + color: Color.mPrimary } NText { @@ -111,11 +113,9 @@ ColumnLayout { } } - GridLayout { + RowLayout { Layout.fillWidth: true - columns: 2 - rowSpacing: Style.marginS - columnSpacing: Style.marginS + spacing: Style.marginS Repeater { model: [{ @@ -137,7 +137,7 @@ ColumnLayout { }] delegate: Rectangle { Layout.fillWidth: true - Layout.preferredHeight: 48 + Layout.preferredHeight: 40 radius: Style.radiusM border.width: 1 @@ -308,7 +308,7 @@ ColumnLayout { radius: 16 border.width: 1 Layout.preferredHeight: 32 - Layout.preferredWidth: Math.max(72, densityText.implicitWidth + Style.marginM) + Layout.preferredWidth: Math.max(90, densityText.implicitWidth + Style.marginXL * 2) property bool isActive: Settings.data.bar.density === modelData.key @@ -403,31 +403,17 @@ ColumnLayout { } } - RowLayout { + NValueSlider { Layout.fillWidth: true - spacing: Style.marginM - NText { - text: "80%" - pointSize: Style.fontSizeS - color: Color.mOnSurfaceVariant - } - NValueSlider { - Layout.fillWidth: true - from: 0.8 - to: 1.2 - stepSize: 0.05 - value: selectedScaleRatio - onMoved: function (value) { - selectedScaleRatio = value - scaleRatioChanged(value) - } - text: Math.floor(selectedScaleRatio * 100) + "%" - } - NText { - text: "120%" - pointSize: Style.fontSizeS - color: Color.mOnSurfaceVariant + from: 0.8 + to: 1.2 + stepSize: 0.05 + value: selectedScaleRatio + onMoved: function (value) { + selectedScaleRatio = value + scaleRatioChanged(value) } + text: Math.floor(selectedScaleRatio * 100) + "%" } } diff --git a/Modules/SetupWizard/SetupWallpaperStep.qml b/Modules/SetupWizard/SetupWallpaperStep.qml index fa1225c2..aa915f72 100644 --- a/Modules/SetupWizard/SetupWallpaperStep.qml +++ b/Modules/SetupWizard/SetupWallpaperStep.qml @@ -23,6 +23,7 @@ ColumnLayout { // Beautiful header with icon ColumnLayout { Layout.fillWidth: true + Layout.bottomMargin: Style.marginL spacing: Style.marginM RowLayout { @@ -50,7 +51,7 @@ ColumnLayout { text: I18n.tr("setup.wallpaper.header") pointSize: Style.fontSizeXL font.weight: Style.fontWeightBold - color: Color.mOnSurface + color: Color.mPrimary } NText { diff --git a/Modules/SetupWizard/SetupWizard.qml b/Modules/SetupWizard/SetupWizard.qml index d34d6013..63cc052f 100644 --- a/Modules/SetupWizard/SetupWizard.qml +++ b/Modules/SetupWizard/SetupWizard.qml @@ -346,7 +346,7 @@ NPanel { } NButton { - text: currentStep === totalSteps - 1 ? "🎉 All Done!" : "Continue →" + text: currentStep === totalSteps - 1 ? "All Done!" : "Continue →" Layout.preferredHeight: 44 onClicked: { if (currentStep < totalSteps - 1) { From 7d0f14a3b446e99ccb80fb01fbf6cb517e79e19b Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Wed, 15 Oct 2025 18:29:23 +0200 Subject: [PATCH 008/109] BatteryService: set chargingMode to disabled after success --- Services/BatteryService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index cf64b853..0e2bea2a 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -60,7 +60,6 @@ Singleton { if (enabled) { setChargingMode(BatteryService.ChargingMode.Full) } else { - BatteryService.chargingMode = BatteryService.ChargingMode.Disabled BatteryService.initialSetter = true ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-setup")) PanelService.getPanel("batteryPanel")?.toggle(this) @@ -194,6 +193,7 @@ Singleton { Logger.log("BatteryService", "Battery Manager uninstalled successfully") ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-success")) Settings.data.battery.chargingMode = BatteryService.chargingMode + BatteryService.chargingMode = BatteryService.ChargingMode.Disabled cleanupProcess.running = true } else { ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-failed")) From 15eee455cf872d9d0acb666e7476de32080ef443 Mon Sep 17 00:00:00 2001 From: lysec Date: Wed, 15 Oct 2025 19:11:18 +0200 Subject: [PATCH 009/109] SetupWizard: replace remote logo with local --- Assets/noctalia.svg | 65 +++++++++++++++++++++++++++++ Modules/SetupWizard/SetupWizard.qml | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 Assets/noctalia.svg diff --git a/Assets/noctalia.svg b/Assets/noctalia.svg new file mode 100644 index 00000000..10646225 --- /dev/null +++ b/Assets/noctalia.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/SetupWizard/SetupWizard.qml b/Modules/SetupWizard/SetupWizard.qml index 63cc052f..494f462f 100644 --- a/Modules/SetupWizard/SetupWizard.qml +++ b/Modules/SetupWizard/SetupWizard.qml @@ -92,7 +92,7 @@ NPanel { anchors.centerIn: parent width: 110 height: 110 - source: "https://assets.noctalia.dev/noctalia-logo.svg" + source: Qt.resolvedUrl(Quickshell.shellDir + "/Assets/noctalia.svg") fillMode: Image.PreserveAspectFit smooth: true From 8045771133e53c3e63e2debcaa7b82acf3db2a4e Mon Sep 17 00:00:00 2001 From: jacobrreed Date: Wed, 15 Oct 2025 13:58:19 -0400 Subject: [PATCH 010/109] style(colorscheme): Added Eldritch color scheme https://github.com/eldritch-theme/eldritch --- Assets/ColorScheme/Eldritch/Eldritch.json | 34 ++++++++++ .../Eldritch/terminal/foot/Eldritch-dark | 23 +++++++ .../Eldritch/terminal/foot/Eldritch-light | 22 +++++++ .../Eldritch/terminal/ghostty/Eldritch-dark | 21 +++++++ .../Eldritch/terminal/ghostty/Eldritch-light | 22 +++++++ .../terminal/kitty/Eldritch-dark.conf | 63 +++++++++++++++++++ .../terminal/kitty/Eldritch-light.conf | 63 +++++++++++++++++++ 7 files changed, 248 insertions(+) create mode 100644 Assets/ColorScheme/Eldritch/Eldritch.json create mode 100644 Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-dark create mode 100644 Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-light create mode 100644 Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-dark create mode 100644 Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-light create mode 100644 Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf create mode 100644 Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf diff --git a/Assets/ColorScheme/Eldritch/Eldritch.json b/Assets/ColorScheme/Eldritch/Eldritch.json new file mode 100644 index 00000000..3fedfeef --- /dev/null +++ b/Assets/ColorScheme/Eldritch/Eldritch.json @@ -0,0 +1,34 @@ +{ + "dark": { + "mPrimary": "#37f499", + "mOnPrimary": "#171928", + "mSecondary": "#04d1f9", + "mOnSecondary": "#171928", + "mTertiary": "#a48cf2", + "mOnTertiary": "#171928", + "mError": "#f16c75", + "mOnError": "#171928", + "mSurface": "#212337", + "mOnSurface": "#ebfafa", + "mSurfaceVariant": "#292e42", + "mOnSurfaceVariant": "#ABB4DA", + "mOutline": "#3b4261", + "mShadow": "#414868" + }, + "light": { + "mPrimary": "#37f499", + "mOnPrimary": "#171928", + "mSecondary": "#04d1f9", + "mOnSecondary": "#171928", + "mTertiary": "#a48cf2", + "mOnTertiary": "#171928", + "mError": "#f16c75", + "mOnError": "#171928", + "mSurface": "#ffffff", + "mOnSurface": "#171928", + "mSurfaceVariant": "#f2f4f8", + "mOnSurfaceVariant": "#3b4261", + "mOutline": "#b0b6c3", + "mShadow": "#e0e3e8" + } +} diff --git a/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-dark b/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-dark new file mode 100644 index 00000000..dc0c5b58 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-dark @@ -0,0 +1,23 @@ +[colors] +foreground=ebfafa +background=212337 +regular0=21222c +regular1=f9515d +regular2=37f499 +regular3=e9f941 +regular4=9071f4 +regular5=f265b5 +regular6=04d1f9 +regular7=ebfafa +bright0=7081d0 +bright1=f16c75 +bright2=69F8B3 +bright3=f1fc79 +bright4=a48cf2 +bright5=FD92CE +bright6=66e4fd +bright7=ffffff +selection-foreground=ebfafa +selection-background=bf4f8e +cursor=37f499 f8f8f2 + diff --git a/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-light b/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-light new file mode 100644 index 00000000..90602968 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/foot/Eldritch-light @@ -0,0 +1,22 @@ +[colors] +foreground=212337 +background=ebfafa +regular0=ebfafa # black → white +regular1=ba1a1a # red (darker) +regular2=1a7f4c # green (darker) +regular3=9e8c13 # yellow (darker) +regular4=3a3e8c # blue (darker) +regular5=8c2a6c # magenta (darker) +regular6=1a6c8c # cyan (darker) +regular7=212337 # white → black +bright0=7081d0 # keep as accent +bright1=d23b3b # lighter red +bright2=37f499 # original green +bright3=e9f941 # original yellow +bright4=9071f4 # original blue +bright5=f265b5 # original magenta +bright6=04d1f9 # original cyan +bright7=212337 # black +selection-foreground=212337 +selection-background=bf4f8e +cursor=1a7f4c 212337 diff --git a/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-dark b/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-dark new file mode 100644 index 00000000..e6b33937 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-dark @@ -0,0 +1,21 @@ +palette = 0=#21222c +palette = 1=#f9515d +palette = 2=#37f499 +palette = 3=#e9f941 +palette = 4=#9071f4 +palette = 5=#f265b5 +palette = 6=#04d1f9 +palette = 7=#ebfafa +palette = 8=#7081d0 +palette = 9=#f16c75 +palette = 10=#69F8B3 +palette = 11=#f1fc79 +palette = 12=#a48cf2 +palette = 13=#FD92CE +palette = 14=#66e4fd +palette = 15=#ffffff +background = 212337 +foreground = ebfafa +cursor-color = 37f499 +selection-background = bf4f8e +selection-foreground = ebfafa diff --git a/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-light b/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-light new file mode 100644 index 00000000..24b6cba5 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/ghostty/Eldritch-light @@ -0,0 +1,22 @@ +palette = 0=#f8f8fa # base background (was dark, now light) +palette = 1=#d7263d # red (less saturated) +palette = 2=#1eb980 # green (cooler, lighter) +palette = 3=#f7c948 # yellow (warmer) +palette = 4=#5e60ce # blue (lighter) +palette = 5=#c77dff # magenta (lighter) +palette = 6=#38a1db # cyan (lighter) +palette = 7=#21222c # base foreground (was background, now dark) +palette = 8=#bfc9e3 # bright black (light gray) +palette = 9=#f9515d # bright red +palette = 10=#37f499 # bright green +palette = 11=#e9f941 # bright yellow +palette = 12=#9071f4 # bright blue +palette = 13=#f265b5 # bright magenta +palette = 14=#04d1f9 # bright cyan +palette = 15=#21222c # bright white (dark for contrast) +background = f8f8fa +foreground = 21222c +cursor-color = 5e60ce +selection-background = d7e3fa +selection-foreground = 21222c + diff --git a/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf new file mode 100644 index 00000000..85a274b6 --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf @@ -0,0 +1,63 @@ +# vim:ft=kitty +## name: Eldritch +## author: jacobrreed +## license: MIT +## upstream: https://github.com/eldritch-theme/kitty +## blurb: A theme for the Ancient Ones! +foreground #ebfafa +background #212337 +selection_foreground #ebfafa +selection_background #bf4f8e + +url_color #04d1f9 + +# black +color0 #21222c +color8 #7081d0 + +# red +color1 #f9515d +color9 #f16c75 + +# green +color2 #37f499 +color10 #69F8B3 + +# yellow +color3 #e9f941 +color11 #f1fc79 + +# blue +color4 #9071f4 +color12 #a48cf2 + +# magenta +color5 #f265b5 +color13 #FD92CE + +# cyan +color6 #04d1f9 +color14 #66e4fd + +# white +color7 #ebfafa +color15 #ffffff + +# Cursor colors +cursor #37f499 +cursor_text_color #f8f8f2 + +# Tab bar colors +active_tab_foreground #212337 +active_tab_background #04d1f9 +inactive_tab_foreground #37f499 +inactive_tab_background #323449 + +# Marks +mark1_foreground #212337 +mark1_background #f9515d + +# Splits/Windows +active_border_color #a48cf2 +inactive_border_color #212337 + diff --git a/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf new file mode 100644 index 00000000..c98a3c1a --- /dev/null +++ b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf @@ -0,0 +1,63 @@ +# vim:ft=kitty +## name: Eldritch Light +## author: jacobrreed (light variant by CodeCompanion) +## license: MIT +## upstream: https://github.com/eldritch-theme/kitty +## blurb: A light variant of the Eldritch theme! + +foreground #212337 # dark text +background #ebfafa # light background +selection_foreground #ebfafa # light text on selection +selection_background #bf4f8e # magenta selection + +url_color #04d1f9 + +# black +color0 #ebfafa # background-like +color8 #7081d0 + +# red +color1 #f9515d +color9 #f16c75 + +# green +color2 #37f499 +color10 #69F8B3 + +# yellow +color3 #e9f941 +color11 #f1fc79 + +# blue +color4 #9071f4 +color12 #a48cf2 + +# magenta +color5 #f265b5 +color13 #FD92CE + +# cyan +color6 #04d1f9 +color14 #66e4fd + +# white +color7 #212337 # foreground-like +color15 #323449 + +# Cursor colors +cursor #212337 # dark cursor +cursor_text_color #ebfafa + +# Tab bar colors +active_tab_foreground #ebfafa +active_tab_background #04d1f9 +inactive_tab_foreground #37f499 +inactive_tab_background #dbe6e6 + +# Marks +mark1_foreground #ebfafa +mark1_background #f9515d + +# Splits/Windows +active_border_color #a48cf2 +inactive_border_color #ebfafa From 09b02b1a693a7afe9ee6c7382a67d3bbd218ef8f Mon Sep 17 00:00:00 2001 From: lysec Date: Wed, 15 Oct 2025 20:10:32 +0200 Subject: [PATCH 011/109] eldritch: fix kitty template --- .../terminal/kitty/Eldritch-dark.conf | 71 ++++-------------- .../terminal/kitty/Eldritch-light.conf | 73 ++++--------------- 2 files changed, 31 insertions(+), 113 deletions(-) diff --git a/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf index 85a274b6..8decd1c9 100644 --- a/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf +++ b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-dark.conf @@ -1,63 +1,22 @@ -# vim:ft=kitty -## name: Eldritch -## author: jacobrreed -## license: MIT -## upstream: https://github.com/eldritch-theme/kitty -## blurb: A theme for the Ancient Ones! -foreground #ebfafa -background #212337 -selection_foreground #ebfafa -selection_background #bf4f8e - -url_color #04d1f9 - -# black -color0 #21222c -color8 #7081d0 - -# red -color1 #f9515d -color9 #f16c75 - -# green -color2 #37f499 +color0 #21222c +color1 #f9515d +color2 #37f499 +color3 #e9f941 +color4 #9071f4 +color5 #f265b5 +color6 #04d1f9 +color7 #ebfafa +color8 #7081d0 +color9 #f16c75 color10 #69F8B3 - -# yellow -color3 #e9f941 color11 #f1fc79 - -# blue -color4 #9071f4 -color12 #a48cf2 - -# magenta -color5 #f265b5 +color12 #a48cf2 color13 #FD92CE - -# cyan -color6 #04d1f9 color14 #66e4fd - -# white -color7 #ebfafa color15 #ffffff - -# Cursor colors +background #212337 +selection_foreground #ebfafa cursor #37f499 cursor_text_color #f8f8f2 - -# Tab bar colors -active_tab_foreground #212337 -active_tab_background #04d1f9 -inactive_tab_foreground #37f499 -inactive_tab_background #323449 - -# Marks -mark1_foreground #212337 -mark1_background #f9515d - -# Splits/Windows -active_border_color #a48cf2 -inactive_border_color #212337 - +foreground #ebfafa +selection_background #bf4f8e diff --git a/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf index c98a3c1a..eb75e925 100644 --- a/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf +++ b/Assets/ColorScheme/Eldritch/terminal/kitty/Eldritch-light.conf @@ -1,63 +1,22 @@ -# vim:ft=kitty -## name: Eldritch Light -## author: jacobrreed (light variant by CodeCompanion) -## license: MIT -## upstream: https://github.com/eldritch-theme/kitty -## blurb: A light variant of the Eldritch theme! - -foreground #212337 # dark text -background #ebfafa # light background -selection_foreground #ebfafa # light text on selection -selection_background #bf4f8e # magenta selection - -url_color #04d1f9 - -# black -color0 #ebfafa # background-like -color8 #7081d0 - -# red -color1 #f9515d -color9 #f16c75 - -# green -color2 #37f499 +color0 #ebfafa +color1 #f9515d +color2 #37f499 +color3 #e9f941 +color4 #9071f4 +color5 #f265b5 +color6 #04d1f9 +color7 #212337 +color8 #7081d0 +color9 #f16c75 color10 #69F8B3 - -# yellow -color3 #e9f941 color11 #f1fc79 - -# blue -color4 #9071f4 -color12 #a48cf2 - -# magenta -color5 #f265b5 +color12 #a48cf2 color13 #FD92CE - -# cyan -color6 #04d1f9 color14 #66e4fd - -# white -color7 #212337 # foreground-like color15 #323449 - -# Cursor colors -cursor #212337 # dark cursor +background #ebfafa +selection_foreground #ebfafa +cursor #212337 cursor_text_color #ebfafa - -# Tab bar colors -active_tab_foreground #ebfafa -active_tab_background #04d1f9 -inactive_tab_foreground #37f499 -inactive_tab_background #dbe6e6 - -# Marks -mark1_foreground #ebfafa -mark1_background #f9515d - -# Splits/Windows -active_border_color #a48cf2 -inactive_border_color #ebfafa +foreground #212337 +selection_background #bf4f8e From fd33761901ca683f236b44c78f85c48059b1982b Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Wed, 15 Oct 2025 16:02:46 -0400 Subject: [PATCH 012/109] BT: fixed signal icons --- Commons/TablerIcons.qml | 6 ++++++ Modules/Bar/Bluetooth/BluetoothDevicesList.qml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Commons/TablerIcons.qml b/Commons/TablerIcons.qml index 3522e30d..a840d72f 100644 --- a/Commons/TablerIcons.qml +++ b/Commons/TablerIcons.qml @@ -128,6 +128,12 @@ Singleton { "bt-device-watch": "device-watch", "bt-device-speaker": "device-speaker", "bt-device-tv": "device-tv", + "antenna-bars-1": "antenna-bars-1", + "antenna-bars-2": "antenna-bars-2", + "antenna-bars-3": "antenna-bars-3", + "antenna-bars-4": "antenna-bars-4", + "antenna-bars-5": "antenna-bars-5", + "antenna-bars-off": "antenna-bars-off", "noctalia": "noctalia", "hyprland": "hyprland", "filepicker-folder": "folder", diff --git a/Modules/Bar/Bluetooth/BluetoothDevicesList.qml b/Modules/Bar/Bluetooth/BluetoothDevicesList.qml index 8e144c31..311bfeb4 100644 --- a/Modules/Bar/Bluetooth/BluetoothDevicesList.qml +++ b/Modules/Bar/Bluetooth/BluetoothDevicesList.qml @@ -109,7 +109,7 @@ ColumnLayout { NIcon { visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked - text: BluetoothService.getSignalIcon(modelData) + icon: BluetoothService.getSignalIcon(modelData) pointSize: Style.fontSizeXS color: getContentColor(Color.mOnSurface) } From 6fd41cd5915ee98a7c20fe04e7227ecf66efc3b1 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Wed, 15 Oct 2025 16:03:29 -0400 Subject: [PATCH 013/109] Removed invalid properties from NIcon callers --- Modules/Settings/Tabs/ColorSchemeTab.qml | 1 - Modules/Wallpaper/WallpaperPanel.qml | 1 - Services/BluetoothService.qml | 12 ++++++------ Widgets/NCheckbox.qml | 1 - Widgets/NFilePicker.qml | 1 - Widgets/NReorderCheckboxes.qml | 1 - 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index b38d521e..969c8769 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -336,7 +336,6 @@ ColumnLayout { NIcon { icon: "check" pointSize: Style.fontSizeXS - font.weight: Style.fontWeightBold color: Color.mOnSecondary anchors.centerIn: parent } diff --git a/Modules/Wallpaper/WallpaperPanel.qml b/Modules/Wallpaper/WallpaperPanel.qml index be3a526b..2dcfe18e 100644 --- a/Modules/Wallpaper/WallpaperPanel.qml +++ b/Modules/Wallpaper/WallpaperPanel.qml @@ -423,7 +423,6 @@ NPanel { NIcon { icon: "check" pointSize: Style.fontSizeM - font.weight: Style.fontWeightBold color: Color.mOnSecondary anchors.centerIn: parent } diff --git a/Services/BluetoothService.qml b/Services/BluetoothService.qml index c5e17d62..2911ee34 100644 --- a/Services/BluetoothService.qml +++ b/Services/BluetoothService.qml @@ -171,22 +171,22 @@ Singleton { function getSignalIcon(device) { if (!device || device.signalStrength === undefined || device.signalStrength <= 0) { - return "signal_cellular_null" + return "antenna-bars-off" } var signal = device.signalStrength if (signal >= 80) { - return "signal_cellular_4_bar" + return "antenna-bars-5" } if (signal >= 60) { - return "signal_cellular_3_bar" + return "antenna-bars-4" } if (signal >= 40) { - return "signal_cellular_2_bar" + return "antenna-bars-3" } if (signal >= 20) { - return "signal_cellular_1_bar" + return "antenna-bars-2" } - return "signal_cellular_0_bar" + return "antenna-bars-1" } function isDeviceBusy(device) { diff --git a/Widgets/NCheckbox.qml b/Widgets/NCheckbox.qml index f27694e6..9fe63be9 100644 --- a/Widgets/NCheckbox.qml +++ b/Widgets/NCheckbox.qml @@ -61,7 +61,6 @@ RowLayout { icon: "check" color: root.activeOnColor pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5) - font.weight: Style.fontWeightBold } MouseArea { diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 4fd7875e..cc318f02 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -624,7 +624,6 @@ Popup { NIcon { icon: "filepicker-check" pointSize: Style.fontSizeS - font.weight: Style.fontWeightBold color: Color.mOnSecondary anchors.centerIn: parent } diff --git a/Widgets/NReorderCheckboxes.qml b/Widgets/NReorderCheckboxes.qml index 9a8c1abc..a7d95927 100644 --- a/Widgets/NReorderCheckboxes.qml +++ b/Widgets/NReorderCheckboxes.qml @@ -217,7 +217,6 @@ Item { icon: "check" color: root.activeOnColor pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5) - font.weight: Style.fontWeightBold } MouseArea { From 4910497487a0ae58239851a0d9bab1c727d97dd3 Mon Sep 17 00:00:00 2001 From: Matan Bendix Shenhav Date: Wed, 15 Oct 2025 23:57:23 +0200 Subject: [PATCH 014/109] fix(flake): allow overriding NixOS systemd package --- flake.nix | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 54381f04..d2046bb1 100644 --- a/flake.nix +++ b/flake.nix @@ -49,9 +49,13 @@ nixpkgs.legacyPackages.${pkgs.system}.app2unit; }; - nixosModules.default = {pkgs, ...}: { + nixosModules.default = { + pkgs, + lib, + ... + }: { imports = [./nix/nixos-module.nix]; - services.noctalia-shell.package = self.packages.${pkgs.system}.default; + services.noctalia-shell.package = lib.mkDefault self.packages.${pkgs.system}.default; }; }; } From 2ce0992b19dca6c3d9934259936526b0c36ef0c3 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Wed, 15 Oct 2025 19:30:15 -0400 Subject: [PATCH 015/109] comment --- Commons/Settings.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 11767999..22a1c2ff 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -102,7 +102,7 @@ Singleton { // Emit the signal root.settingsLoaded() - // Last, update our local settings version + // Finally, update our local settings version adapter.settingsVersion = settingsVersion } } From 61043b78a58fe1433bed511b75366206ca816e9a Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Thu, 16 Oct 2025 09:52:47 +0800 Subject: [PATCH 016/109] feat(bar): Introduce core density scaling mechanism --- Modules/Bar/Bar.qml | 6 ++++++ Modules/Bar/Extras/BarWidgetLoader.qml | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index fa61bd95..31504433 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -102,6 +102,7 @@ Variants { model: Settings.data.bar.widgets.left delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") + barDensity: Settings.data.bar.density widgetProps: { "screen": root.modelData || null, "widgetId": modelData.id, @@ -124,6 +125,7 @@ Variants { model: Settings.data.bar.widgets.center delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") + barDensity: Settings.data.bar.density widgetProps: { "screen": root.modelData || null, "widgetId": modelData.id, @@ -147,6 +149,7 @@ Variants { model: Settings.data.bar.widgets.right delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") + barDensity: Settings.data.bar.density widgetProps: { "screen": root.modelData || null, "widgetId": modelData.id, @@ -180,6 +183,7 @@ Variants { model: Settings.data.bar.widgets.left delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") + barDensity: Settings.data.bar.density widgetProps: { "screen": root.modelData || null, "widgetId": modelData.id, @@ -204,6 +208,7 @@ Variants { model: Settings.data.bar.widgets.center delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") + barDensity: Settings.data.bar.density widgetProps: { "screen": root.modelData || null, "widgetId": modelData.id, @@ -229,6 +234,7 @@ Variants { model: Settings.data.bar.widgets.right delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") + barDensity: Settings.data.bar.density widgetProps: { "screen": root.modelData || null, "widgetId": modelData.id, diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml index fff11ebf..40610d3a 100644 --- a/Modules/Bar/Extras/BarWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -12,6 +12,19 @@ Item { property string section: widgetProps && widgetProps.section || "" property int sectionIndex: widgetProps && widgetProps.sectionWidgetIndex || 0 + property string barDensity: "default" + readonly property real scaling: (function() { + switch (barDensity) { + case "mini": + return 0.8 + case "compact": + return 0.9 + case "comfortable": + default: + return 1.0 + } + })() + // Don't reserve space unless the loaded widget is really visible implicitWidth: getImplicitSize(loader.item, "implicitWidth") implicitHeight: getImplicitSize(loader.item, "implicitHeight") @@ -40,6 +53,10 @@ Item { item[prop] = widgetProps[prop] } } + // Explicitly set scaling property + if (item.hasOwnProperty("scaling")) { + item.scaling = Qt.binding(function() { return root.scaling; }) + } } // Register this widget instance with BarService From d11a8fc3119e00b99f0632e0f308bb324e115ded Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Thu, 16 Oct 2025 09:53:00 +0800 Subject: [PATCH 017/109] feat(bar): Adapt ActiveWindow to bar density scaling --- Modules/Bar/Widgets/ActiveWindow.qml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 745f69b5..79670824 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -18,6 +18,7 @@ Item { property string section: "" property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 + property real scaling: 1.0 property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetSettings: { @@ -55,8 +56,7 @@ Item { } function calculatedVerticalDimension() { - const ratio = (Settings.data.bar.density === "mini") ? 0.67 : 0.8 - return Math.round(Style.baseWidgetSize * ratio) + return Math.round((Style.baseWidgetSize - 5) * scaling) } function getAppIcon() { @@ -107,7 +107,7 @@ Item { id: fullTitleMetrics visible: false text: windowTitle - pointSize: Style.fontSizeS + pointSize: Style.fontSizeS * scaling applyUiScale: false font.weight: Style.fontWeightMedium } @@ -125,21 +125,21 @@ Item { Item { id: mainContainer anchors.fill: parent - anchors.leftMargin: isVerticalBar ? 0 : Style.marginS - anchors.rightMargin: isVerticalBar ? 0 : Style.marginS + anchors.leftMargin: isVerticalBar ? 0 : Style.marginS * scaling + anchors.rightMargin: isVerticalBar ? 0 : Style.marginS * scaling // Horizontal layout for top/bottom bars RowLayout { id: rowLayout anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginS + spacing: Style.marginS * scaling visible: !isVerticalBar z: 1 // Window icon Item { - Layout.preferredWidth: 18 - Layout.preferredHeight: 18 + Layout.preferredWidth: 18 * scaling + Layout.preferredHeight: 18 * scaling Layout.alignment: Qt.AlignVCenter visible: showIcon @@ -254,7 +254,7 @@ Item { NText { id: titleText text: windowTitle - pointSize: Style.fontSizeS + pointSize: Style.fontSizeS * scaling applyUiScale: false font.weight: Style.fontWeightMedium verticalAlignment: Text.AlignVCenter @@ -265,7 +265,7 @@ Item { NText { text: windowTitle font: titleText.font - pointSize: Style.fontSizeS + pointSize: Style.fontSizeS * scaling applyUiScale: false verticalAlignment: Text.AlignVCenter color: Color.mOnSurface @@ -316,7 +316,7 @@ Item { // Window icon Item { - width: Style.baseWidgetSize * 0.5 + width: Style.baseWidgetSize * 0.5 * scaling height: width anchors.centerIn: parent visible: windowTitle !== "" From 04d091465e648aac07a88cabc8311b7589c8ff69 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Thu, 16 Oct 2025 09:53:08 +0800 Subject: [PATCH 018/109] feat(bar): Adapt Clock to bar density scaling --- Modules/Bar/Widgets/Clock.qml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml index cbbecd2f..11cd31cf 100644 --- a/Modules/Bar/Widgets/Clock.qml +++ b/Modules/Bar/Widgets/Clock.qml @@ -15,6 +15,7 @@ Rectangle { property string section: "" property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 + property real scaling: 1.0 property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetSettings: { @@ -66,11 +67,13 @@ Rectangle { visible: text !== "" text: modelData family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault - pointSize: { - if (repeater.model.length == 1) { - return Style.fontSizeS - } else { - return (index == 0) ? Style.fontSizeXS : Style.fontSizeXXS + Binding on pointSize { + value: { + if (repeater.model.length == 1) { + return Style.fontSizeS * scaling + } else { + return (index == 0) ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling + } } } applyUiScale: false @@ -97,8 +100,11 @@ Rectangle { visible: text !== "" text: modelData family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault - pointSize: Style.fontSizeS + Binding on pointSize { + value: Style.fontSizeS * scaling + } applyUiScale: false + font.weight: Style.fontWeightBold color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface wrapMode: Text.WordWrap From ec5a60e92c85cc8ed51b34a48e000ade5f82712c Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Thu, 16 Oct 2025 09:53:37 +0800 Subject: [PATCH 019/109] feat(bar): Adapt MediaMini to bar density scaling --- Modules/Bar/Widgets/MediaMini.qml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 922316f4..263164d5 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -17,6 +17,7 @@ Item { property string section: "" property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 + property real scaling: 1.0 property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetSettings: { @@ -76,8 +77,7 @@ Item { } function calculatedVerticalDimension() { - const ratio = (Settings.data.bar.density === "mini") ? 0.67 : 0.8 - return Math.round(Style.baseWidgetSize * ratio) + return Math.round((Style.baseWidgetSize - 5) * scaling) } // A hidden text element to safely measure the full title width @@ -86,7 +86,7 @@ Item { visible: false text: titleText.text font: titleText.font - applyUiScale: false + pointSize: Style.fontSizeS * scaling } Rectangle { @@ -102,8 +102,8 @@ Item { Item { id: mainContainer anchors.fill: parent - anchors.leftMargin: isVerticalBar ? 0 : Style.marginS - anchors.rightMargin: isVerticalBar ? 0 : Style.marginS + anchors.leftMargin: isVerticalBar ? 0 : Style.marginS * scaling + anchors.rightMargin: isVerticalBar ? 0 : Style.marginS * scaling Loader { anchors.verticalCenter: parent.verticalCenter @@ -155,7 +155,7 @@ Item { id: rowLayout anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginS + spacing: Style.marginS * scaling visible: !isVerticalBar z: 1 // Above the visualizer @@ -163,7 +163,7 @@ Item { id: windowIcon icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc" color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant - pointSize: Style.fontSizeL + pointSize: Style.fontSizeL * scaling verticalAlignment: Text.AlignVCenter Layout.alignment: Qt.AlignVCenter visible: !hasActivePlayer || (!showAlbumArt && !trackArt.visible) @@ -175,8 +175,8 @@ Item { spacing: 0 Item { - Layout.preferredWidth: Math.round(21 * Style.uiScaleRatio) - Layout.preferredHeight: Math.round(21 * Style.uiScaleRatio) + Layout.preferredWidth: Math.round(21 * Style.uiScaleRatio * scaling) + Layout.preferredHeight: Math.round(21 * Style.uiScaleRatio * scaling) NImageCircled { id: trackArt @@ -288,7 +288,7 @@ Item { NText { id: titleText text: hasActivePlayer ? getTitle() : placeholderText - pointSize: Style.fontSizeS + pointSize: Style.fontSizeS * scaling applyUiScale: false font.weight: Style.fontWeightMedium verticalAlignment: Text.AlignVCenter @@ -299,7 +299,7 @@ Item { NText { text: hasActivePlayer ? getTitle() : placeholderText font: titleText.font - applyUiScale: false + pointSize: Style.fontSizeS * scaling verticalAlignment: Text.AlignVCenter horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant @@ -359,7 +359,7 @@ Item { anchors.fill: parent icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc" color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant - pointSize: Style.fontSizeL + pointSize: Style.fontSizeL * scaling verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } From d43e4ea4009a891b36fa26be7ef774c9c39afbbd Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Wed, 15 Oct 2025 23:15:33 -0400 Subject: [PATCH 020/109] NiriOverview: more sane blur values --- Modules/Background/Overview.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index 34e673e6..a14db065 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -77,8 +77,8 @@ Variants { source: bgImage autoPaddingEnabled: false blurEnabled: true - blur: 0.48 - blurMax: 128 + blur: 1.0 + blurMax: 64 colorization: Style.opacityMedium colorizationColor: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface } From 24a5d2ff560b5afd5e2c24c7c862d1fbb52efe7d Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Wed, 15 Oct 2025 23:23:27 -0400 Subject: [PATCH 021/109] MediaCard: Spicying it up with a little blue + fallback to wallpaper display when not art available. --- Modules/ControlCenter/Cards/MediaCard.qml | 64 ++++++++++++----------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index 06891868..d5753455 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -11,17 +11,39 @@ import qs.Widgets NBox { id: root - // Background artwork that covers everything + // Wrapper - rounded rect clipper Item { anchors.fill: parent - clip: true + layer.enabled: true + layer.effect: MultiEffect { + maskEnabled: true + maskThresholdMin: 0.5 + maskSpreadAtMin: 0.0 + maskSource: ShaderEffectSource { + sourceItem: Rectangle { + width: root.width + height: root.height + radius: Style.radiusM + color: "white" + } + } + } - NImageRounded { - id: bgArtImage + // Background image that covers everything + Image { + readonly property int dim: Math.round(256 * Style.uiScaleRatio) + id: bgImage anchors.fill: parent - imagePath: MediaService.trackArtUrl - imageRadius: Style.radiusM - visible: MediaService.trackArtUrl !== "" + source: MediaService.trackArtUrl || WallpaperService.getWallpaper(Screen.name) + sourceSize: Qt.size(dim, dim) + fillMode: Image.PreserveAspectCrop + + layer.enabled: true + layer.effect: MultiEffect { + blurEnabled: true + blur: 0.25 + blurMax: 16 + } } // Dark overlay for readability @@ -40,27 +62,9 @@ NBox { border.width: 1 radius: Style.radiusM } - } - - // Background visualizer on top of the artwork - Item { - id: visualizerContainer - anchors.fill: parent - layer.enabled: true - layer.effect: MultiEffect { - maskEnabled: true - maskThresholdMin: 0.5 - maskSpreadAtMin: 0.0 - maskSource: ShaderEffectSource { - sourceItem: Rectangle { - width: root.width - height: root.height - radius: Style.radiusM - color: "white" - } - } - } + //} + // Background visualizer on top of the artwork Loader { anchors.fill: parent active: Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none" @@ -84,7 +88,7 @@ NBox { anchors.fill: parent values: CavaService.values fillColor: Color.mPrimary - opacity: MediaService.trackArtUrl !== "" ? 0.5 : 0.8 + opacity: 0.5 } } @@ -94,7 +98,7 @@ NBox { anchors.fill: parent values: CavaService.values fillColor: Color.mPrimary - opacity: MediaService.trackArtUrl !== "" ? 0.5 : 0.8 + opacity: 0.5 } } @@ -104,7 +108,7 @@ NBox { anchors.fill: parent values: CavaService.values fillColor: Color.mPrimary - opacity: MediaService.trackArtUrl !== "" ? 0.5 : 0.8 + opacity: 0.5 } } } From a1aa162e3f66d246703090220dedaba68e221903 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 16 Oct 2025 11:36:14 +0200 Subject: [PATCH 022/109] 18n: fix a lot of german tooltips --- Assets/Translations/de.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index ae9d2261..72b59d78 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1088,7 +1088,7 @@ }, "horizontal-bar": { "label": "Horizontale Statusleiste", - "description": "Tipp: Verwenden Sie \\n, um einen Zeilenumbruch zu erstellen." + "description": "Tipp: Verwenden Sie \n, um einen Zeilenumbruch zu erstellen." }, "vertical-bar": { "label": "Vertikale Statusleiste", @@ -1230,26 +1230,26 @@ "keep-awake": "Wach bleiben", "enable-keep-awake": "Wach bleiben aktivieren", "disable-keep-awake": "Wach bleiben deaktivieren", - "wallpaper-selector": "Linksklick: Hintergrundbild-Auswahl öffnen.\\nRechtsklick: Zufälliges Hintergrundbild setzen.", + "wallpaper-selector": "Linksklick: Hintergrundbild-Auswahl öffnen.\nRechtsklick: Zufälliges Hintergrundbild setzen.", "do-not-disturb-enabled": "'Nicht stören' aktiviert", "do-not-disturb-disabled": "'Nicht stören' deaktiviert", "connect-disconnect-devices": "Linksklick zum Verbinden. Rechtsklick zum Vergessen.", "set-power-profile": "'{profile}' Energieprofil setzen", "switch-to-light-mode": "Zum hellen Modus wechseln", "switch-to-dark-mode": "Zum dunklen Modus wechseln", - "night-light-disabled": "Nachtlicht ist deaktiviert.\\nLinksklick zum Wechseln des Modus.\\nRechtsklick für Einstellungen.", - "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.", + "night-light-disabled": "Nachtlicht ist deaktiviert.\nLinksklick zum Wechseln des Modus.\nRechtsklick für Einstellungen.", + "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.", "click-to-start-recording": "Klicken zum Starten der Aufnahme", "click-to-stop-recording": "Klicken zum Stoppen der Aufnahme", "open-control-center": "Kontrollzentrum öffnen", - "volume-at": "Lautstärke bei {volume}%\\nLinksklick zum Stumm-/Lautschalten. Rechtsklick für Einstellungen.\\nScrollen zum Ändern der Lautstärke.", - "microphone-volume-at": "Mikrofon-Lautstärke bei {volume}%\\nLinksklick zum Stumm-/Lautschalten. Rechtsklick für Einstellungen.\\nScrollen zum Ändern der Lautstärke.", + "volume-at": "Lautstärke bei {volume}%\nLinksklick zum Stumm-/Lautschalten. Rechtsklick für Einstellungen.\nScrollen zum Ändern der Lautstärke.", + "microphone-volume-at": "Mikrofon-Lautstärke bei {volume}%\nLinksklick zum Stumm-/Lautschalten. Rechtsklick für Einstellungen.\nScrollen zum Ändern der Lautstärke.", "manage-wifi": "WLAN verwalten", "bluetooth-devices": "Bluetooth-Geräte", - "open-notification-history-enable-dnd": "Benachrichtigungsverlauf öffnen\\nRechtsklick um 'Nicht stören' zu aktivieren.", - "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.", + "open-notification-history-disable-dnd": "Benachrichtigungsverlauf öffnen\nRechtsklick um 'Nicht stören' zu deaktivieren.", "open-wallpaper-selector": "Hintergrundbild-Auswahl öffnen", "previous-media": "Vorheriges Medium", "pause": "Pausieren", From 60afceb15ab1c9c6957567c73aca55d0570587a6 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 16 Oct 2025 11:39:43 +0200 Subject: [PATCH 023/109] Battery.qml: replace hardcoded strings with i18n (fixes #497) --- Assets/Translations/de.json | 5 +++++ Assets/Translations/en.json | 5 +++++ Assets/Translations/es.json | 5 +++++ Assets/Translations/fr.json | 5 +++++ Assets/Translations/pt.json | 5 +++++ Assets/Translations/zh-CN.json | 5 +++++ Modules/Bar/Widgets/Battery.qml | 17 +++++++++-------- 7 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 72b59d78..34bd7764 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1445,6 +1445,11 @@ "discharging-rate": "Entladerate: {rate} W.", "charging": "Wird geladen.", "discharging": "Wird entladen.", + "plugged-in": "Angeschlossen.", + "idle": "Leerlauf.", + "time-left": "Verbleibende Zeit: {time}.", + "time-until-full": "Zeit bis vollständig geladen: {time}.", + "health": "Zustand: {percent}%", "panel": { "title": "Ladeschwelle", "full": "Volle Kapazität ({percent}%)", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 7ea6c7cc..5603af49 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1616,6 +1616,11 @@ "discharging-rate": "Discharging rate: {rate} W.", "charging": "Charging.", "discharging": "Discharging.", + "plugged-in": "Plugged in.", + "idle": "Idle.", + "time-left": "Time left: {time}.", + "time-until-full": "Time until full: {time}.", + "health": "Health: {percent}%", "panel": { "title": "Charge threshold", "full": "Full capacity ({percent}%)", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index fe04122f..67bb2d0f 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1616,6 +1616,11 @@ "discharging-rate": "Tasa de descarga: {rate} W.", "charging": "Cargando.", "discharging": "Descargando.", + "plugged-in": "Conectado.", + "idle": "Inactivo.", + "time-left": "Tiempo restante: {time}.", + "time-until-full": "Tiempo hasta carga completa: {time}.", + "health": "Salud: {percent}%", "panel": { "title": "Umbral de carga", "full": "Capacidad total ({percent}%)", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 96d668d2..3533f3a1 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1616,6 +1616,11 @@ "discharging-rate": "Taux de décharge : {rate} W.", "charging": "En charge.", "discharging": "En décharge.", + "plugged-in": "Branché.", + "idle": "Inactif.", + "time-left": "Temps restant : {time}.", + "time-until-full": "Temps jusqu'à charge complète : {time}.", + "health": "État : {percent}%", "panel": { "title": "Seuil de charge", "full": "Capacité totale ({percent}%)", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 55121c93..61eba991 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1616,6 +1616,11 @@ "discharging-rate": "Taxa de descarregamento: {rate} W.", "charging": "Carregando.", "discharging": "Descarregando.", + "plugged-in": "Conectado.", + "idle": "Ocioso.", + "time-left": "Tempo restante: {time}.", + "time-until-full": "Tempo até carga completa: {time}.", + "health": "Saúde: {percent}%", "panel": { "balanced": "Balanceado ({percent}%)", "disabled": "Gerenciador de bateria desativado", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 884d6cc1..20aa587b 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1616,6 +1616,11 @@ "discharging-rate": "放电速率:{rate} W。", "charging": "正在充电。", "discharging": "正在放电。", + "plugged-in": "已接通电源。", + "idle": "空闲。", + "time-left": "剩余时间:{time}。", + "time-until-full": "充满所需时间:{time}。", + "health": "健康状况:{percent}%", "panel": { "title": "充电阈值", "full": "完全容量 ({percent}%)", diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index 47676cdb..ec307841 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -103,28 +103,29 @@ Item { return lines.join("\n") } if (!isReady || !battery.isLaptopBattery) { - return "No battery detected." + return I18n.tr("battery.no-battery-detected") } if (battery.timeToEmpty > 0) { - lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(battery.timeToEmpty)}.`) + lines.push(I18n.tr("battery.time-left", {"time": Time.formatVagueHumanReadableDuration(battery.timeToEmpty)})) } if (battery.timeToFull > 0) { - lines.push(`Time until full: ${Time.formatVagueHumanReadableDuration(battery.timeToFull)}.`) + lines.push(I18n.tr("battery.time-until-full", {"time": Time.formatVagueHumanReadableDuration(battery.timeToFull)})) } if (battery.changeRate !== undefined) { const rate = battery.changeRate if (rate > 0) { - lines.push(charging ? "Charging rate: " + rate.toFixed(2) + " W." : "Discharging rate: " + rate.toFixed(2) + " W.") + lines.push(charging ? I18n.tr("battery.charging-rate", {"rate": rate.toFixed(2)}) : I18n.tr("battery.discharging-rate", {"rate": rate.toFixed(2)})) } else if (rate < 0) { - lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W.") + lines.push(I18n.tr("battery.discharging-rate", {"rate": Math.abs(rate).toFixed(2)})) } else { - lines.push("Estimating...") + // Rate is 0 - check if plugged in (charging state) or idle + lines.push(charging ? I18n.tr("battery.plugged-in") : I18n.tr("battery.idle")) } } else { - lines.push(charging ? "Charging." : "Discharging.") + lines.push(charging ? I18n.tr("battery.charging") : I18n.tr("battery.discharging")) } if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) { - lines.push("Health: " + Math.round(battery.healthPercentage) + "%") + lines.push(I18n.tr("battery.health", {"percent": Math.round(battery.healthPercentage)})) } return lines.join("\n") } From 806a5c0c2617d5d5ab3b820129991a2cd79f0500 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 16 Oct 2025 11:43:41 +0200 Subject: [PATCH 024/109] NotificationService: fix flatpak app names again (should fix #495) --- Services/NotificationService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index 249a5845..616d0ba9 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -140,7 +140,7 @@ Singleton { "id": id, "summary": (n.summary || ""), "body": stripTags(n.body || ""), - "appName": getAppName(n.appName), + "appName": getAppName(n.appName || n.desktopEntry || ""), "urgency": n.urgency < 0 || n.urgency > 2 ? 1 : n.urgency, "expireTimeout": n.expireTimeout, "timestamp": time, From 814abf4725576979a94208508f6fe4dd76b42506 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 16 Oct 2025 12:25:22 +0200 Subject: [PATCH 025/109] LocationTab: add toggle to fully disable weather (as requested in #488) --- Assets/Translations/de.json | 7 +++++++ Assets/Translations/en.json | 18 +++++++++++----- Assets/Translations/es.json | 7 +++++++ Assets/Translations/fr.json | 7 +++++++ Assets/Translations/pt.json | 7 +++++++ Assets/Translations/zh-CN.json | 7 +++++++ Assets/settings-default.json | 1 + Commons/Settings.qml | 1 + Modules/Bar/Calendar/CalendarPanel.qml | 11 +++++++--- Modules/Bar/Widgets/Battery.qml | 22 +++++++++++++++----- Modules/ControlCenter/Cards/WeatherCard.qml | 2 +- Modules/ControlCenter/ControlCenterPanel.qml | 10 ++++++--- Modules/LockScreen/LockScreen.qml | 15 +++++++++++-- Modules/Settings/Tabs/LocationTab.qml | 9 ++++++++ Services/LocationService.qml | 6 +++++- 15 files changed, 110 insertions(+), 20 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 34bd7764..2be6197b 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -576,6 +576,10 @@ "label": "Wetter", "description": "Bevorzugte Temperatureinheit wählen." }, + "enabled": { + "label": "Wetter aktivieren", + "description": "Wetterinformationen in der gesamten Benutzeroberfläche anzeigen und Wetterdaten abrufen. Wenn deaktiviert, werden alle Wetterelemente ausgeblendet und es werden keine Netzwerkanfragen gestellt." + }, "fahrenheit": { "label": "Temperatur in Fahrenheit (°F) anzeigen", "description": "Temperatur in Fahrenheit statt Celsius anzeigen." @@ -1203,6 +1207,9 @@ "calendar": { "panel": { "week": "Woche" + }, + "weather": { + "loading": "Wetter wird geladen…" } }, "tooltips": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 5603af49..915b75d1 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -576,6 +576,10 @@ "label": "Weather", "description": "Choose your preferred temperature unit." }, + "enabled": { + "label": "Enable weather", + "description": "Show weather information throughout the interface and fetch weather data. When disabled, all weather elements will be hidden and no network requests will be made." + }, "fahrenheit": { "label": "Display temperature in Fahrenheit (°F)", "description": "Display temperature in Fahrenheit instead of Celsius." @@ -1183,11 +1187,7 @@ "scan-again": "Scan again" } }, - "calendar": { - "panel": { - "week": "Week" - } - }, + "tooltips": { "refresh": "Refresh", "close": "Close", @@ -1244,6 +1244,14 @@ "clock": { "tooltip": "Open calendar" }, + "calendar": { + "panel": { + "week": "Week" + }, + "weather": { + "loading": "Loading weather…" + } + }, "dock": { "menu": { "focus": "Focus", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 67bb2d0f..72251b3f 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -576,6 +576,10 @@ "label": "Clima", "description": "Elige tu unidad de temperatura preferida." }, + "enabled": { + "label": "Activar clima", + "description": "Mostrar información del clima en toda la interfaz y obtener datos meteorológicos. Cuando está desactivado, todos los elementos del clima estarán ocultos y no se realizarán solicitudes de red." + }, "fahrenheit": { "label": "Mostrar temperatura en Fahrenheit (°F)", "description": "Muestra la temperatura en Fahrenheit en lugar de Celsius." @@ -1186,6 +1190,9 @@ "calendar": { "panel": { "week": "Semana" + }, + "weather": { + "loading": "Cargando el clima…" } }, "tooltips": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 3533f3a1..8399a63a 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -576,6 +576,10 @@ "label": "Météo", "description": "Choisissez votre unité de température préférée." }, + "enabled": { + "label": "Activer la météo", + "description": "Afficher les informations météorologiques dans toute l'interface et récupérer les données météo. Lorsque désactivé, tous les éléments météo seront masqués et aucune requête réseau ne sera effectuée." + }, "fahrenheit": { "label": "Afficher la température en Fahrenheit (°F)", "description": "Afficher la température en Fahrenheit au lieu de Celsius." @@ -1186,6 +1190,9 @@ "calendar": { "panel": { "week": "Semaine" + }, + "weather": { + "loading": "Chargement de la météo…" } }, "tooltips": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 61eba991..c4c3ea52 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -538,6 +538,10 @@ "label": "Clima", "description": "Escolha sua unidade de temperatura preferida." }, + "enabled": { + "label": "Ativar clima", + "description": "Mostrar informações meteorológicas em toda a interface e buscar dados do clima. Quando desativado, todos os elementos do clima serão ocultados e nenhuma solicitação de rede será feita." + }, "fahrenheit": { "label": "Exibir temperatura em Fahrenheit (°F)", "description": "Exibe a temperatura em Fahrenheit em vez de Celsius." @@ -1186,6 +1190,9 @@ "calendar": { "panel": { "week": "Semana" + }, + "weather": { + "loading": "Carregando o clima…" } }, "tooltips": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 20aa587b..0116a5da 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -576,6 +576,10 @@ "label": "天气", "description": "选择您喜欢的温度单位。" }, + "enabled": { + "label": "启用天气", + "description": "在整个界面中显示天气信息并获取天气数据。禁用后,所有天气元素将被隐藏,不会进行网络请求。" + }, "fahrenheit": { "label": "以华氏度显示温度 (°F)", "description": "以华氏度而非摄氏度显示温度。" @@ -1186,6 +1190,9 @@ "calendar": { "panel": { "week": "周" + }, + "weather": { + "loading": "正在加载天气…" } }, "tooltips": { diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 8d3d6ce8..dbb01840 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -69,6 +69,7 @@ }, "location": { "name": "Tokyo", + "weatherEnabled": true, "useFahrenheit": false, "use12hourFormat": false, "showWeekNumberInCalendar": false diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 22a1c2ff..271cecb7 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -195,6 +195,7 @@ Singleton { // location property JsonObject location: JsonObject { property string name: defaultLocation + property bool weatherEnabled: true property bool useFahrenheit: false property bool use12hourFormat: false property bool showWeekNumberInCalendar: false diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 68b27ab3..b882324d 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -24,7 +24,7 @@ NPanel { readonly property int firstDayOfWeek: Qt.locale().firstDayOfWeek property bool isCurrentMonth: checkIsCurrentMonth() - readonly property bool weatherReady: (LocationService.data.weather !== null) + readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) function checkIsCurrentMonth() { return (Time.date.getMonth() === grid.month) && (Time.date.getFullYear() === grid.year) @@ -78,12 +78,13 @@ NPanel { // Weather icon and temperature ColumnLayout { + visible: Settings.data.location.weatherEnabled && weatherReady Layout.alignment: Qt.AlignVCenter spacing: Style.marginXXS NIcon { Layout.alignment: Qt.AlignHCenter - icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : "cloud" + icon: Settings.data.location.weatherEnabled && weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : "" pointSize: Style.fontSizeXXL color: Color.mOnPrimary } @@ -91,6 +92,8 @@ NPanel { NText { Layout.alignment: Qt.AlignHCenter text: { + if (!Settings.data.location.weatherEnabled) + return "" if (!weatherReady) return "" var temp = LocationService.data.weather.current_weather.temperature @@ -168,6 +171,8 @@ NPanel { NText { text: { + if (!Settings.data.location.weatherEnabled) + return "" if (!weatherReady) return I18n.tr("calendar.weather.loading") const chunks = Settings.data.location.name.split(",") @@ -323,7 +328,7 @@ NPanel { } } RowLayout { - visible: !weatherReady + visible: Settings.data.location.weatherEnabled && !weatherReady Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter NBusyIndicator {} diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index ec307841..d29c817e 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -106,17 +106,27 @@ Item { return I18n.tr("battery.no-battery-detected") } if (battery.timeToEmpty > 0) { - lines.push(I18n.tr("battery.time-left", {"time": Time.formatVagueHumanReadableDuration(battery.timeToEmpty)})) + lines.push(I18n.tr("battery.time-left", { + "time": Time.formatVagueHumanReadableDuration(battery.timeToEmpty) + })) } if (battery.timeToFull > 0) { - lines.push(I18n.tr("battery.time-until-full", {"time": Time.formatVagueHumanReadableDuration(battery.timeToFull)})) + lines.push(I18n.tr("battery.time-until-full", { + "time": Time.formatVagueHumanReadableDuration(battery.timeToFull) + })) } if (battery.changeRate !== undefined) { const rate = battery.changeRate if (rate > 0) { - lines.push(charging ? I18n.tr("battery.charging-rate", {"rate": rate.toFixed(2)}) : I18n.tr("battery.discharging-rate", {"rate": rate.toFixed(2)})) + lines.push(charging ? I18n.tr("battery.charging-rate", { + "rate": rate.toFixed(2) + }) : I18n.tr("battery.discharging-rate", { + "rate": rate.toFixed(2) + })) } else if (rate < 0) { - lines.push(I18n.tr("battery.discharging-rate", {"rate": Math.abs(rate).toFixed(2)})) + lines.push(I18n.tr("battery.discharging-rate", { + "rate": Math.abs(rate).toFixed(2) + })) } else { // Rate is 0 - check if plugged in (charging state) or idle lines.push(charging ? I18n.tr("battery.plugged-in") : I18n.tr("battery.idle")) @@ -125,7 +135,9 @@ Item { lines.push(charging ? I18n.tr("battery.charging") : I18n.tr("battery.discharging")) } if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) { - lines.push(I18n.tr("battery.health", {"percent": Math.round(battery.healthPercentage)})) + lines.push(I18n.tr("battery.health", { + "percent": Math.round(battery.healthPercentage) + })) } return lines.join("\n") } diff --git a/Modules/ControlCenter/Cards/WeatherCard.qml b/Modules/ControlCenter/Cards/WeatherCard.qml index e6b366ff..4b5fe5df 100644 --- a/Modules/ControlCenter/Cards/WeatherCard.qml +++ b/Modules/ControlCenter/Cards/WeatherCard.qml @@ -9,7 +9,7 @@ import qs.Widgets NBox { id: root - readonly property bool weatherReady: (LocationService.data.weather !== null) + readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) ColumnLayout { id: content diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 45cf6a9c..fcc13cb8 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -17,9 +17,13 @@ NPanel { var count = 0 for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) { const card = Settings.data.controlCenter.cards[i] - if (!card.enabled) { + if (!card.enabled) continue - } + + const contributes = (card.id !== "weather-card" || Settings.data.location.weatherEnabled) + if (!contributes) + continue + count++ switch (card.id) { case "profile-card": @@ -72,7 +76,7 @@ NPanel { Repeater { model: Settings.data.controlCenter.cards Loader { - active: modelData.enabled + active: modelData.enabled && (modelData.id !== "weather-card" || Settings.data.location.weatherEnabled) visible: active Layout.fillWidth: true Layout.preferredHeight: { diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 8b646e57..68d1c114 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -548,16 +548,18 @@ Loader { anchors.margins: 14 spacing: 14 - // Weather section + // Top info row RowLayout { Layout.fillWidth: true Layout.preferredHeight: 65 spacing: 18 - visible: !Settings.data.general.compactLockScreen && LocationService.coordinatesReady && LocationService.data.weather !== null + visible: !Settings.data.general.compactLockScreen // Media widget with visualizer Rectangle { Layout.preferredWidth: 220 + // Expand to take remaining space when weather is hidden + Layout.fillWidth: !(Settings.data.location.weatherEnabled && LocationService.data.weather !== null) Layout.preferredHeight: 50 radius: 25 color: Color.transparent @@ -666,6 +668,7 @@ Loader { // Current weather RowLayout { + visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null Layout.preferredWidth: 180 spacing: 8 @@ -738,6 +741,7 @@ Loader { // 3-day forecast RowLayout { + visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null Layout.preferredWidth: 260 Layout.rightMargin: 8 spacing: 4 @@ -785,9 +789,16 @@ Loader { } } + Item { + Layout.fillWidth: true + visible: !(Settings.data.location.weatherEnabled && LocationService.data.weather !== null) + Layout.preferredWidth: visible ? 1 : 0 + } + // Battery and Keyboard Layout (full mode only) ColumnLayout { Layout.preferredWidth: 60 + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter spacing: 8 // Battery diff --git a/Modules/Settings/Tabs/LocationTab.qml b/Modules/Settings/Tabs/LocationTab.qml index b4ccfe4f..7b6c340b 100644 --- a/Modules/Settings/Tabs/LocationTab.qml +++ b/Modules/Settings/Tabs/LocationTab.qml @@ -71,11 +71,20 @@ ColumnLayout { description: I18n.tr("settings.location.weather.section.description") } + NToggle { + label: I18n.tr("settings.location.weather.enabled.label") + description: I18n.tr("settings.location.weather.enabled.description") + checked: Settings.data.location.weatherEnabled + onToggled: checked => Settings.data.location.weatherEnabled = checked + } + NToggle { label: I18n.tr("settings.location.weather.fahrenheit.label") description: I18n.tr("settings.location.weather.fahrenheit.description") checked: Settings.data.location.useFahrenheit onToggled: checked => Settings.data.location.useFahrenheit = checked + enabled: Settings.data.location.weatherEnabled + opacity: Settings.data.location.weatherEnabled ? 1.0 : 0.5 } } diff --git a/Services/LocationService.qml b/Services/LocationService.qml index 79b4aa58..0e3c0c9b 100644 --- a/Services/LocationService.qml +++ b/Services/LocationService.qml @@ -69,7 +69,7 @@ Singleton { Timer { id: updateTimer interval: 20 * 1000 - running: true + running: Settings.data.location.weatherEnabled repeat: true onTriggered: { updateWeather() @@ -114,6 +114,10 @@ Singleton { // -------------------------------- function updateWeather() { + if (!Settings.data.location.weatherEnabled) { + return + } + if (isFetchingWeather) { Logger.warn("Location", "Weather is still fetching") return From dd882401cea036d75e45963530aa06e4a770f5f1 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 16 Oct 2025 12:29:06 +0200 Subject: [PATCH 026/109] LauncherTab: check if app2unit exists --- Modules/Settings/Tabs/LauncherTab.qml | 10 ++++++++-- Services/ProgramCheckerService.qml | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Modules/Settings/Tabs/LauncherTab.qml b/Modules/Settings/Tabs/LauncherTab.qml index b64f26b6..b19332a3 100644 --- a/Modules/Settings/Tabs/LauncherTab.qml +++ b/Modules/Settings/Tabs/LauncherTab.qml @@ -95,8 +95,14 @@ ColumnLayout { NToggle { label: I18n.tr("settings.launcher.settings.use-app2unit.label") description: I18n.tr("settings.launcher.settings.use-app2unit.description") - checked: Settings.data.appLauncher.useApp2Unit - onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked + checked: Settings.data.appLauncher.useApp2Unit && ProgramCheckerService.app2unitAvailable + enabled: ProgramCheckerService.app2unitAvailable + opacity: ProgramCheckerService.app2unitAvailable ? 1.0 : 0.6 + onToggled: checked => { + if (ProgramCheckerService.app2unitAvailable) { + Settings.data.appLauncher.useApp2Unit = checked + } + } } NTextInput { diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml index 268274e3..94b65066 100644 --- a/Services/ProgramCheckerService.qml +++ b/Services/ProgramCheckerService.qml @@ -18,6 +18,7 @@ Singleton { property bool fuzzelAvailable: false property bool gpuScreenRecorderAvailable: false property bool wlsunsetAvailable: false + property bool app2unitAvailable: false // Discord client auto-detection property var availableDiscordClients: [] @@ -85,6 +86,7 @@ Singleton { "ghosttyAvailable": ["which", "ghostty"], "footAvailable": ["which", "foot"], "fuzzelAvailable": ["which", "fuzzel"], + "app2unitAvailable": ["which", "app2unit"], "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"] }) From 0c42fd5bb60e054b6f40dbf8d020901aae9aff16 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 07:42:05 -0400 Subject: [PATCH 027/109] autofmt --- Modules/Settings/Tabs/LauncherTab.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/Settings/Tabs/LauncherTab.qml b/Modules/Settings/Tabs/LauncherTab.qml index b19332a3..88dfb6be 100644 --- a/Modules/Settings/Tabs/LauncherTab.qml +++ b/Modules/Settings/Tabs/LauncherTab.qml @@ -99,10 +99,10 @@ ColumnLayout { enabled: ProgramCheckerService.app2unitAvailable opacity: ProgramCheckerService.app2unitAvailable ? 1.0 : 0.6 onToggled: checked => { - if (ProgramCheckerService.app2unitAvailable) { - Settings.data.appLauncher.useApp2Unit = checked - } - } + if (ProgramCheckerService.app2unitAvailable) { + Settings.data.appLauncher.useApp2Unit = checked + } + } } NTextInput { From df7e0d026b74e35978c3b9af4d578b3588ac6e28 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 16 Oct 2025 13:43:57 +0200 Subject: [PATCH 028/109] shell: only launch setupwizard if not nixos DistroLogoService -> DistroService --- Modules/Bar/Widgets/ControlCenter.qml | 2 +- .../Bar/WidgetSettings/ControlCenterSettings.qml | 2 +- Modules/Settings/Tabs/LauncherTab.qml | 8 ++++---- Services/{DistroLogoService.qml => DistroService.qml} | 9 ++++++--- shell.qml | 4 ++++ 5 files changed, 16 insertions(+), 9 deletions(-) rename Services/{DistroLogoService.qml => DistroService.qml} (87%) diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index b0ceea69..be793115 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -56,7 +56,7 @@ NIconButton { if (customIconPath !== "") return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath if (useDistroLogo) - return DistroLogoService.osLogo + return DistroService.osLogo return "" } visible: source !== "" diff --git a/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml index 2c0383e2..66c30c11 100644 --- a/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml @@ -31,7 +31,7 @@ ColumnLayout { label: I18n.tr("bar.widget-settings.control-center.use-distro-logo.label") description: I18n.tr("bar.widget-settings.control-center.use-distro-logo.description") checked: valueUseDistroLogo - onToggled: { + onToggled: function (checked) { valueUseDistroLogo = checked if (checked) { valueCustomIconPath = "" diff --git a/Modules/Settings/Tabs/LauncherTab.qml b/Modules/Settings/Tabs/LauncherTab.qml index b19332a3..88dfb6be 100644 --- a/Modules/Settings/Tabs/LauncherTab.qml +++ b/Modules/Settings/Tabs/LauncherTab.qml @@ -99,10 +99,10 @@ ColumnLayout { enabled: ProgramCheckerService.app2unitAvailable opacity: ProgramCheckerService.app2unitAvailable ? 1.0 : 0.6 onToggled: checked => { - if (ProgramCheckerService.app2unitAvailable) { - Settings.data.appLauncher.useApp2Unit = checked - } - } + if (ProgramCheckerService.app2unitAvailable) { + Settings.data.appLauncher.useApp2Unit = checked + } + } } NTextInput { diff --git a/Services/DistroLogoService.qml b/Services/DistroService.qml similarity index 87% rename from Services/DistroLogoService.qml rename to Services/DistroService.qml index 46f531b5..337ba4c0 100644 --- a/Services/DistroLogoService.qml +++ b/Services/DistroService.qml @@ -11,6 +11,7 @@ Singleton { // Public properties property string osPretty: "" property string osLogo: "" + property bool isNixOS: false // Internal helpers function buildCandidates(name) { @@ -75,11 +76,13 @@ Singleton { return l ? l.split("=")[1].replace(/"/g, "") : "" } root.osPretty = val("PRETTY_NAME") || val("NAME") + const osId = (val("ID") || "").toLowerCase() + root.isNixOS = osId === "nixos" || (root.osPretty || "").toLowerCase().includes("nixos") const logoName = val("LOGO") if (logoName) resolveLogo(logoName) } catch (e) { - Logger.warn("DistroLogoService", "failed to read os-release", e) + Logger.warn("DistroService", "failed to read os-release", e) } } } @@ -90,10 +93,10 @@ Singleton { const p = String(stdout.text || "").trim() if (code === 0 && p) { root.osLogo = `file://${p}` - Logger.log("DistroLogoService", "found", root.osLogo) + Logger.log("DistroService", "found", root.osLogo) } else { root.osLogo = "" - Logger.warn("DistroLogoService", "none found") + Logger.warn("DistroService", "none found") } } stdout: StdioCollector {} diff --git a/shell.qml b/shell.qml index 66d15d51..4c4291c4 100644 --- a/shell.qml +++ b/shell.qml @@ -189,6 +189,10 @@ ShellRoot { function onSettingsLoaded() { // Only open the setup wizard for new users if (!Settings.data.setupCompleted) { + if (DistroService && DistroService.isNixOS) { + Settings.data.setupCompleted = true + return + } if (Settings.data.settingsVersion >= Settings.settingsVersion) { setupWizardLoader.active = true } else { From a6c0a9fc990f9911fbf6fbe739363b698e649474 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Tue, 14 Oct 2025 22:21:19 +0200 Subject: [PATCH 029/109] DarkModeService: Added for automatic dark mode switching. --- Services/DarkModeService.qml | 85 ++++++++++++++++++++++++++++++++++++ Services/LocationService.qml | 2 +- shell.qml | 1 + 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 Services/DarkModeService.qml diff --git a/Services/DarkModeService.qml b/Services/DarkModeService.qml new file mode 100644 index 00000000..afba5481 --- /dev/null +++ b/Services/DarkModeService.qml @@ -0,0 +1,85 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Services + +Singleton { + id: root + + property bool initComplete: false + property bool nextDarkModeState: false + + Connections { + target: LocationService.data + function onWeatherChanged() { + if (LocationService.data.weather !== null) { + const changes = root.collectChanges(LocationService.data.weather) + if (!root.initComplete) { + root.initComplete = true + root.resetDarkMode(changes) + } + root.scheduleChange(changes) + } + } + } + + Timer { + id: timer + onTriggered: { + Settings.data.colorSchemes.darkMode = root.nextDarkModeState + if (LocationService.data.weather !== null) { + const changes = root.collectChanges(LocationService.data.weather) + root.scheduleChange(changes) + } + } + } + + function collectChanges(weather) { + const changes = [] + for (var i = 0; i < weather.daily.sunrise.length; i++) { + changes.push({ + "time": Date.parse(weather.daily.sunrise[i]), + "darkMode": false + }) + changes.push({ + "time": Date.parse(weather.daily.sunset[i]), + "darkMode": true + }) + } + return changes + } + + function resetDarkMode(changes) { + const now = Date.now() + + // changes.findLast(change => change.time < now) // not available in QML... + let lastChange = null + for (var i = 0; i < changes.length; i++) { + if (changes[i].time < now) { + lastChange = changes[i] + } + } + + if (lastChange) { + Settings.data.colorSchemes.darkMode = lastChange.darkMode + Logger.log("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`) + } + } + + function scheduleChange(changes) { + const now = Date.now() + const nextChange = changes.find(change => change.time > now) + if (nextChange) { + root.nextDarkModeState = nextChange.darkMode + timer.interval = nextChange.time - now + timer.restart() + Logger.log("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`) + } + } + + function init() { + Logger.log("DarkModeService", "Service started") + } +} diff --git a/Services/LocationService.qml b/Services/LocationService.qml index 0e3c0c9b..729f05bf 100644 --- a/Services/LocationService.qml +++ b/Services/LocationService.qml @@ -190,7 +190,7 @@ Singleton { // -------------------------------- function _fetchWeather(latitude, longitude, errorCallback) { Logger.log("Location", "Fetching weather from api.open-meteo.com") - var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude + "&longitude=" + longitude + "¤t_weather=true¤t=relativehumidity_2m,surface_pressure&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=auto" + var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude + "&longitude=" + longitude + "¤t_weather=true¤t=relativehumidity_2m,surface_pressure&daily=temperature_2m_max,temperature_2m_min,weathercode,sunset,sunrise&timezone=auto" var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { diff --git a/shell.qml b/shell.qml index 4c4291c4..cc2837ad 100644 --- a/shell.qml +++ b/shell.qml @@ -86,6 +86,7 @@ ShellRoot { BarWidgetRegistry.init() LocationService.init() NightLightService.apply() + DarkModeService.init() FontService.init() HooksService.init() BluetoothService.init() From 838491ba33f7c2afb9e03e26ef757fb572fd6310 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 16 Oct 2025 15:07:11 +0200 Subject: [PATCH 030/109] Logger: huge cleanup --- Commons/Color.qml | 4 +- Commons/I18n.qml | 76 ++++++++----------- Commons/Icons.qml | 12 +-- Commons/Logger.qml | 25 ++++-- Commons/Settings.qml | 25 +++--- Modules/Background/Background.qml | 8 +- Modules/Background/Overview.qml | 2 +- Modules/Bar/Extras/BarWidgetLoader.qml | 4 +- Modules/Bar/Extras/TrayMenu.qml | 4 +- Modules/Bar/Widgets/ActiveWindow.qml | 10 +-- Modules/Bar/Widgets/CustomButton.qml | 6 +- Modules/Bar/Widgets/Microphone.qml | 4 +- Modules/Bar/Widgets/Taskbar.qml | 4 +- Modules/Bar/Widgets/Tray.qml | 10 +-- Modules/Bar/Widgets/Volume.qml | 2 +- Modules/Bar/Widgets/WiFi.qml | 2 +- .../ControlCenterWidgetLoader.qml | 4 +- Modules/ControlCenter/Widgets/WiFi.qml | 2 +- Modules/Dock/DockMenu.qml | 4 +- Modules/Launcher/Launcher.qml | 12 +-- .../Launcher/Plugins/ApplicationsPlugin.qml | 12 +-- Modules/Launcher/Plugins/ClipboardPlugin.qml | 4 +- Modules/LockScreen/LockContext.qml | 18 ++--- Modules/Settings/Tabs/BarTab.qml | 6 +- Modules/Settings/Tabs/ColorSchemeTab.qml | 4 +- Modules/Settings/Tabs/ControlCenterTab.qml | 6 +- Modules/SetupWizard/SetupAppearanceStep.qml | 4 +- Modules/SetupWizard/SetupWizard.qml | 6 +- Modules/Toast/ToastScreen.qml | 4 +- Services/AppThemeService.qml | 14 ++-- Services/AudioService.qml | 14 ++-- Services/BarService.qml | 10 +-- Services/BarWidgetRegistry.qml | 2 +- Services/BatteryService.qml | 30 ++++---- Services/BluetoothService.qml | 10 +-- Services/BrightnessService.qml | 16 ++-- Services/CavaService.qml | 2 +- Services/ColorSchemeService.qml | 16 ++-- Services/CompositorService.qml | 28 +++---- Services/ControlCenterWidgetRegistry.qml | 2 +- Services/DistroService.qml | 6 +- Services/FontService.qml | 6 +- Services/GitHubService.qml | 30 ++++---- Services/HooksService.qml | 10 +-- Services/HyprlandService.qml | 20 ++--- Services/IPCService.qml | 4 +- Services/IdleInhibitorService.qml | 32 ++++---- Services/KeyboardLayoutService.qml | 2 +- Services/LocationService.qml | 22 +++--- Services/MediaService.qml | 8 +- Services/NetworkService.qml | 48 ++++++------ Services/NightLightService.qml | 4 +- Services/NiriService.qml | 32 ++++---- Services/NotificationService.qml | 6 +- Services/PanelService.qml | 2 +- Services/PowerProfileService.qml | 2 +- Services/ProgramCheckerService.qml | 12 +-- Services/SwayService.qml | 20 ++--- Services/SystemStatService.qml | 12 +-- Services/TooltipService.qml | 4 +- Services/UpdateService.qml | 4 +- Services/WallpaperService.qml | 14 ++-- Widgets/NIcon.qml | 2 +- Widgets/NImageCached.qml | 2 +- Widgets/NPanel.qml | 4 +- Widgets/NSectionEditor.qml | 6 +- shell.qml | 6 +- 67 files changed, 372 insertions(+), 376 deletions(-) diff --git a/Commons/Color.qml b/Commons/Color.qml index 7d1dc68c..4b5884a5 100644 --- a/Commons/Color.qml +++ b/Commons/Color.qml @@ -106,11 +106,11 @@ Singleton { printErrors: false watchChanges: true onFileChanged: { - Logger.log("Color", "Reloading colors from disk") + Logger.i("Color", "Reloading colors from disk") reload() } onAdapterUpdated: { - Logger.log("Color", "Writing colors to disk") + Logger.i("Color", "Writing colors to disk") writeAdapter() } diff --git a/Commons/I18n.qml b/Commons/I18n.qml index 11180047..6f49f097 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -7,8 +7,6 @@ import qs.Commons Singleton { id: root - - property bool debug: false property string debugForceLanguage: "" property bool isLoaded: false @@ -36,7 +34,7 @@ Singleton { var output = stdoutCollector.text || "" parseDirectoryListing(output) } else { - Logger.error("I18n", `Failed to scan translation directory`) + Logger.e("I18n", `Failed to scan translation directory`) // Fallback to default languages availableLanguages = ["en"] detectLanguage() @@ -53,21 +51,19 @@ Singleton { try { var data = JSON.parse(text()) root.translations = data - Logger.log("I18n", `Loaded translations for "${root.langCode}"`) - if (debug) { - Logger.log("I18n", `Available root keys: ${Object.keys(data).join(", ")}`) - } + Logger.i("I18n", `Loaded translations for "${root.langCode}"`) + Logger.d("I18n", `Available root keys: ${Object.keys(data).join(", ")}`) root.isLoaded = true root.translationsLoaded() } catch (e) { - Logger.error("I18n", `Failed to parse translation file: ${e}`) + Logger.e("I18n", `Failed to parse translation file: ${e}`) setLanguage("en") } } onLoadFailed: function (error) { setLanguage("en") - Logger.error("I18n", `Failed to load translation file: ${error}`) + Logger.e("I18n", `Failed to load translation file: ${error}`) } } @@ -80,24 +76,24 @@ Singleton { try { var data = JSON.parse(text()) root.fallbackTranslations = data - Logger.log("I18n", `Loaded english fallback translations`) + Logger.d("I18n", `Loaded english fallback translations`) } catch (e) { - Logger.error("I18n", `Failed to parse fallback translation file: ${e}`) + Logger.e("I18n", `Failed to parse fallback translation file: ${e}`) } } onLoadFailed: function (error) { - Logger.error("I18n", `Failed to load fallback translation file: ${error}`) + Logger.e("I18n", `Failed to load fallback translation file: ${error}`) } } Component.onCompleted: { - Logger.log("I18n", "Service started") + Logger.i("I18n", "Service started") scanAvailableLanguages() } // ------------------------------------------- function scanAvailableLanguages() { - Logger.log("I18n", "Scanning for available translation files...") + Logger.d("I18n", "Scanning for available translation files...") directoryScanner.running = true } @@ -107,7 +103,7 @@ Singleton { try { if (!output || output.trim() === "") { - Logger.warn("I18n", "Empty directory listing output") + Logger.w("I18n", "Empty directory listing output") availableLanguages = ["en"] detectLanguage() return @@ -136,17 +132,17 @@ Singleton { } if (languages.length === 0) { - Logger.warn("I18n", "No translation files found, using fallback") + Logger.w("I18n", "No translation files found, using fallback") languages = ["en"] // Fallback } availableLanguages = languages - Logger.log("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`) + Logger.d("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`) // Detect language after scanning detectLanguage() } catch (e) { - Logger.error("I18n", `Failed to parse directory listing: ${e}`) + Logger.e("I18n", `Failed to parse directory listing: ${e}`) // Fallback to default languages availableLanguages = ["en"] detectLanguage() @@ -155,20 +151,20 @@ Singleton { // ------------------------------------------- function detectLanguage() { - Logger.log("I18n", `detectLanguage() called. Available languages: [${availableLanguages.join(', ')}]`) + Logger.d("I18n", `detectLanguage() called. Available languages: [${availableLanguages.join(', ')}]`) if (availableLanguages.length === 0) { - Logger.warn("I18n", "No available languages found") + Logger.w("I18n", "No available languages found") return } - if (debug && debugForceLanguage !== "") { - Logger.log("I18n", `Debug mode: forcing language to "${debugForceLanguage}"`) + if (Settings.isDebug && debugForceLanguage !== "") { + Logger.d("I18n", `Debug mode: forcing language to "${debugForceLanguage}"`) if (availableLanguages.includes(debugForceLanguage)) { setLanguage(debugForceLanguage) return } else { - Logger.warn("I18n", `Debug language "${debugForceLanguage}" not available in [${availableLanguages.join(', ')}]`) + Logger.w("I18n", `Debug language "${debugForceLanguage}" not available in [${availableLanguages.join(', ')}]`) } } @@ -178,7 +174,7 @@ Singleton { // Try full code match (such as zh CN, en US) if (availableLanguages.includes(fullUserLang)) { - Logger.log("I18n", `Exact match found: "${fullUserLang}"`) + Logger.d("I18n", `Exact match found: "${fullUserLang}"`) setLanguage(fullUserLang) return } @@ -186,12 +182,12 @@ Singleton { // If full code match fails, try short code matching (such as zh, en) const shortUserLang = fullUserLang.substring(0, 2) if (availableLanguages.includes(shortUserLang)) { - Logger.log("I18n", `Short code match found: "${shortUserLang}" from "${fullUserLang}"`) + Logger.d("I18n", `Short code match found: "${shortUserLang}" from "${fullUserLang}"`) setLanguage(shortUserLang) return } - Logger.log("I18n", `No match for system language: "${fullUserLang}"`) + Logger.d("I18n", `No match for system language: "${fullUserLang}"`) } // Fallback to first available language (preferably "en" if available) @@ -203,11 +199,11 @@ Singleton { function setLanguage(newLangCode) { if (newLangCode !== langCode && availableLanguages.includes(newLangCode)) { langCode = newLangCode - Logger.log("I18n", `Language set to "${langCode}"`) + Logger.i("I18n", `Language set to "${langCode}"`) languageChanged(langCode) loadTranslations() } else if (!availableLanguages.includes(newLangCode)) { - Logger.warn("I18n", `Language "${newLangCode}" is not available`) + Logger.w("I18n", `Language "${newLangCode}" is not available`) } } @@ -219,7 +215,7 @@ Singleton { const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json` fileView.path = filePath isLoaded = false - Logger.log("I18n", `Loading translations: ${langCode}`) + Logger.d("I18n", `Loading translations: ${langCode}`) // Only load fallback translations if we are not using english and english is available if (langCode !== "en" && availableLanguages.includes("en")) { @@ -271,7 +267,7 @@ Singleton { // ------------------------------------------- // Reload translations (useful for development) function reload() { - Logger.log("I18n", "Reloading translations") + Logger.d("I18n", "Reloading translations") loadTranslations() } @@ -282,9 +278,7 @@ Singleton { interpolations = {} if (!isLoaded) { - if (debug) { - Logger.warn("I18n", "Translations not loaded yet") - } + Logger.d("I18n", "Translations not loaded yet") return key } @@ -294,20 +288,12 @@ Singleton { // Look-up translation in the active language var value = translations var notFound = false - if (debug) { - Logger.log("I18n", `Looking up key: "${key}"`) - } for (var i = 0; i < keys.length; i++) { if (value && typeof value === "object" && keys[i] in value) { value = value[keys[i]] - if (debug) { - Logger.log("I18n", `Found key part "${keys[i]}"`) - } } else { - if (debug) { - Logger.warn("I18n", `Translation key "${key}" not found at part "${keys[i]}"`) - Logger.warn("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`) - } + Logger.d("I18n", `Translation key "${key}" not found at part "${keys[i]}"`) + Logger.d("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`) notFound = true break } @@ -333,9 +319,7 @@ Singleton { } if (typeof value !== "string") { - if (debug) { - Logger.warn("I18n", `Translation key "${key}" is not a string`) - } + Logger.d("I18n", `Translation key "${key}" is not a string`) return key } diff --git a/Commons/Icons.qml b/Commons/Icons.qml index 88b9b35b..919782d6 100644 --- a/Commons/Icons.qml +++ b/Commons/Icons.qml @@ -26,14 +26,14 @@ Singleton { signal fontReloaded Component.onCompleted: { - Logger.log("Icons", "Service started") + Logger.i("Icons", "Service started") loadFontWithCacheBusting() } Connections { target: Quickshell function onReloadCompleted() { - Logger.log("Icons", "Quickshell reload completed - forcing font reload") + Logger.d("Icons", "Quickshell reload completed - forcing font reload") reloadFont() } } @@ -50,7 +50,7 @@ Singleton { } function loadFontWithCacheBusting() { - Logger.log("Icons", "Loading font with cache busting") + Logger.d("Icons", "Loading font with cache busting") // Destroy old loader first if (currentFontLoader) { @@ -69,16 +69,16 @@ Singleton { // Connect to the new loader's status changes currentFontLoader.statusChanged.connect(function () { if (currentFontLoader.status === FontLoader.Ready) { - Logger.log("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")") + Logger.d("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")") fontReloaded() } else if (currentFontLoader.status === FontLoader.Error) { - Logger.error("Icons", "Font failed to load (version " + fontVersion + ")") + Logger.e("Icons", "Font failed to load (version " + fontVersion + ")") } }) } function reloadFont() { - Logger.log("Icons", "Forcing font reload...") + Logger.d("Icons", "Forcing font reload...") fontVersion++ loadFontWithCacheBusting() } diff --git a/Commons/Logger.qml b/Commons/Logger.qml index 72b62e46..5613d890 100644 --- a/Commons/Logger.qml +++ b/Commons/Logger.qml @@ -25,34 +25,45 @@ Singleton { } } - function log(...args) { + // Info log (always visible) + function i(...args) { var msg = _formatMessage(...args) console.log(msg) } - function warn(...args) { + // Debug log (only when Settings.isDebug is true) + function d(...args) { + if (Settings && Settings.isDebug) { + var msg = _formatMessage(...args) + console.log(msg) + } + } + + // Warning log + function w(...args) { var msg = _formatMessage(...args) console.warn(msg) } - function error(...args) { + // Error log + function e(...args) { var msg = _formatMessage(...args) console.error(msg) } function callStack() { var stack = _getStackTrace() - Logger.log("Debug", "--------------------------") - Logger.log("Debug", "Current call stack") + Logger.i("Debug", "--------------------------") + Logger.i("Debug", "Current call stack") // Split the stack into lines and log each one var stackLines = stack.split('\n') for (var i = 0; i < stackLines.length; i++) { var line = stackLines[i].trim() // Remove leading/trailing whitespace if (line.length > 0) { // Only log non-empty lines - Logger.log("Debug", `- ${line}`) + Logger.i("Debug", `- ${line}`) } } - Logger.log("Debug", "--------------------------") + Logger.i("Debug", "--------------------------") } } diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 271cecb7..0c5fb9c6 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -15,6 +15,7 @@ Singleton { property bool isLoaded: false property bool directoriesCreated: false property int settingsVersion: 16 + property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1" // Define our app directories // Default config directory: ~/.config/noctalia @@ -93,7 +94,7 @@ Singleton { } onLoaded: function () { if (!isLoaded) { - Logger.log("Settings", "Settings loaded") + Logger.i("Settings", "Settings loaded") upgradeSettingsData() validateMonitorConfigurations() @@ -434,7 +435,7 @@ Singleton { // Generate default settings at the root of the repo function generateDefaultSettings() { try { - Logger.log("Settings", "Generating settings-default.json") + Logger.d("Settings", "Generating settings-default.json") // Prepare a clean JSON var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter) @@ -446,7 +447,7 @@ Singleton { var base64Data = Qt.btoa(jsonData) Quickshell.execDetached(["sh", "-c", `echo "${base64Data}" | base64 -d > "${defaultPath}"`]) } catch (error) { - Logger.error("Settings", "Failed to generate default settings file: " + error) + Logger.e("Settings", "Failed to generate default settings file: " + error) } } @@ -458,8 +459,8 @@ Singleton { availableScreenNames.push(Quickshell.screens[i].name) } - Logger.log("Settings", "Available monitors: [" + availableScreenNames.join(", ") + "]") - Logger.log("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]") + Logger.d("Settings", "Available monitors: [" + availableScreenNames.join(", ") + "]") + Logger.d("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]") // Check bar monitors if (adapter.bar.monitors.length > 0) { @@ -471,15 +472,15 @@ Singleton { } } if (!hasValidBarMonitor) { - Logger.warn("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens") + Logger.w("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens") adapter.bar.monitors = [] } else { - //Logger.log("Settings", "Found valid bar monitors, keeping configuration") + //Logger.i("Settings", "Found valid bar monitors, keeping configuration") } } else { - //Logger.log("Settings", "Bar monitor list is empty, will show on all available screens") + //Logger.i("Settings", "Bar monitor list is empty, will show on all available screens") } } @@ -489,7 +490,7 @@ Singleton { function upgradeSettingsData() { // Wait for BarWidgetRegistry to be ready if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) { - Logger.warn("Settings", "BarWidgetRegistry not ready, deferring upgrade") + Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade") Qt.callLater(upgradeSettingsData) return } @@ -530,7 +531,7 @@ Singleton { for (var i = widgets.length - 1; i >= 0; i--) { var widget = widgets[i] if (!BarWidgetRegistry.hasWidget(widget.id)) { - Logger.warn(`Settings`, `Deleted invalid widget ${widget.id}`) + Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`) widgets.splice(i, 1) removedWidget = true } @@ -551,7 +552,7 @@ Singleton { } if (upgradeWidget(widget)) { - Logger.log("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget)) + Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget)) } } } @@ -577,7 +578,7 @@ Singleton { adapter.bar.widgets["right"].push(({ "id": "ControlCenter" })) - Logger.warn("Settings", "Added a ControlCenter widget to the right section") + Logger.w("Settings", "Added a ControlCenter widget to the right section") } } } diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 2bcbb04f..9da4118f 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -118,7 +118,7 @@ Variants { sourceSize: undefined onStatusChanged: { if (status === Image.Error) { - Logger.warn("Current wallpaper failed to load:", source) + Logger.w("Current wallpaper failed to load:", source) } else if (status === Image.Ready && !dimensionsCalculated) { dimensionsCalculated = true const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight) @@ -147,7 +147,7 @@ Variants { sourceSize: undefined onStatusChanged: { if (status === Image.Error) { - Logger.warn("Next wallpaper failed to load:", source) + Logger.w("Next wallpaper failed to load:", source) } else if (status === Image.Ready && !dimensionsCalculated) { dimensionsCalculated = true const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight) @@ -336,7 +336,7 @@ Variants { dim = Qt.size(h * imageAspectRatio, h) } - Logger.log("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height) + Logger.d("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height) return dim } @@ -425,7 +425,7 @@ Variants { transitionType = "fade" } - //Logger.log("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType) + //Logger.i("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType) switch (transitionType) { case "none": setWallpaperImmediate(futureWallpaper) diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index a14db065..a4009366 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -20,7 +20,7 @@ Variants { Component.onCompleted: { if (modelData) { - Logger.log("Overview", "Loading Overview component for Niri on", modelData.name) + Logger.d("Overview", "Loading Overview component for Niri on", modelData.name) } setWallpaperInitial() } diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml index fff11ebf..42890653 100644 --- a/Modules/Bar/Extras/BarWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -51,7 +51,7 @@ Item { item.onLoaded() } - //Logger.log("BarWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name) + //Logger.i("BarWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name) } Component.onDestruction: { @@ -67,7 +67,7 @@ Item { // Error handling onWidgetIdChanged: { if (widgetId && !BarWidgetRegistry.hasWidget(widgetId)) { - Logger.warn("BarWidgetLoader", "Widget not found in registry:", widgetId) + Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId) } } } diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml index c0f15e0e..00755184 100644 --- a/Modules/Bar/Extras/TrayMenu.qml +++ b/Modules/Bar/Extras/TrayMenu.qml @@ -30,12 +30,12 @@ PopupWindow { function showAt(item, x, y) { if (!item) { - Logger.warn("TrayMenu", "anchorItem is undefined, won't show menu.") + Logger.w("TrayMenu", "anchorItem is undefined, won't show menu.") return } if (!opener.children || opener.children.values.length === 0) { - //Logger.warn("TrayMenu", "Menu not ready, delaying show") + //Logger.w("TrayMenu", "Menu not ready, delaying show") Qt.callLater(() => showAt(item, x, y)) return } diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 745f69b5..17d842c1 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -72,7 +72,7 @@ Item { return iconResult } } catch (iconError) { - Logger.warn("ActiveWindow", "Error getting icon from CompositorService:", iconError) + Logger.w("ActiveWindow", "Error getting icon from CompositorService:", iconError) } } @@ -90,14 +90,14 @@ Item { } } } catch (fallbackError) { - Logger.warn("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError) + Logger.w("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError) } } } return ThemeIcons.iconFromName(fallbackIcon) } catch (e) { - Logger.warn("ActiveWindow", "Error in getAppIcon:", e) + Logger.w("ActiveWindow", "Error in getAppIcon:", e) return ThemeIcons.iconFromName(fallbackIcon) } } @@ -367,7 +367,7 @@ Item { windowIcon.source = Qt.binding(getAppIcon) windowIconVertical.source = Qt.binding(getAppIcon) } catch (e) { - Logger.warn("ActiveWindow", "Error in onActiveWindowChanged:", e) + Logger.w("ActiveWindow", "Error in onActiveWindowChanged:", e) } } function onWindowListChanged() { @@ -375,7 +375,7 @@ Item { windowIcon.source = Qt.binding(getAppIcon) windowIconVertical.source = Qt.binding(getAppIcon) } catch (e) { - Logger.warn("ActiveWindow", "Error in onWindowListChanged:", e) + Logger.w("ActiveWindow", "Error in onWindowListChanged:", e) } } } diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index af0e58cd..4015c4f4 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -112,7 +112,7 @@ Item { function onClicked() { if (leftClickExec) { Quickshell.execDetached(["sh", "-c", leftClickExec]) - Logger.log("CustomButton", `Executing command: ${leftClickExec}`) + Logger.i("CustomButton", `Executing command: ${leftClickExec}`) } else if (!hasExec) { // No script was defined, open settings var settingsPanel = PanelService.getPanel("settingsPanel") @@ -124,14 +124,14 @@ Item { function onRightClicked() { if (rightClickExec) { Quickshell.execDetached(["sh", "-c", rightClickExec]) - Logger.log("CustomButton", `Executing command: ${rightClickExec}`) + Logger.i("CustomButton", `Executing command: ${rightClickExec}`) } } function onMiddleClicked() { if (middleClickExec) { Quickshell.execDetached(["sh", "-c", middleClickExec]) - Logger.log("CustomButton", `Executing command: ${middleClickExec}`) + Logger.i("CustomButton", `Executing command: ${middleClickExec}`) } } } diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index a3058e36..86338210 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -51,7 +51,7 @@ Item { Connections { target: AudioService.source?.audio ? AudioService.source?.audio : null function onVolumeChanged() { - // Logger.log("Bar:Microphone", "onInputVolumeChanged") + // Logger.i("Bar:Microphone", "onInputVolumeChanged") if (!firstInputVolumeReceived) { // Ignore the first volume change firstInputVolumeReceived = true @@ -66,7 +66,7 @@ Item { Connections { target: AudioService.source?.audio ? AudioService.source?.audio : null function onMutedChanged() { - // Logger.log("Bar:Microphone", "onInputMutedChanged") + // Logger.i("Bar:Microphone", "onInputMutedChanged") if (!firstInputVolumeReceived) { // Ignore the first mute change firstInputVolumeReceived = true diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index 3d519dfc..fe08ba01 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -168,13 +168,13 @@ Rectangle { try { CompositorService.focusWindow(taskbarItem.modelData) } catch (error) { - Logger.error("Taskbar", "Failed to activate toplevel: " + error) + Logger.e("Taskbar", "Failed to activate toplevel: " + error) } } else if (mouse.button === Qt.RightButton) { try { CompositorService.closeWindow(taskbarItem.modelData) } catch (error) { - Logger.error("Taskbar", "Failed to close toplevel: " + error) + Logger.e("Taskbar", "Failed to close toplevel: " + error) } } } diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index f76be377..43077266 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -43,7 +43,7 @@ Rectangle { if (!str || !rule) { return false } - Logger.log("Tray", "wildCardMatch - Input str:", str, "rule:", rule) + Logger.i("Tray", "wildCardMatch - Input str:", str, "rule:", rule) // Escape all special regex characters in the rule let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') @@ -52,15 +52,15 @@ Rectangle { // Add ^ and $ to match the entire string pattern = '^' + pattern + '$' - Logger.log("Tray", "wildCardMatch - Generated pattern:", pattern) + Logger.i("Tray", "wildCardMatch - Generated pattern:", pattern) try { const regex = new RegExp(pattern, 'i') // 'i' for case-insensitive - Logger.log("Tray", "wildCardMatch - Regex test result:", regex.test(str)) + Logger.i("Tray", "wildCardMatch - Regex test result:", regex.test(str)) return regex.test(str) } catch (e) { - Logger.warn("Tray", "Invalid regex pattern for wildcard match:", rule, e.message) + Logger.w("Tray", "Invalid regex pattern for wildcard match:", rule, e.message) return false // If regex is invalid, it won't match } } @@ -252,7 +252,7 @@ Rectangle { trayMenu.item.menu = modelData.menu trayMenu.item.showAt(parent, menuX, menuY) } else { - Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set") + Logger.i("Tray", "No menu available for", modelData.id, "or trayMenu not set") } } } diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index 4254b842..10018771 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -51,7 +51,7 @@ Item { Connections { target: AudioService.sink?.audio ? AudioService.sink?.audio : null function onVolumeChanged() { - // Logger.log("Bar:Volume", "onVolumeChanged") + // Logger.i("Bar:Volume", "onVolumeChanged") if (!firstVolumeReceived) { // Ignore the first volume change firstVolumeReceived = true diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index a9156e3e..d2b996b5 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -37,7 +37,7 @@ NIconButton { } return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off" } catch (error) { - Logger.error("Wi-Fi", "Error getting icon:", error) + Logger.e("Wi-Fi", "Error getting icon:", error) return "signal_wifi_bad" } } diff --git a/Modules/ControlCenter/ControlCenterWidgetLoader.qml b/Modules/ControlCenter/ControlCenterWidgetLoader.qml index 5dbe1338..e0360d4c 100644 --- a/Modules/ControlCenter/ControlCenterWidgetLoader.qml +++ b/Modules/ControlCenter/ControlCenterWidgetLoader.qml @@ -46,7 +46,7 @@ Item { item.onLoaded() } - //Logger.log("ControlCenterWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name) + //Logger.i("ControlCenterWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name) } Component.onDestruction: { @@ -58,7 +58,7 @@ Item { // Error handling onWidgetIdChanged: { if (widgetId && !ControlCenterWidgetRegistry.hasWidget(widgetId)) { - Logger.warn("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId) + Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId) } } } diff --git a/Modules/ControlCenter/Widgets/WiFi.qml b/Modules/ControlCenter/Widgets/WiFi.qml index faaedc73..eea64700 100644 --- a/Modules/ControlCenter/Widgets/WiFi.qml +++ b/Modules/ControlCenter/Widgets/WiFi.qml @@ -23,7 +23,7 @@ NIconButtonHot { } return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off" } catch (error) { - Logger.error("Wi-Fi", "Error getting icon:", error) + Logger.e("Wi-Fi", "Error getting icon:", error) return "signal_wifi_bad" } } diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index d09ea6e7..63ad8017 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -121,7 +121,7 @@ PopupWindow { function show(item, toplevelData) { if (!item) { - Logger.warn("DockMenu", "anchorItem is undefined, won't show menu.") + Logger.w("DockMenu", "anchorItem is undefined, won't show menu.") return } @@ -176,7 +176,7 @@ PopupWindow { Qt.callLater(root.onAppClosed) } } else { - Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference") + Logger.w("DockMenu", "Cannot close app - invalid toplevel reference") } root.hide() root.requestClose() diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 271d4c4b..8aa60188 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -127,27 +127,27 @@ NPanel { const appsPlugin = Qt.createComponent("Plugins/ApplicationsPlugin.qml").createObject(this) if (appsPlugin) { registerPlugin(appsPlugin) - Logger.log("Launcher", "Registered: ApplicationsPlugin") + Logger.d("Launcher", "Registered: ApplicationsPlugin") } else { - Logger.error("Launcher", "Failed to load ApplicationsPlugin") + Logger.e("Launcher", "Failed to load ApplicationsPlugin") } // Load calculator plugin const calcPlugin = Qt.createComponent("Plugins/CalculatorPlugin.qml").createObject(this) if (calcPlugin) { registerPlugin(calcPlugin) - Logger.log("Launcher", "Registered: CalculatorPlugin") + Logger.d("Launcher", "Registered: CalculatorPlugin") } else { - Logger.error("Launcher", "Failed to load CalculatorPlugin") + Logger.e("Launcher", "Failed to load CalculatorPlugin") } // Load clipboard history plugin const clipboardPlugin = Qt.createComponent("Plugins/ClipboardPlugin.qml").createObject(this) if (clipboardPlugin) { registerPlugin(clipboardPlugin) - Logger.log("Launcher", "Registered: ClipboardPlugin") + Logger.d("Launcher", "Registered: ClipboardPlugin") } else { - Logger.error("Launcher", "Failed to load ClipboardPlugin") + Logger.e("Launcher", "Failed to load ClipboardPlugin") } } diff --git a/Modules/Launcher/Plugins/ApplicationsPlugin.qml b/Modules/Launcher/Plugins/ApplicationsPlugin.qml index 07a8b804..66ad3880 100644 --- a/Modules/Launcher/Plugins/ApplicationsPlugin.qml +++ b/Modules/Launcher/Plugins/ApplicationsPlugin.qml @@ -54,7 +54,7 @@ Item { function loadApplications() { if (typeof DesktopEntries === 'undefined') { - Logger.warn("ApplicationsPlugin", "DesktopEntries service not available") + Logger.w("ApplicationsPlugin", "DesktopEntries service not available") return } @@ -64,7 +64,7 @@ Item { app.executableName = getExecutableName(app) return app }) - Logger.log("ApplicationsPlugin", `Loaded ${entries.length} applications`) + Logger.d("ApplicationsPlugin", `Loaded ${entries.length} applications`) } function getExecutableName(app) { @@ -198,12 +198,12 @@ Item { // Ensures we are not preventing the future focusing of the app launcher.close() - Logger.log("ApplicationsPlugin", `Launching: ${app.name}`) + Logger.d("ApplicationsPlugin", `Launching: ${app.name}`) // Record usage and persist asynchronously if (Settings.data.appLauncher.sortByMostUsed) recordUsage(app) if (Settings.data.appLauncher.useApp2Unit && app.id) { - Logger.log("ApplicationsPlugin", `Using app2unit for: ${app.id}`) + Logger.d("ApplicationsPlugin", `Using app2unit for: ${app.id}`) if (app.runInTerminal) Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"]) else @@ -212,7 +212,7 @@ Item { // Fallback logic when app2unit is not used if (app.runInTerminal) { // If app.execute() fails for terminal apps, we handle it manually. - Logger.log("ApplicationsPlugin", "Executing terminal app manually: " + app.name) + Logger.d("ApplicationsPlugin", "Executing terminal app manually: " + app.name) const terminal = Settings.data.appLauncher.terminalCommand.split(" ") const command = terminal.concat(app.command) Quickshell.execDetached(command) @@ -220,7 +220,7 @@ Item { // Default execution for GUI apps app.execute() } else { - Logger.log("ApplicationsPlugin", `Could not launch: ${app.name}. No valid launch method.`) + Logger.w("ApplicationsPlugin", `Could not launch: ${app.name}. No valid launch method.`) } } } diff --git a/Modules/Launcher/Plugins/ClipboardPlugin.qml b/Modules/Launcher/Plugins/ClipboardPlugin.qml index 1969606c..0d5837bd 100644 --- a/Modules/Launcher/Plugins/ClipboardPlugin.qml +++ b/Modules/Launcher/Plugins/ClipboardPlugin.qml @@ -40,7 +40,7 @@ Item { // Initialize plugin function init() { - Logger.log("ClipboardPlugin", "Initialized") + Logger.i("ClipboardPlugin", "Initialized") // Pre-load clipboard data if service is active if (ClipboardService.active) { ClipboardService.list(100) @@ -189,7 +189,7 @@ Item { }) } - //Logger.log("ClipboardPlugin", `Returning ${results.length} results for query: "${query}"`) + //Logger.i("ClipboardPlugin", `Returning ${results.length} results for query: "${query}"`) return results } diff --git a/Modules/LockScreen/LockContext.qml b/Modules/LockScreen/LockContext.qml index b7661919..ed31d0fa 100644 --- a/Modules/LockScreen/LockContext.qml +++ b/Modules/LockScreen/LockContext.qml @@ -33,7 +33,7 @@ Scope { errorMessage = "" showFailure = false - Logger.log("LockContext", "Starting PAM authentication for user:", pam.user) + Logger.i("LockContext", "Starting PAM authentication for user:", pam.user) pam.start() } @@ -43,7 +43,7 @@ Scope { user: Quickshell.env("USER") onPamMessage: { - Logger.log("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired) + Logger.i("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired) if (messageIsError) { errorMessage = message @@ -52,26 +52,26 @@ Scope { } if (responseRequired) { - Logger.log("LockContext", "Responding to PAM with password") + Logger.i("LockContext", "Responding to PAM with password") respond(root.currentText) } } onResponseRequiredChanged: { - Logger.log("LockContext", "Response required changed:", responseRequired) + Logger.i("LockContext", "Response required changed:", responseRequired) if (responseRequired && root.unlockInProgress) { - Logger.log("LockContext", "Automatically responding to PAM") + Logger.i("LockContext", "Automatically responding to PAM") respond(root.currentText) } } onCompleted: result => { - Logger.log("LockContext", "PAM completed with result:", result) + Logger.i("LockContext", "PAM completed with result:", result) if (result === PamResult.Success) { - Logger.log("LockContext", "Authentication successful") + Logger.i("LockContext", "Authentication successful") root.unlocked() } else { - Logger.log("LockContext", "Authentication failed") + Logger.i("LockContext", "Authentication failed") errorMessage = "Authentication failed" showFailure = true root.failed() @@ -80,7 +80,7 @@ Scope { } onError: { - Logger.log("LockContext", "PAM error:", error, "message:", message) + Logger.i("LockContext", "PAM error:", error, "message:", message) errorMessage = message || "Authentication error" showFailure = true root.unlockInProgress = false diff --git a/Modules/Settings/Tabs/BarTab.qml b/Modules/Settings/Tabs/BarTab.qml index e1a9421a..a9552659 100644 --- a/Modules/Settings/Tabs/BarTab.qml +++ b/Modules/Settings/Tabs/BarTab.qml @@ -344,14 +344,14 @@ ColumnLayout { newArray.splice(toIndex, 0, item) Settings.data.bar.widgets[section] = newArray - //Logger.log("BarTab", "Widget reordered. New array:", JSON.stringify(newArray)) + //Logger.i("BarTab", "Widget reordered. New array:", JSON.stringify(newArray)) } } function _updateWidgetSettingsInSection(section, index, settings) { // Update the widget settings in the Settings data Settings.data.bar.widgets[section][index] = settings - //Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`) + //Logger.i("BarTab", `Updated widget settings for ${settings.id} in ${section} section`) } function _moveWidgetBetweenSections(fromSection, index, toSection) { @@ -369,7 +369,7 @@ ColumnLayout { targetArray.push(widget) Settings.data.bar.widgets[toSection] = targetArray - //Logger.log("BarTab", `Moved widget ${widget.id} from ${fromSection} to ${toSection}`) + //Logger.i("BarTab", `Moved widget ${widget.id} from ${fromSection} to ${toSection}`) } } diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 969c8769..f72597ea 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -121,7 +121,7 @@ ColumnLayout { var jsonData = JSON.parse(text()) root.schemeLoaded(schemeName, jsonData) } catch (e) { - Logger.warn("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e) + Logger.w("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e) root.schemeLoaded(schemeName, null) } } @@ -312,7 +312,7 @@ ColumnLayout { cursorShape: Qt.PointingHandCursor onClicked: { Settings.data.colorSchemes.useWallpaperColors = false - Logger.log("ColorSchemeTab", "Disabled wallpaper colors") + Logger.i("ColorSchemeTab", "Disabled wallpaper colors") Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml index 5fbbbf9e..372add57 100644 --- a/Modules/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -180,7 +180,7 @@ ColumnLayout { root.handleDragEnd() } onItemToggled: function (index, enabled) { - //Logger.log("ControlCenterTab", "Item", index, "toggled to", enabled) + //Logger.i("ControlCenterTab", "Item", index, "toggled to", enabled) var newModel = cardsModel.slice() newModel[index] = Object.assign({}, newModel[index], { "enabled": enabled @@ -189,7 +189,7 @@ ColumnLayout { saveCards() } onItemsReordered: function (fromIndex, toIndex) { - //Logger.log("ControlCenterTab", "Item moved from", fromIndex, "to", toIndex) + //Logger.i("ControlCenterTab", "Item moved from", fromIndex, "to", toIndex) var newModel = cardsModel.slice() var item = newModel.splice(fromIndex, 1)[0] newModel.splice(toIndex, 0, item) @@ -324,7 +324,7 @@ ColumnLayout { targetArray.push(widget) Settings.data.controlCenter.shortcuts[toSection] = targetArray - //Logger.log("BarTab", `Moved widget ${widget.id} from ${fromSection} to ${toSection}`) + //Logger.i("BarTab", `Moved widget ${widget.id} from ${fromSection} to ${toSection}`) } } diff --git a/Modules/SetupWizard/SetupAppearanceStep.qml b/Modules/SetupWizard/SetupAppearanceStep.qml index 32903902..e247e070 100644 --- a/Modules/SetupWizard/SetupAppearanceStep.qml +++ b/Modules/SetupWizard/SetupAppearanceStep.qml @@ -36,13 +36,13 @@ ColumnLayout { var value = jsonData || {} schemeColorsCache[schemeName] = value cacheVersion++ - Logger.log("SetupAppearanceStep", `Loaded scheme ${schemeName}`) + Logger.i("SetupAppearanceStep", `Loaded scheme ${schemeName}`) } Connections { target: ColorSchemeService function onSchemesChanged() { - Logger.log("SetupAppearanceStep", `Color schemes changed: ${ColorSchemeService.schemes.length}`) + Logger.i("SetupAppearanceStep", `Color schemes changed: ${ColorSchemeService.schemes.length}`) schemeColorsCache = {} cacheVersion++ } diff --git a/Modules/SetupWizard/SetupWizard.qml b/Modules/SetupWizard/SetupWizard.qml index 2a7a5c1b..d8f79bb7 100644 --- a/Modules/SetupWizard/SetupWizard.qml +++ b/Modules/SetupWizard/SetupWizard.qml @@ -363,7 +363,7 @@ NPanel { } function completeSetup() { - Logger.log("SetupWizard", "Completing setup with selected options") + Logger.i("SetupWizard", "Completing setup with selected options") if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) { Settings.data.wallpaper.directory = selectedWallpaperDirectory @@ -380,7 +380,7 @@ NPanel { Settings.data.setupCompleted = true Settings.saveImmediate() - Logger.log("SetupWizard", "Setup completed successfully") + Logger.i("SetupWizard", "Setup completed successfully") root.close() } @@ -402,7 +402,7 @@ NPanel { } Component.onCompleted: { - Logger.log("SetupWizard", "Setup wizard opened") + Logger.i("SetupWizard", "Setup wizard opened") // Initialize selections from existing settings to avoid overwriting user config if (Settings && Settings.data) { selectedScaleRatio = Settings.data.general.scaleRatio diff --git a/Modules/Toast/ToastScreen.qml b/Modules/Toast/ToastScreen.qml index ee19a625..badb00b6 100644 --- a/Modules/Toast/ToastScreen.qml +++ b/Modules/Toast/ToastScreen.qml @@ -44,11 +44,11 @@ Item { function enqueueToast(toastData) { // Safe logging - fix the substring bug var descPreview = (toastData.description || "").substring(0, 100).replace(/\n/g, " ") - Logger.log("ToastScreen", "Queuing", toastData.type, ":", toastData.message, descPreview) + Logger.i("ToastScreen", "Queuing", toastData.type, ":", toastData.message, descPreview) // Bounded queue to prevent unbounded memory growth if (messageQueue.length >= maxQueueSize) { - Logger.log("ToastScreen", "Queue full, dropping oldest toast") + Logger.i("ToastScreen", "Queue full, dropping oldest toast") messageQueue.shift() } diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index d8fa9589..dd24327a 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -81,14 +81,14 @@ Singleton { Connections { target: Settings.data.colorSchemes function onDarkModeChanged() { - Logger.log("AppThemeService", "Detected dark mode change") + Logger.i("AppThemeService", "Detected dark mode change") AppThemeService.generate() } } // -------------------------------------------------------------------------------- function init() { - Logger.log("AppThemeService", "Service started") + Logger.i("AppThemeService", "Service started") } // -------------------------------------------------------------------------------- @@ -106,10 +106,10 @@ Singleton { // -------------------------------------------------------------------------------- function generateFromWallpaper() { - // Logger.log("AppThemeService", "Generating from wallpaper on screen:", Screen.name) + // Logger.i("AppThemeService", "Generating from wallpaper on screen:", Screen.name) const wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''") if (!wp) { - Logger.error("AppThemeService", "No wallpaper found") + Logger.e("AppThemeService", "No wallpaper found") return } @@ -141,7 +141,7 @@ Singleton { // Instead, we use 'sed' to apply a custom palette to the existing matugen templates. // -------------------------------------------------------------------------------- function generateFromPredefinedScheme(schemeData) { - Logger.log("AppThemeService", "Generating templates from predefined color scheme") + Logger.i("AppThemeService", "Generating templates from predefined color scheme") handleTerminalThemes() @@ -349,7 +349,7 @@ Singleton { stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.warn("AppThemeService", "GenerateProcess stderr:", this.text) + Logger.w("AppThemeService", "GenerateProcess stderr:", this.text) } } } @@ -361,7 +361,7 @@ Singleton { stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.warn("AppThemeService", "CopyProcess stderr:", this.text) + Logger.w("AppThemeService", "CopyProcess stderr:", this.text) } } } diff --git a/Services/AudioService.qml b/Services/AudioService.qml index 739fd221..38182d8a 100644 --- a/Services/AudioService.qml +++ b/Services/AudioService.qml @@ -61,7 +61,7 @@ Singleton { function onMutedChanged() { root._muted = (sink?.audio.muted ?? true) - Logger.log("AudioService", "OnMuteChanged:", root._muted) + Logger.i("AudioService", "OnMuteChanged:", root._muted) ToastService.showNotice(I18n.tr("settings.audio.devices.output-device.label"), root._muted ? I18n.tr("toast.audio.muted") : I18n.tr("toast.audio.unmuted")) } } @@ -79,7 +79,7 @@ Singleton { function onMutedChanged() { root._inputMuted = (source?.audio.muted ?? true) - Logger.log("AudioService", "OnInputMuteChanged:", root._inputMuted) + Logger.i("AudioService", "OnInputMuteChanged:", root._inputMuted) ToastService.showNotice(I18n.tr("settings.audio.devices.input-device.label"), root._inputMuted ? I18n.tr("toast.audio.muted") : I18n.tr("toast.audio.unmuted")) } } @@ -97,9 +97,9 @@ Singleton { // Clamp it accordingly sink.audio.muted = false sink.audio.volume = Math.max(0, Math.min(Settings.data.audio.volumeOverdrive ? 1.5 : 1.0, newVolume)) - //Logger.log("AudioService", "SetVolume", sink.audio.volume); + //Logger.i("AudioService", "SetVolume", sink.audio.volume); } else { - Logger.warn("AudioService", "No sink available") + Logger.w("AudioService", "No sink available") } } @@ -107,7 +107,7 @@ Singleton { if (sink?.ready && sink?.audio) { sink.audio.muted = muted } else { - Logger.warn("AudioService", "No sink available") + Logger.w("AudioService", "No sink available") } } @@ -125,7 +125,7 @@ Singleton { source.audio.muted = false source.audio.volume = Math.max(0, Math.min(Settings.data.audio.volumeOverdrive ? 1.5 : 1.0, newVolume)) } else { - Logger.warn("AudioService", "No source available") + Logger.w("AudioService", "No source available") } } @@ -133,7 +133,7 @@ Singleton { if (source?.ready && source?.audio) { source.audio.muted = muted } else { - Logger.warn("AudioService", "No source available") + Logger.w("AudioService", "No source available") } } diff --git a/Services/BarService.qml b/Services/BarService.qml index 0a839294..db584218 100644 --- a/Services/BarService.qml +++ b/Services/BarService.qml @@ -38,14 +38,14 @@ Singleton { } Component.onCompleted: { - Logger.log("BarService", "Service started") + Logger.d("BarService", "Service started") } // Function for the Bar to call when it's ready function registerBar(screenName) { if (!readyBars[screenName]) { readyBars[screenName] = true - Logger.log("BarService", "Bar is ready on screen:", screenName) + Logger.d("BarService", "Bar is ready on screen:", screenName) barReadyChanged(screenName) } } @@ -69,7 +69,7 @@ Singleton { timerCheckVisualizer.restart() - Logger.log("BarService", "Registered widget:", key) + Logger.d("BarService", "Registered widget:", key) root.activeWidgetsChanged() } @@ -77,7 +77,7 @@ Singleton { function unregisterWidget(screenName, section, widgetId, index) { const key = [screenName, section, widgetId, index].join("|") delete widgetInstances[key] - Logger.log("BarService", "Unregistered widget:", key) + Logger.d("BarService", "Unregistered widget:", key) root.activeWidgetsChanged() } @@ -219,7 +219,7 @@ Singleton { } } } catch (e) { - Logger.error(e) + Logger.e(e) } return false } diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index 15263558..bd134591 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -215,7 +215,7 @@ Singleton { } function init() { - Logger.log("BarWidgetRegistry", "Service started") + Logger.i("BarWidgetRegistry", "Service started") } // ------------------------------ diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index 0e2bea2a..4977233c 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -69,7 +69,7 @@ Singleton { function setChargingMode(newMode) { if (newMode !== BatteryService.ChargingMode.Full && newMode !== BatteryService.ChargingMode.Balanced && newMode !== BatteryService.ChargingMode.Lifespan) { - Logger.warn("BatteryService", `Invalid charging mode set ${newMode}`) + Logger.w("BatteryService", `Invalid charging mode set ${newMode}`) return } BatteryService.chargingMode = newMode @@ -112,7 +112,7 @@ Singleton { running: false onExited: (exitCode, exitStatus) => { if (exitCode === 0) { - Logger.log("BatteryService", "Battery threshold set successfully") + Logger.i("BatteryService", "Battery threshold set successfully") if (BatteryService.initialSetter) { BatteryService.initialSetter = false return @@ -127,20 +127,20 @@ Singleton { BatteryService.runInstaller() } else { ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.set-failed")) - Logger.error("BatteryService", `Setter process failed with exit code: ${exitCode}`) + Logger.e("BatteryService", `Setter process failed with exit code: ${exitCode}`) } } stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.warn("BatteryService", "SetterProcess stderr:", this.text) + Logger.w("BatteryService", "SetterProcess stderr:", this.text) } } } stdout: StdioCollector { onStreamFinished: { if (this.text) { - Logger.log("BatteryService", "SetterProcess stdout:", this.text) + Logger.i("BatteryService", "SetterProcess stdout:", this.text) } } } @@ -170,14 +170,14 @@ Singleton { stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.warn("BatteryService", "InstallerProcess stderr:", this.text) + Logger.w("BatteryService", "InstallerProcess stderr:", this.text) } } } stdout: StdioCollector { onStreamFinished: { if (this.text) { - Logger.log("BatteryService", "InstallerProcess stdout:", this.text) + Logger.i("BatteryService", "InstallerProcess stdout:", this.text) } } } @@ -190,27 +190,27 @@ Singleton { running: false onExited: (exitCode, exitStatus) => { if (exitCode === 0) { - Logger.log("BatteryService", "Battery Manager uninstalled successfully") + Logger.i("BatteryService", "Battery Manager uninstalled successfully") ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-success")) Settings.data.battery.chargingMode = BatteryService.chargingMode BatteryService.chargingMode = BatteryService.ChargingMode.Disabled cleanupProcess.running = true } else { ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-failed")) - Logger.error("BatteryService", `Uninstaller process failed with exit code: ${exitCode}`) + Logger.e("BatteryService", `Uninstaller process failed with exit code: ${exitCode}`) } } stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.warn("BatteryService", "UninstallerProcess stderr:", this.text) + Logger.w("BatteryService", "UninstallerProcess stderr:", this.text) } } } stdout: StdioCollector { onStreamFinished: { if (this.text) { - Logger.log("BatteryService", "UninstallerProcess stdout:", this.text) + Logger.i("BatteryService", "UninstallerProcess stdout:", this.text) } } } @@ -224,22 +224,22 @@ Singleton { running: false onExited: (exitCode, exitStatus) => { if (exitCode === 0) { - Logger.log("BatteryService", "Battery Manager uninstalled successfully") + Logger.i("BatteryService", "Battery Manager uninstalled successfully") } else { - Logger.error("BatteryService", `Cleanup process failed with exit code: ${exitCode}`) + Logger.e("BatteryService", `Cleanup process failed with exit code: ${exitCode}`) } } stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.warn("BatteryService", "CleanupProcess stderr:", this.text) + Logger.w("BatteryService", "CleanupProcess stderr:", this.text) } } } stdout: StdioCollector { onStreamFinished: { if (this.text) { - Logger.log("BatteryService", "CleanupProcess stdout:", this.text) + Logger.i("BatteryService", "CleanupProcess stdout:", this.text) } } } diff --git a/Services/BluetoothService.qml b/Services/BluetoothService.qml index 2911ee34..d6c56e79 100644 --- a/Services/BluetoothService.qml +++ b/Services/BluetoothService.qml @@ -32,7 +32,7 @@ Singleton { } function init() { - Logger.log("Bluetooth", "Service initialized") + Logger.i("Bluetooth", "Service initialized") } Timer { @@ -46,11 +46,11 @@ Singleton { target: adapter function onEnabledChanged() { if (!adapter) { - Logger.warn("Bluetooth", "onEnabledChanged", "No adapter available") + Logger.w("Bluetooth", "onEnabledChanged", "No adapter available") return } - Logger.log("Bluetooth", "onEnableChanged", adapter.enabled) + Logger.d("Bluetooth", "onEnableChanged", adapter.enabled) if (adapter.enabled) { ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.enabled")) discoveryTimer.running = true @@ -225,11 +225,11 @@ Singleton { function setBluetoothEnabled(state) { if (!adapter) { - Logger.warn("Bluetooth", "No adapter available") + Logger.w("Bluetooth", "No adapter available") return } - Logger.log("Bluetooth", "SetBluetoothEnabled", state) + Logger.i("Bluetooth", "SetBluetoothEnabled", state) adapter.enabled = state } } diff --git a/Services/BrightnessService.qml b/Services/BrightnessService.qml index e4243640..f98d9215 100644 --- a/Services/BrightnessService.qml +++ b/Services/BrightnessService.qml @@ -46,7 +46,7 @@ Singleton { reloadableId: "brightness" Component.onCompleted: { - Logger.log("Brightness", "Service started") + Logger.i("Brightness", "Service started") } onMonitorsChanged: { @@ -85,7 +85,7 @@ Singleton { var ddcModel = ddcModelMatch ? ddcModelMatch.length > 0 : false var model = modelMatch ? modelMatch[1] : "Unknown" var bus = busMatch ? busMatch[1] : "Unknown" - Logger.log("Brigthness", "Detected DDC Monitor:", model, "on bus", bus, "is DDC:", !ddcModel) + Logger.i("Brigthness", "Detected DDC Monitor:", model, "on bus", bus, "is DDC:", !ddcModel) return { "model": model, "busNum": bus, @@ -141,7 +141,7 @@ Singleton { monitor.brightness = newBrightness monitor.brightnessUpdated(monitor.brightness) root.monitorBrightnessChanged(monitor, monitor.brightness) - //Logger.log("Brightness", "Refreshed brightness from system:", monitor.modelData.name, monitor.brightness) + //Logger.i("Brightness", "Refreshed brightness from system:", monitor.modelData.name, monitor.brightness) } } } @@ -190,12 +190,12 @@ Singleton { return } - //Logger.log("Brightness", "Raw brightness data for", monitor.modelData.name + ":", dataText) + //Logger.i("Brightness", "Raw brightness data for", monitor.modelData.name + ":", dataText) if (monitor.isAppleDisplay) { var val = parseInt(dataText) if (!isNaN(val)) { monitor.brightness = val / 101 - Logger.log("Brightness", "Apple display brightness:", monitor.brightness) + Logger.d("Brightness", "Apple display brightness:", monitor.brightness) } } else if (monitor.isDdc) { var parts = dataText.split(" ") @@ -204,7 +204,7 @@ Singleton { var max = parseInt(parts[4]) if (!isNaN(current) && !isNaN(max) && max > 0) { monitor.brightness = current / max - Logger.log("Brightness", "DDC brightness:", current + "/" + max + " =", monitor.brightness) + Logger.d("Brightness", "DDC brightness:", current + "/" + max + " =", monitor.brightness) } } } else { @@ -220,8 +220,8 @@ Singleton { if (!isNaN(current) && !isNaN(max) && max > 0) { monitor.maxBrightness = max monitor.brightness = current / max - Logger.log("Brightness", "Internal brightness:", current + "/" + max + " =", monitor.brightness) - Logger.log("Brightness", "Using backlight device:", monitor.backlightDevice) + Logger.d("Brightness", "Internal brightness:", current + "/" + max + " =", monitor.brightness) + Logger.d("Brightness", "Using backlight device:", monitor.backlightDevice) } } } diff --git a/Services/CavaService.qml b/Services/CavaService.qml index 07b8de7f..527f0275 100644 --- a/Services/CavaService.qml +++ b/Services/CavaService.qml @@ -40,7 +40,7 @@ Singleton { running: root.shouldRun command: ["cava", "-p", "/dev/stdin"] onRunningChanged: { - Logger.log("Cava", "Process running:", running) + Logger.d("Cava", "Process running:", running) } onExited: { stdinEnabled = true diff --git a/Services/ColorSchemeService.qml b/Services/ColorSchemeService.qml index 7e07f27a..4d78df2a 100644 --- a/Services/ColorSchemeService.qml +++ b/Services/ColorSchemeService.qml @@ -18,7 +18,7 @@ Singleton { Connections { target: Settings.data.colorSchemes function onDarkModeChanged() { - Logger.log("ColorScheme", "Detected dark mode change") + Logger.i("ColorScheme", "Detected dark mode change") if (!Settings.data.colorSchemes.useWallpaperColors && Settings.data.colorSchemes.predefinedScheme) { // Re-apply current scheme to pick the right variant applyScheme(Settings.data.colorSchemes.predefinedScheme) @@ -35,12 +35,12 @@ Singleton { function init() { // does nothing but ensure the singleton is created // do not remove - Logger.log("ColorScheme", "Service started") + Logger.i("ColorScheme", "Service started") loadColorSchemes() } function loadColorSchemes() { - Logger.log("ColorScheme", "Load colorScheme") + Logger.d("ColorScheme", "Load colorScheme") scanning = true schemes = [] // Use find command to locate all scheme.json files @@ -108,7 +108,7 @@ Singleton { }) schemes = files scanning = false - Logger.log("ColorScheme", "Listed", schemes.length, "schemes") + Logger.d("ColorScheme", "Listed", schemes.length, "schemes") // Normalize stored scheme to basename and re-apply if necessary var stored = Settings.data.colorSchemes.predefinedScheme if (stored) { @@ -121,7 +121,7 @@ Singleton { } } } else { - Logger.error("ColorScheme", "Failed to find color scheme files") + Logger.e("ColorScheme", "Failed to find color scheme files") schemes = [] scanning = false } @@ -147,14 +147,14 @@ Singleton { } } writeColorsToDisk(variant) - Logger.log("ColorScheme", "Applying color scheme:", getBasename(path)) + Logger.i("ColorScheme", "Applying color scheme:", getBasename(path)) // Generate Matugen templates if any are enabled and setting allows it if (Settings.data.colorSchemes.generateTemplatesForPredefined && hasEnabledTemplates()) { AppThemeService.generateFromPredefinedScheme(data) } } catch (e) { - Logger.error("ColorScheme", "Failed to parse scheme JSON:", path, e) + Logger.e("ColorScheme", "Failed to parse scheme JSON:", path, e) } } } @@ -171,7 +171,7 @@ Singleton { path: colorsJsonFilePath onSaved: { - // Logger.log("ColorScheme", "Colors saved") + // Logger.i("ColorScheme", "Colors saved") } JsonAdapter { id: out diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml index 5640c9d4..307dc9b7 100644 --- a/Services/CompositorService.qml +++ b/Services/CompositorService.qml @@ -100,13 +100,13 @@ Singleton { // Load cached display scales displayScales = displayCacheAdapter.displays || {} displayScalesLoaded = true - // Logger.log("CompositorService", "Loaded display scales from cache:", JSON.stringify(displayScales)) + // Logger.i("CompositorService", "Loaded display scales from cache:", JSON.stringify(displayScales)) } onLoadFailed: { // Cache doesn't exist yet, will be created on first update displayScalesLoaded = true - // Logger.log("CompositorService", "No display cache found, will create on first update") + // Logger.i("CompositorService", "No display cache found, will create on first update") } } @@ -195,7 +195,7 @@ Singleton { // Update display scales from backend function updateDisplayScales() { if (!backend || !backend.queryDisplayScales) { - Logger.warn("CompositorService", "Backend does not support display scale queries") + Logger.w("CompositorService", "Backend does not support display scale queries") return } @@ -207,7 +207,7 @@ Singleton { displayScales = scales saveDisplayScalesToCache() displayScalesChanged() - Logger.log("CompositorService", "Display scales updated") + Logger.i("CompositorService", "Display scales updated") } // Save display scales to cache @@ -261,7 +261,7 @@ Singleton { if (backend && backend.switchToWorkspace) { backend.switchToWorkspace(workspace) } else { - Logger.warn("Compositor", "No backend available for workspace switching") + Logger.w("Compositor", "No backend available for workspace switching") } } @@ -293,7 +293,7 @@ Singleton { if (backend && backend.focusWindow) { backend.focusWindow(window) } else { - Logger.warn("Compositor", "No backend available for window focus") + Logger.w("Compositor", "No backend available for window focus") } } @@ -302,43 +302,43 @@ Singleton { if (backend && backend.closeWindow) { backend.closeWindow(window) } else { - Logger.warn("Compositor", "No backend available for window closing") + Logger.w("Compositor", "No backend available for window closing") } } // Session management function logout() { if (backend && backend.logout) { - Logger.log("Compositor", "Logout requested") + Logger.i("Compositor", "Logout requested") backend.logout() } else { - Logger.warn("Compositor", "No backend available for logout") + Logger.w("Compositor", "No backend available for logout") } } function shutdown() { - Logger.log("Compositor", "Shutdown requested") + Logger.i("Compositor", "Shutdown requested") Quickshell.execDetached(["shutdown", "-h", "now"]) } function reboot() { - Logger.log("Compositor", "Reboot requested") + Logger.i("Compositor", "Reboot requested") Quickshell.execDetached(["reboot"]) } function suspend() { - Logger.log("Compositor", "Suspend requested") + Logger.i("Compositor", "Suspend requested") Quickshell.execDetached(["systemctl", "suspend"]) } function lockAndSuspend() { - Logger.log("Compositor", "Lock and suspend requested") + Logger.i("Compositor", "Lock and suspend requested") try { if (PanelService && PanelService.lockScreen && !PanelService.lockScreen.active) { PanelService.lockScreen.active = true } } catch (e) { - Logger.warn("Compositor", "Failed to activate lock screen before suspend: " + e) + Logger.w("Compositor", "Failed to activate lock screen before suspend: " + e) } // Queue suspend to the next event loop cycle to allow lock UI to render Qt.callLater(suspend) diff --git a/Services/ControlCenterWidgetRegistry.qml b/Services/ControlCenterWidgetRegistry.qml index ce2fcbf1..f00263ed 100644 --- a/Services/ControlCenterWidgetRegistry.qml +++ b/Services/ControlCenterWidgetRegistry.qml @@ -49,7 +49,7 @@ Singleton { } function init() { - Logger.log("ControlCenterWidgetRegistry", "Service started") + Logger.i("ControlCenterWidgetRegistry", "Service started") } // ------------------------------ diff --git a/Services/DistroService.qml b/Services/DistroService.qml index 337ba4c0..6f5a8944 100644 --- a/Services/DistroService.qml +++ b/Services/DistroService.qml @@ -82,7 +82,7 @@ Singleton { if (logoName) resolveLogo(logoName) } catch (e) { - Logger.warn("DistroService", "failed to read os-release", e) + Logger.w("DistroService", "failed to read os-release", e) } } } @@ -93,10 +93,10 @@ Singleton { const p = String(stdout.text || "").trim() if (code === 0 && p) { root.osLogo = `file://${p}` - Logger.log("DistroService", "found", root.osLogo) + Logger.i("DistroService", "found", root.osLogo) } else { root.osLogo = "" - Logger.warn("DistroService", "none found") + Logger.w("DistroService", "none found") } } stdout: StdioCollector {} diff --git a/Services/FontService.qml b/Services/FontService.qml index 08e07f78..1ffeee77 100644 --- a/Services/FontService.qml +++ b/Services/FontService.qml @@ -26,7 +26,7 @@ Singleton { // ------------------------------------------- function init() { - Logger.log("Font", "Service started") + Logger.i("Font", "Service started") loadFontconfigMonospaceFonts() } @@ -39,7 +39,7 @@ Singleton { if (isLoading) return - Logger.log("Font", "Loading system fonts...") + Logger.d("Font", "Loading system fonts...") isLoading = true var fontFamilies = Qt.fontFamilies() @@ -125,7 +125,7 @@ Singleton { fontsLoaded = true isLoading = false - Logger.log("Font", "Loaded", availableFonts.count, "fonts:", monospaceFonts.count, "monospace,", displayFonts.count, "display") + Logger.d("Font", "Loaded", availableFonts.count, "fonts:", monospaceFonts.count, "monospace,", displayFonts.count, "display") } function isMonospaceFont(fontName) { diff --git a/Services/GitHubService.qml b/Services/GitHubService.qml index 93982df9..1a2efc8a 100644 --- a/Services/GitHubService.qml +++ b/Services/GitHubService.qml @@ -53,11 +53,11 @@ Singleton { function loadFromCache() { const now = Time.timestamp if (!data.timestamp || (now >= data.timestamp + githubUpdateFrequency)) { - Logger.log("GitHub", "Cache expired or missing, fetching new data") + Logger.d("GitHub", "Cache expired or missing, fetching new data") fetchFromGitHub() return } - Logger.log("GitHub", "Loading cached GitHub data (age:", Math.round((now - data.timestamp) / 60), "minutes)") + Logger.d("GitHub", "Loading cached GitHub data (age:", Math.round((now - data.timestamp) / 60), "minutes)") if (data.version) { root.latestVersion = data.version @@ -70,7 +70,7 @@ Singleton { // -------------------------------- function fetchFromGitHub() { if (isFetchingData) { - Logger.warn("GitHub", "GitHub data is still fetching") + Logger.w("GitHub", "GitHub data is still fetching") return } @@ -82,8 +82,8 @@ Singleton { // -------------------------------- function saveData() { data.timestamp = Time.timestamp - Logger.log("GitHub", "Saving data to cache file:", githubDataFile) - Logger.log("GitHub", "Data to save - version:", data.version, "contributors:", data.contributors.length) + Logger.d("GitHub", "Saving data to cache file:", githubDataFile) + Logger.d("GitHub", "Data to save - version:", data.version, "contributors:", data.contributors.length) // Ensure cache directory exists Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]) @@ -91,7 +91,7 @@ Singleton { Qt.callLater(() => { // Use direct ID reference to the FileView githubDataFileView.writeAdapter() - Logger.log("GitHub", "Cache file written successfully") + Logger.d("GitHub", "Cache file written successfully") }) } @@ -120,15 +120,15 @@ Singleton { const version = data.tag_name root.data.version = version root.latestVersion = version - Logger.log("GitHub", "Latest version fetched from GitHub:", version) + Logger.d("GitHub", "Latest version fetched from GitHub:", version) } else { - Logger.log("GitHub", "No tag_name in GitHub response") + Logger.w("GitHub", "No tag_name in GitHub response") } } else { - Logger.log("GitHub", "Empty response from GitHub API") + Logger.w("GitHub", "Empty response from GitHub API") } } catch (e) { - Logger.error("GitHub", "Failed to parse version:", e) + Logger.e("GitHub", "Failed to parse version:", e) } // Check if both processes are done @@ -146,20 +146,20 @@ Singleton { onStreamFinished: { try { const response = text - Logger.log("GitHub", "Raw contributors response length:", response ? response.length : 0) + Logger.d("GitHub", "Raw contributors response length:", response ? response.length : 0) if (response && response.trim()) { const data = JSON.parse(response) - Logger.log("GitHub", "Parsed contributors data type:", typeof data, "length:", Array.isArray(data) ? data.length : "not array") + Logger.d("GitHub", "Parsed contributors data type:", typeof data, "length:", Array.isArray(data) ? data.length : "not array") root.data.contributors = data || [] root.contributors = root.data.contributors - Logger.log("GitHub", "Contributors fetched from GitHub:", root.contributors.length) + Logger.d("GitHub", "Contributors fetched from GitHub:", root.contributors.length) } else { - Logger.log("GitHub", "Empty response from GitHub API for contributors") + Logger.w("GitHub", "Empty response from GitHub API for contributors") root.data.contributors = [] root.contributors = [] } } catch (e) { - Logger.error("GitHub", "Failed to parse contributors:", e) + Logger.e("GitHub", "Failed to parse contributors:", e) root.data.contributors = [] root.contributors = [] } diff --git a/Services/HooksService.qml b/Services/HooksService.qml index 4f04124a..6f6db9d3 100644 --- a/Services/HooksService.qml +++ b/Services/HooksService.qml @@ -38,9 +38,9 @@ Singleton { let command = script.replace(/\$1/g, wallpaperPath) command = command.replace(/\$2/g, screenName || "") Quickshell.execDetached(["sh", "-c", command]) - Logger.log("HooksService", `Executed wallpaper hook: ${command}`) + Logger.d("HooksService", `Executed wallpaper hook: ${command}`) } catch (e) { - Logger.error("HooksService", `Failed to execute wallpaper hook: ${e}`) + Logger.e("HooksService", `Failed to execute wallpaper hook: ${e}`) } } @@ -58,14 +58,14 @@ Singleton { try { const command = script.replace(/\$1/g, isDarkMode ? "true" : "false") Quickshell.execDetached(["sh", "-c", command]) - Logger.log("HooksService", `Executed dark mode hook: ${command}`) + Logger.d("HooksService", `Executed dark mode hook: ${command}`) } catch (e) { - Logger.error("HooksService", `Failed to execute dark mode hook: ${e}`) + Logger.e("HooksService", `Failed to execute dark mode hook: ${e}`) } } // Initialize the service function init() { - Logger.log("HooksService", "Service initialized") + Logger.i("HooksService", "Service initialized") } } diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index 0cafa965..25b4642f 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -45,9 +45,9 @@ Item { queryDisplayScales() }) initialized = true - Logger.log("HyprlandService", "Initialized successfully") + Logger.i("HyprlandService", "Initialized successfully") } catch (e) { - Logger.error("HyprlandService", "Failed to initialize:", e) + Logger.e("HyprlandService", "Failed to initialize:", e) } } @@ -74,7 +74,7 @@ Item { onExited: function (exitCode) { if (exitCode !== 0 || !accumulatedOutput) { - Logger.error("HyprlandService", "Failed to query monitors, exit code:", exitCode) + Logger.e("HyprlandService", "Failed to query monitors, exit code:", exitCode) accumulatedOutput = "" return } @@ -105,7 +105,7 @@ Item { CompositorService.onDisplayScalesUpdated(scales) } } catch (e) { - Logger.error("HyprlandService", "Failed to parse monitors:", e) + Logger.e("HyprlandService", "Failed to parse monitors:", e) } finally { // Clear accumulated output for next query accumulatedOutput = "" @@ -152,7 +152,7 @@ Item { workspaces.append(wsData) } } catch (e) { - Logger.error("HyprlandService", "Error updating workspaces:", e) + Logger.e("HyprlandService", "Error updating workspaces:", e) } } @@ -227,7 +227,7 @@ Item { activeWindowChanged() } } catch (e) { - Logger.error("HyprlandService", "Error updating windows:", e) + Logger.e("HyprlandService", "Error updating windows:", e) } } @@ -345,7 +345,7 @@ Item { try { Hyprland.dispatch(`workspace ${workspace.idx}`) } catch (e) { - Logger.error("HyprlandService", "Failed to switch workspace:", e) + Logger.e("HyprlandService", "Failed to switch workspace:", e) } } @@ -353,7 +353,7 @@ Item { try { Hyprland.dispatch(`focuswindow address:0x${window.id.toString()}`) } catch (e) { - Logger.error("HyprlandService", "Failed to switch window:", e) + Logger.e("HyprlandService", "Failed to switch window:", e) } } @@ -361,7 +361,7 @@ Item { try { Hyprland.dispatch(`killwindow address:0x${window.id}`) } catch (e) { - Logger.error("HyprlandService", "Failed to close window:", e) + Logger.e("HyprlandService", "Failed to close window:", e) } } @@ -369,7 +369,7 @@ Item { try { Quickshell.execDetached(["hyprctl", "dispatch", "exit"]) } catch (e) { - Logger.error("HyprlandService", "Failed to logout:", e) + Logger.e("HyprlandService", "Failed to logout:", e) } } } diff --git a/Services/IPCService.qml b/Services/IPCService.qml index e0f2eac0..67eb2b1b 100644 --- a/Services/IPCService.qml +++ b/Services/IPCService.qml @@ -230,7 +230,7 @@ Item { function seekRelative(offset: string) { var offsetVal = parseFloat(position) if (Number.isNaN(offsetVal)) { - Logger.warn("Media", "Argument to ipc call 'media seekRelative' must be a number") + Logger.w("Media", "Argument to ipc call 'media seekRelative' must be a number") return } MediaService.seekRelative(offsetVal) @@ -239,7 +239,7 @@ Item { function seekByRatio(position: string) { var positionVal = parseFloat(position) if (Number.isNaN(positionVal)) { - Logger.warn("Media", "Argument to ipc call 'media seekByRatio' must be a number") + Logger.w("Media", "Argument to ipc call 'media seekByRatio' must be a number") return } MediaService.seekByRatio(positionVal) diff --git a/Services/IdleInhibitorService.qml b/Services/IdleInhibitorService.qml index a1f789d0..964430c8 100644 --- a/Services/IdleInhibitorService.qml +++ b/Services/IdleInhibitorService.qml @@ -17,13 +17,13 @@ Singleton { property string strategy: "systemd" // "systemd", "wayland", or "auto" Component.onCompleted: { - Logger.log("IdleInhibitor", "Service started") + Logger.i("IdleInhibitor", "Service started") detectStrategy() // Restore previous state from settings if (Settings.data.ui.idleInhibitorEnabled) { addInhibitor("manual", "Restored from previous session") - Logger.log("IdleInhibitor", "Restored previous manual inhibition state") + Logger.d("IdleInhibitor", "Restored previous manual inhibition state") } } @@ -34,7 +34,7 @@ Singleton { try { var systemdResult = Quickshell.execDetached(["which", "systemd-inhibit"]) strategy = "systemd" - Logger.log("IdleInhibitor", "Using systemd-inhibit strategy") + Logger.d("IdleInhibitor", "Using systemd-inhibit strategy") return } catch (e) { @@ -44,14 +44,14 @@ Singleton { try { var waylandResult = Quickshell.execDetached(["which", "wayhibitor"]) strategy = "wayland" - Logger.log("IdleInhibitor", "Using wayhibitor strategy") + Logger.d("IdleInhibitor", "Using wayhibitor strategy") return } catch (e) { // wayhibitor not found } - Logger.warn("IdleInhibitor", "No suitable inhibitor found - will try systemd as fallback") + Logger.w("IdleInhibitor", "No suitable inhibitor found - will try systemd as fallback") strategy = "systemd" // Fallback to systemd even if not detected } } @@ -59,13 +59,13 @@ Singleton { // Add an inhibitor function addInhibitor(id, reason = "Application request") { if (activeInhibitors.includes(id)) { - Logger.warn("IdleInhibitor", "Inhibitor already active:", id) + Logger.w("IdleInhibitor", "Inhibitor already active:", id) return false } activeInhibitors.push(id) updateInhibition(reason) - Logger.log("IdleInhibitor", "Added inhibitor:", id) + Logger.d("IdleInhibitor", "Added inhibitor:", id) return true } @@ -73,13 +73,13 @@ Singleton { function removeInhibitor(id) { const index = activeInhibitors.indexOf(id) if (index === -1) { - Logger.warn("IdleInhibitor", "Inhibitor not found:", id) + Logger.w("IdleInhibitor", "Inhibitor not found:", id) return false } activeInhibitors.splice(index, 1) updateInhibition() - Logger.log("IdleInhibitor", "Removed inhibitor:", id) + Logger.d("IdleInhibitor", "Removed inhibitor:", id) return true } @@ -108,12 +108,12 @@ Singleton { } else if (strategy === "wayland") { startWaylandInhibition() } else { - Logger.warn("IdleInhibitor", "No inhibition strategy available") + Logger.w("IdleInhibitor", "No inhibition strategy available") return } isInhibited = true - Logger.log("IdleInhibitor", "Started inhibition:", reason) + Logger.i("IdleInhibitor", "Started inhibition:", reason) } // Stop system inhibition @@ -126,7 +126,7 @@ Singleton { } isInhibited = false - Logger.log("IdleInhibitor", "Stopped inhibition") + Logger.i("IdleInhibitor", "Stopped inhibition") } // Systemd inhibition using systemd-inhibit @@ -148,13 +148,13 @@ Singleton { onExited: function (exitCode, exitStatus) { if (isInhibited) { - Logger.warn("IdleInhibitor", "Inhibitor process exited unexpectedly:", exitCode) + Logger.w("IdleInhibitor", "Inhibitor process exited unexpectedly:", exitCode) isInhibited = false } } onStarted: function () { - Logger.log("IdleInhibitor", "Inhibitor process started successfully") + Logger.d("IdleInhibitor", "Inhibitor process started successfully") } } @@ -164,13 +164,13 @@ Singleton { removeInhibitor("manual") Settings.data.ui.idleInhibitorEnabled = false ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("toast.keep-awake.disabled")) - Logger.log("IdleInhibitor", "Manual inhibition disabled and saved to settings") + Logger.i("IdleInhibitor", "Manual inhibition disabled and saved to settings") return false } else { addInhibitor("manual", "Manually activated by user") Settings.data.ui.idleInhibitorEnabled = true ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("toast.keep-awake.enabled")) - Logger.log("IdleInhibitor", "Manual inhibition enabled and saved to settings") + Logger.i("IdleInhibitor", "Manual inhibition enabled and saved to settings") return true } } diff --git a/Services/KeyboardLayoutService.qml b/Services/KeyboardLayoutService.qml index 3006d121..ed0b4f8b 100644 --- a/Services/KeyboardLayoutService.qml +++ b/Services/KeyboardLayoutService.qml @@ -202,7 +202,7 @@ Singleton { } Component.onCompleted: { - Logger.log("KeyboardLayout", "Service started") + Logger.i("KeyboardLayout", "Service started") updateLayout() } diff --git a/Services/LocationService.qml b/Services/LocationService.qml index 0e3c0c9b..0886e142 100644 --- a/Services/LocationService.qml +++ b/Services/LocationService.qml @@ -28,14 +28,14 @@ Singleton { printErrors: false onAdapterUpdated: saveTimer.start() onLoaded: { - Logger.log("Location", "Loaded cached data") + Logger.d("Location", "Loaded cached data") // Initialize stable properties on load if (adapter.latitude !== "" && adapter.longitude !== "" && adapter.weatherLastFetch > 0) { root.stableLatitude = adapter.latitude root.stableLongitude = adapter.longitude root.stableName = adapter.name root.coordinatesReady = true - Logger.log("Location", "Coordinates ready") + Logger.i("Location", "Coordinates ready") } updateWeather() } @@ -87,12 +87,12 @@ Singleton { function init() { // does nothing but ensure the singleton is created // do not remove - Logger.log("Location", "Service started") + Logger.i("Location", "Service started") } // -------------------------------- function resetWeather() { - Logger.log("Location", "Resetting weather data") + Logger.i("Location", "Resetting weather data") // Mark as changing to prevent UI updates root.coordinatesReady = false @@ -119,7 +119,7 @@ Singleton { } if (isFetchingWeather) { - Logger.warn("Location", "Weather is still fetching") + Logger.w("Location", "Weather is still fetching") return } @@ -136,13 +136,13 @@ Singleton { const locationChanged = data.name !== Settings.data.location.name if (locationChanged) { root.coordinatesReady = false - Logger.log("Location", "Location changed from", adapter.name, "to", Settings.data.location.name) + Logger.d("Location", "Location changed from", adapter.name, "to", Settings.data.location.name) } if ((adapter.latitude === "") || (adapter.longitude === "") || locationChanged) { _geocodeLocation(Settings.data.location.name, function (latitude, longitude, name, country) { - Logger.log("Location", "Geocoded", Settings.data.location.name, "to:", latitude, "/", longitude) + Logger.d("Location", "Geocoded", Settings.data.location.name, "to:", latitude, "/", longitude) // Save location name adapter.name = Settings.data.location.name @@ -162,7 +162,7 @@ Singleton { // -------------------------------- function _geocodeLocation(locationName, callback, errorCallback) { - Logger.log("Location", "Geocoding location name") + Logger.d("Location", "Geocoding location name") var geoUrl = "https://assets.noctalia.dev/geocode.php?city=" + encodeURIComponent(locationName) + "&language=en&format=json" var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { @@ -189,7 +189,7 @@ Singleton { // -------------------------------- function _fetchWeather(latitude, longitude, errorCallback) { - Logger.log("Location", "Fetching weather from api.open-meteo.com") + Logger.d("Location", "Fetching weather from api.open-meteo.com") var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude + "&longitude=" + longitude + "¤t_weather=true¤t=relativehumidity_2m,surface_pressure&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=auto" var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { @@ -209,7 +209,7 @@ Singleton { root.coordinatesReady = true isFetchingWeather = false - Logger.log("Location", "Cached weather to disk - stable coordinates updated") + Logger.i("Location", "Cached weather to disk - stable coordinates updated") } catch (e) { errorCallback("Location", "Failed to parse weather data") } @@ -224,7 +224,7 @@ Singleton { // -------------------------------- function errorCallback(module, message) { - Logger.error(module, message) + Logger.e(module, message) isFetchingWeather = false } diff --git a/Services/MediaService.qml b/Services/MediaService.qml index b0a4eb16..13c6a32f 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -136,14 +136,14 @@ Singleton { function findActivePlayer() { let availablePlayers = getAvailablePlayers() if (availablePlayers.length === 0) { - //Logger.log("Media", "No active player found") + //Logger.i("Media", "No active player found") return null } // Prioritize the actively playing player --- for (var i = 0; i < availablePlayers.length; i++) { if (availablePlayers[i] && availablePlayers[i].playbackState === MprisPlaybackState.Playing) { - Logger.log("Media", "Found actively playing player: " + availablePlayers[i].identity) + Logger.d("Media", "Found actively playing player: " + availablePlayers[i].identity) selectedPlayerIndex = i return availablePlayers[i] } @@ -177,7 +177,7 @@ Singleton { if (newPlayer !== currentPlayer) { currentPlayer = newPlayer currentPosition = currentPlayer ? currentPlayer.position : 0 - Logger.log("Media", "Switching player") + Logger.d("Media", "Switching player") } } @@ -293,7 +293,7 @@ Singleton { Connections { target: Mpris.players function onValuesChanged() { - Logger.log("Media", "Players changed") + Logger.d("Media", "Players changed") updateCurrentPlayer() } } diff --git a/Services/NetworkService.qml b/Services/NetworkService.qml index 0caaf025..4e5d6dfb 100644 --- a/Services/NetworkService.qml +++ b/Services/NetworkService.qml @@ -56,7 +56,7 @@ Singleton { } Component.onCompleted: { - Logger.log("Network", "Service initialized") + Logger.i("Network", "Service initialized") syncWifiState() scan() } @@ -105,7 +105,7 @@ Singleton { if (scanning) { // Mark current scan results to be ignored and schedule a new scan - Logger.log("Network", "Scan already in progress, will ignore results and rescan") + Logger.d("Network", "Scan already in progress, will ignore results and rescan") ignoreScanResults = true scanPending = true return @@ -117,7 +117,7 @@ Singleton { // Get existing profiles first, then scan profileCheckProcess.running = true - Logger.log("Network", "Wi-Fi scan in progress...") + Logger.d("Network", "Wi-Fi scan in progress...") } function connect(ssid, password = "") { @@ -229,7 +229,7 @@ Singleton { }) if (root.ethernetConnected !== connected) { root.ethernetConnected = connected - Logger.log("Network", "Ethernet connected:", root.ethernetConnected) + Logger.d("Network", "Ethernet connected:", root.ethernetConnected) } } } @@ -245,7 +245,7 @@ Singleton { stdout: StdioCollector { onStreamFinished: { const enabled = text.trim() === "enabled" - Logger.log("Network", "Wi-Fi adapter was detect as enabled:", enabled) + Logger.d("Network", "Wi-Fi adapter was detect as enabled:", enabled) if (Settings.data.network.wifiEnabled !== enabled) { Settings.data.network.wifiEnabled = enabled } @@ -261,7 +261,7 @@ Singleton { stdout: StdioCollector { onStreamFinished: { - Logger.log("Network", "Wi-Fi state change command executed.") + Logger.i("Network", "Wi-Fi state change command executed.") // Re-check the state to ensure it's in sync syncWifiState() } @@ -270,7 +270,7 @@ Singleton { stderr: StdioCollector { onStreamFinished: { if (text.trim()) { - Logger.warn("Network", "Error changing Wi-Fi state: " + text) + Logger.w("Network", "Error changing Wi-Fi state: " + text) } } } @@ -285,7 +285,7 @@ Singleton { stdout: StdioCollector { onStreamFinished: { if (root.ignoreScanResults) { - Logger.log("Network", "Ignoring profile check results (new scan requested)") + Logger.d("Network", "Ignoring profile check results (new scan requested)") root.scanning = false // Check if we need to start a new scan @@ -318,7 +318,7 @@ Singleton { stdout: StdioCollector { onStreamFinished: { if (root.ignoreScanResults) { - Logger.log("Network", "Ignoring scan results (new scan requested)") + Logger.d("Network", "Ignoring scan results (new scan requested)") root.scanning = false // Check if we need to start a new scan @@ -344,7 +344,7 @@ Singleton { // We know the last 3 fields, so everything else is SSID const lastColonIdx = line.lastIndexOf(":") if (lastColonIdx === -1) { - Logger.warn("Network", "Malformed nmcli output line:", line) + Logger.w("Network", "Malformed nmcli output line:", line) continue } @@ -353,7 +353,7 @@ Singleton { const secondLastColonIdx = remainingLine.lastIndexOf(":") if (secondLastColonIdx === -1) { - Logger.warn("Network", "Malformed nmcli output line:", line) + Logger.w("Network", "Malformed nmcli output line:", line) continue } @@ -362,7 +362,7 @@ Singleton { const thirdLastColonIdx = remainingLine2.lastIndexOf(":") if (thirdLastColonIdx === -1) { - Logger.warn("Network", "Malformed nmcli output line:", line) + Logger.w("Network", "Malformed nmcli output line:", line) continue } @@ -410,15 +410,15 @@ Singleton { if (newNetworks.length > 0 || lostNetworks.length > 0) { if (newNetworks.length > 0) { - Logger.log("Network", "New Wi-Fi SSID discovered:", newNetworks.join(", ")) + Logger.d("Network", "New Wi-Fi SSID discovered:", newNetworks.join(", ")) } if (lostNetworks.length > 0) { - Logger.log("Network", "Wi-Fi SSID disappeared:", lostNetworks.join(", ")) + Logger.d("Network", "Wi-Fi SSID disappeared:", lostNetworks.join(", ")) } - Logger.log("Network", "Total Wi-Fi SSIDs:", Object.keys(networksMap).length) + Logger.d("Network", "Total Wi-Fi SSIDs:", Object.keys(networksMap).length) } - Logger.log("Network", "Wi-Fi scan completed") + Logger.d("Network", "Wi-Fi scan completed") root.networks = networksMap root.scanning = false @@ -435,7 +435,7 @@ Singleton { onStreamFinished: { root.scanning = false if (text.trim()) { - Logger.warn("Network", "Scan error: " + text) + Logger.w("Network", "Scan error: " + text) // If scan fails, retry delayedScanTimer.interval = 5000 @@ -491,7 +491,7 @@ Singleton { root.connecting = false root.connectingTo = "" - Logger.log("Network", `Connected to network: '${connectProcess.ssid}'`) + Logger.i("Network", `Connected to network: '${connectProcess.ssid}'`) ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.connected", { "ssid": connectProcess.ssid })) @@ -520,7 +520,7 @@ Singleton { root.lastError = text.split("\n")[0].trim() } - Logger.warn("Network", "Connect error: " + text) + Logger.w("Network", "Connect error: " + text) } } } @@ -534,7 +534,7 @@ Singleton { stdout: StdioCollector { onStreamFinished: { - Logger.log("Network", `Disconnected from network: '${disconnectProcess.ssid}'`) + Logger.i("Network", `Disconnected from network: '${disconnectProcess.ssid}'`) ToastService.showNotice(I18n.tr("wifi.panel.title"), I18n.tr("toast.wifi.disconnected", { "ssid": disconnectProcess.ssid })) @@ -553,7 +553,7 @@ Singleton { onStreamFinished: { root.disconnectingFrom = "" if (text.trim()) { - Logger.warn("Network", "Disconnect error: " + text) + Logger.w("Network", "Disconnect error: " + text) } // Still trigger a scan even on error delayedScanTimer.interval = 5000 @@ -599,8 +599,8 @@ Singleton { stdout: StdioCollector { onStreamFinished: { - Logger.log("Network", `Forget network: "${forgetProcess.ssid}"`) - Logger.log("Network", text.trim().replace(/[\r\n]/g, " ")) + Logger.i("Network", `Forget network: "${forgetProcess.ssid}"`) + Logger.d("Network", text.trim().replace(/[\r\n]/g, " ")) // Update both cached and existing status immediately let nets = root.networks @@ -624,7 +624,7 @@ Singleton { onStreamFinished: { root.forgettingNetwork = "" if (text.trim() && !text.includes("No profiles found")) { - Logger.warn("Network", "Forget error: " + text) + Logger.w("Network", "Forget error: " + text) } // Still Trigger a scan even on error delayedScanTimer.interval = 5000 diff --git a/Services/NightLightService.qml b/Services/NightLightService.qml index a52be4d8..adf3704e 100644 --- a/Services/NightLightService.qml +++ b/Services/NightLightService.qml @@ -93,10 +93,10 @@ Singleton { id: runner running: false onStarted: { - Logger.log("NightLight", "Wlsunset started:", runner.command) + Logger.i("NightLight", "Wlsunset started:", runner.command) } onExited: function (code, status) { - Logger.log("NightLight", "Wlsunset exited:", code, status) + Logger.i("NightLight", "Wlsunset exited:", code, status) } } } diff --git a/Services/NiriService.qml b/Services/NiriService.qml index 16226348..ab89d366 100644 --- a/Services/NiriService.qml +++ b/Services/NiriService.qml @@ -28,7 +28,7 @@ Item { updateWorkspaces() updateWindows() queryDisplayScales() - Logger.log("NiriService", "Initialized successfully") + Logger.i("NiriService", "Initialized successfully") } // Update workspaces @@ -89,7 +89,7 @@ Item { CompositorService.onDisplayScalesUpdated(scales) } } catch (e) { - Logger.error("NiriService", "Failed to parse outputs:", e, line) + Logger.e("NiriService", "Failed to parse outputs:", e, line) } } } @@ -136,7 +136,7 @@ Item { workspaceChanged() } catch (e) { - Logger.error("NiriService", "Failed to parse workspaces:", e, line) + Logger.e("NiriService", "Failed to parse workspaces:", e, line) } } } @@ -154,7 +154,7 @@ Item { const windowsData = JSON.parse(line) recollectWindows(windowsData) } catch (e) { - Logger.error("NiriService", "Failed to parse windows:", e, line) + Logger.e("NiriService", "Failed to parse windows:", e, line) } } } @@ -193,7 +193,7 @@ Item { queryDisplayScales() } } catch (e) { - Logger.error("NiriService", "Error parsing event stream:", e, data) + Logger.e("NiriService", "Error parsing event stream:", e, data) } } } @@ -300,7 +300,7 @@ Item { windowListChanged() } catch (e) { - Logger.error("NiriService", "Error handling WindowOpenedOrChanged:", e) + Logger.e("NiriService", "Error handling WindowOpenedOrChanged:", e) } } @@ -324,7 +324,7 @@ Item { windowListChanged() } } catch (e) { - Logger.error("NiriService", "Error handling WindowClosed:", e) + Logger.e("NiriService", "Error handling WindowClosed:", e) } } @@ -333,7 +333,7 @@ Item { const windowsData = eventData.windows recollectWindows(windowsData) } catch (e) { - Logger.error("NiriService", "Error handling WindowsChanged:", e) + Logger.e("NiriService", "Error handling WindowsChanged:", e) } } @@ -359,7 +359,7 @@ Item { activeWindowChanged() } catch (e) { - Logger.error("NiriService", "Error handling WindowFocusChanged:", e) + Logger.e("NiriService", "Error handling WindowFocusChanged:", e) } } @@ -378,16 +378,16 @@ Item { windowListChanged() } catch (e) { - Logger.error("NiriService", "Error handling WindowLayoutChanged:", e) + Logger.e("NiriService", "Error handling WindowLayoutChanged:", e) } } function handleOverviewOpenedOrClosed(eventData) { try { overviewActive = eventData.is_open - Logger.log("NiriService", "Overview opened or closed:", eventData.is_open) + Logger.d("NiriService", "Overview opened or closed:", eventData.is_open) } catch (e) { - Logger.error("NiriService", "Error handling OverviewOpenedOrClosed:", e) + Logger.e("NiriService", "Error handling OverviewOpenedOrClosed:", e) } } @@ -396,7 +396,7 @@ Item { try { Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", workspace.idx.toString()]) } catch (e) { - Logger.error("NiriService", "Failed to switch workspace:", e) + Logger.e("NiriService", "Failed to switch workspace:", e) } } @@ -404,7 +404,7 @@ Item { try { Quickshell.execDetached(["niri", "msg", "action", "focus-window", "--id", window.id.toString()]) } catch (e) { - Logger.error("NiriService", "Failed to switch window:", e) + Logger.e("NiriService", "Failed to switch window:", e) } } @@ -412,7 +412,7 @@ Item { try { Quickshell.execDetached(["niri", "msg", "action", "close-window", "--id", window.id.toString()]) } catch (e) { - Logger.error("NiriService", "Failed to close window:", e) + Logger.e("NiriService", "Failed to close window:", e) } } @@ -420,7 +420,7 @@ Item { try { Quickshell.execDetached(["niri", "msg", "action", "quit", "--skip-confirmation"]) } catch (e) { - Logger.error("NiriService", "Failed to logout:", e) + Logger.e("NiriService", "Failed to logout:", e) } } } diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index 616d0ba9..3d8c0298 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -301,7 +301,7 @@ Singleton { adapter.notifications = items historyFileView.writeAdapter() } catch (e) { - Logger.error("Notifications", "Save history failed:", e) + Logger.e("Notifications", "Save history failed:", e) } } @@ -331,7 +331,7 @@ Singleton { }) } } catch (e) { - Logger.error("Notifications", "Load failed:", e) + Logger.e("Notifications", "Load failed:", e) } } @@ -461,7 +461,7 @@ Singleton { try { Quickshell.execDetached(["sh", "-c", `rm -rf "${Settings.cacheDirImagesNotifications}"*`]) } catch (e) { - Logger.error("Notifications", "Failed to clear cache directory:", e) + Logger.e("Notifications", "Failed to clear cache directory:", e) } historyList.clear() diff --git a/Services/PanelService.qml b/Services/PanelService.qml index 29320c1d..1ee055c1 100644 --- a/Services/PanelService.qml +++ b/Services/PanelService.qml @@ -24,7 +24,7 @@ Singleton { // Register this panel function registerPanel(panel) { registeredPanels[panel.objectName] = panel - Logger.log("PanelService", "Registered:", panel.objectName) + Logger.d("PanelService", "Registered:", panel.objectName) } // Returns a panel diff --git a/Services/PowerProfileService.qml b/Services/PowerProfileService.qml index 9a43217e..8aa7ac64 100644 --- a/Services/PowerProfileService.qml +++ b/Services/PowerProfileService.qml @@ -55,7 +55,7 @@ Singleton { try { powerProfiles.profile = p } catch (e) { - Logger.error("PowerProfileService", "Failed to set profile:", e) + Logger.e("PowerProfileService", "Failed to set profile:", e) } } diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml index 94b65066..5335ed52 100644 --- a/Services/ProgramCheckerService.qml +++ b/Services/ProgramCheckerService.qml @@ -65,12 +65,12 @@ Singleton { } } - Logger.log("ProgramChecker", "Detected Discord clients:", detectedClients.join(", ")) + Logger.i("ProgramChecker", "Detected Discord clients:", detectedClients.join(", ")) } } if (availableDiscordClients.length === 0) { - Logger.log("ProgramChecker", "No Discord clients detected") + Logger.d("ProgramChecker", "No Discord clients detected") } } @@ -161,7 +161,7 @@ Singleton { // Function to check a specific program function checkProgram(programProperty) { if (!programsToCheck.hasOwnProperty(programProperty)) { - Logger.warn("ProgramChecker", "Unknown program property:", programProperty) + Logger.w("ProgramChecker", "Unknown program property:", programProperty) return } @@ -172,14 +172,14 @@ Singleton { // Manual function to test Discord detection (for debugging) function testDiscordDetection() { - Logger.log("ProgramChecker", "Testing Discord detection...") - Logger.log("ProgramChecker", "HOME:", Quickshell.env("HOME")) + Logger.d("ProgramChecker", "Testing Discord detection...") + 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] var configDir = client.configPath.replace("~", Quickshell.env("HOME")) - Logger.log("ProgramChecker", "Checking:", configDir) + Logger.d("ProgramChecker", "Checking:", configDir) } detectDiscordClient() diff --git a/Services/SwayService.qml b/Services/SwayService.qml index 9e1ddd71..8306912a 100644 --- a/Services/SwayService.qml +++ b/Services/SwayService.qml @@ -43,9 +43,9 @@ Item { queryDisplayScales() }) initialized = true - Logger.log("SwayService", "Initialized successfully") + Logger.i("SwayService", "Initialized successfully") } catch (e) { - Logger.error("SwayService", "Failed to initialize:", e) + Logger.e("SwayService", "Failed to initialize:", e) } } @@ -70,7 +70,7 @@ Item { onExited: function (exitCode) { if (exitCode !== 0 || !accumulatedOutput) { - Logger.error("SwayService", "Failed to query outputs, exit code:", exitCode) + Logger.e("SwayService", "Failed to query outputs, exit code:", exitCode) accumulatedOutput = "" return } @@ -101,7 +101,7 @@ Item { CompositorService.onDisplayScalesUpdated(scales) } } catch (e) { - Logger.error("SwayService", "Failed to parse outputs:", e) + Logger.e("SwayService", "Failed to parse outputs:", e) } finally { // Clear accumulated output for next query accumulatedOutput = "" @@ -147,7 +147,7 @@ Item { workspaces.append(wsData) } } catch (e) { - Logger.error("SwayService", "Error updating workspaces:", e) + Logger.e("SwayService", "Error updating workspaces:", e) } } @@ -187,7 +187,7 @@ Item { activeWindowChanged() } } catch (e) { - Logger.error("SwayService", "Error updating windows:", e) + Logger.e("SwayService", "Error updating windows:", e) } } @@ -272,7 +272,7 @@ Item { try { workspace.handle.activate() } catch (e) { - Logger.error("SwayService", "Failed to switch workspace:", e) + Logger.e("SwayService", "Failed to switch workspace:", e) } } @@ -280,7 +280,7 @@ Item { try { window.handle.activate() } catch (e) { - Logger.error("SwayService", "Failed to switch window:", e) + Logger.e("SwayService", "Failed to switch window:", e) } } @@ -288,7 +288,7 @@ Item { try { window.handle.close() } catch (e) { - Logger.error("SwayService", "Failed to close window:", e) + Logger.e("SwayService", "Failed to close window:", e) } } @@ -296,7 +296,7 @@ Item { try { Quickshell.execDetached(["swaymsg", "exit"]) } catch (e) { - Logger.error("SwayService", "Failed to logout:", e) + Logger.e("SwayService", "Failed to logout:", e) } } } diff --git a/Services/SystemStatService.qml b/Services/SystemStatService.qml index 9692c1ee..e69cd112 100644 --- a/Services/SystemStatService.qml +++ b/Services/SystemStatService.qml @@ -42,7 +42,7 @@ Singleton { // -------------------------------------------- Component.onCompleted: { - Logger.log("SystemStat", "Service started with interval:", root.sleepDuration, "ms") + Logger.i("SystemStat", "Service started with interval:", root.sleepDuration, "ms") // Kickoff the cpu name detection for temperature cpuTempNameReader.checkNext() @@ -121,11 +121,11 @@ Singleton { function checkNext() { if (currentIndex >= 16) { // Check up to hwmon10 - Logger.warn("No supported temperature sensor found") + Logger.w("No supported temperature sensor found") return } - //Logger.log("SystemStat", "---- Probing: hwmon", currentIndex) + //Logger.i("SystemStat", "---- Probing: hwmon", currentIndex) cpuTempNameReader.path = `/sys/class/hwmon/hwmon${currentIndex}/name` cpuTempNameReader.reload() } @@ -135,7 +135,7 @@ Singleton { if (root.supportedTempCpuSensorNames.includes(name)) { root.cpuTempSensorName = name root.cpuTempHwmonPath = `/sys/class/hwmon/hwmon${currentIndex}` - Logger.log("SystemStat", `Found ${root.cpuTempSensorName} CPU thermal sensor at ${root.cpuTempHwmonPath}`) + Logger.i("SystemStat", `Found ${root.cpuTempSensorName} CPU thermal sensor at ${root.cpuTempHwmonPath}`) } else { currentIndex++ Qt.callLater(() => { @@ -386,9 +386,9 @@ Singleton { sum += root.intelTempValues[i] } root.cpuTemp = Math.round(sum / root.intelTempValues.length) - //Logger.log("SystemStat", `Averaged ${root.intelTempValues.length} CPU thermal sensors: ${root.cpuTemp}°C`) + //Logger.i("SystemStat", `Averaged ${root.intelTempValues.length} CPU thermal sensors: ${root.cpuTemp}°C`) } else { - Logger.warn("SystemStat", "No temperature sensors found for coretemp") + Logger.w("SystemStat", "No temperature sensors found for coretemp") root.cpuTemp = 0 } return diff --git a/Services/TooltipService.qml b/Services/TooltipService.qml index f083848f..606e2c39 100644 --- a/Services/TooltipService.qml +++ b/Services/TooltipService.qml @@ -22,7 +22,7 @@ Singleton { // Don't create if no text if (!screen || !target || !text) { - Logger.log("Tooltip", "No target or text") + Logger.i("Tooltip", "No target or text") return } @@ -82,7 +82,7 @@ Singleton { return newTooltip } else { - Logger.error("Tooltip", "Failed to create tooltip instance") + Logger.e("Tooltip", "Failed to create tooltip instance") } return null diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index e1c864b5..a2ca41f5 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -20,11 +20,11 @@ Singleton { function checkForUpdates() { // TODO: Implement update checking logic - Logger.log("UpdateService", "Checking for updates...") + Logger.i("UpdateService", "Checking for updates...") } function init() { // Ensure the singleton is created - Logger.log("UpdateService", "Version:", root.currentVersion) + Logger.i("UpdateService", "Version:", root.currentVersion) } } diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 2046f6cc..bed6647c 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -76,7 +76,7 @@ Singleton { // ------------------------------------------------- function init() { - Logger.log("Wallpaper", "Service started") + Logger.i("Wallpaper", "Service started") translateModels() @@ -247,11 +247,11 @@ Singleton { } if (screenName === undefined) { - Logger.warn("Wallpaper", "setWallpaper", "no screen specified") + Logger.w("Wallpaper", "setWallpaper", "no screen specified") return } - //Logger.log("Wallpaper", "setWallpaper on", screenName, ": ", path) + //Logger.i("Wallpaper", "setWallpaper on", screenName, ": ", path) // Check if wallpaper actually changed var oldPath = currentWallpapers[screenName] || "" @@ -303,7 +303,7 @@ Singleton { // ------------------------------------------------------------------- function setRandomWallpaper() { - Logger.log("Wallpaper", "setRandomWallpaper") + Logger.d("Wallpaper", "setRandomWallpaper") if (Settings.data.wallpaper.enableMultiMonitorDirectories) { // Pick a random wallpaper per screen @@ -331,7 +331,7 @@ Singleton { // ------------------------------------------------------------------- function toggleRandomWallpaper() { - Logger.log("Wallpaper", "toggleRandomWallpaper") + Logger.d("Wallpaper", "toggleRandomWallpaper") if (Settings.data.wallpaper.randomEnabled) { restartRandomWallpaperTimer() setRandomWallpaper() @@ -355,7 +355,7 @@ Singleton { // ------------------------------------------------------------------- function refreshWallpapersList() { - Logger.log("Wallpaper", "refreshWallpapersList") + Logger.d("Wallpaper", "refreshWallpapersList") scanningCount = 0 // Force refresh by toggling the folder property on each FolderListModel @@ -429,7 +429,7 @@ Singleton { root.wallpaperLists[screenName] = files scanningCount-- - Logger.log("Wallpaper", "List refreshed for", screenName, "count:", files.length) + Logger.d("Wallpaper", "List refreshed for", screenName, "count:", files.length) root.wallpaperListChanged(screenName, files.length) } } diff --git a/Widgets/NIcon.qml b/Widgets/NIcon.qml index 87e63c1f..32e274f2 100644 --- a/Widgets/NIcon.qml +++ b/Widgets/NIcon.qml @@ -16,7 +16,7 @@ Text { return "" } if (Icons.get(icon) === undefined) { - Logger.warn("Icon", `"${icon}"`, "doesn't exist in the icons font") + Logger.w("Icon", `"${icon}"`, "doesn't exist in the icons font") Logger.callStack() return Icons.get(Icons.defaultIcon) } diff --git a/Widgets/NImageCached.qml b/Widgets/NImageCached.qml index edeb4baf..2645d479 100644 --- a/Widgets/NImageCached.qml +++ b/Widgets/NImageCached.qml @@ -23,7 +23,7 @@ Image { onImagePathChanged: { if (imagePath) { imageHash = Checksum.sha256(imagePath) - // Logger.log("NImageCached", imagePath, imageHash) + // Logger.i("NImageCached", imagePath, imageHash) } else { source = "" imageHash = "" diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index 5651d8af..72e4905e 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -135,7 +135,7 @@ Loader { readonly property real verticalBarWidth: Style.barHeight Component.onCompleted: { - Logger.log("NPanel", "Opened", root.objectName, "on", screen.name) + Logger.d("NPanel", "Opened", root.objectName, "on", screen.name) dimmingOpacity = Style.opacityHeavy } @@ -148,7 +148,7 @@ Loader { if (buttonName) { setPosition() } - Logger.log("NPanel", "OnScreenChanged", root.screen.name) + Logger.d("NPanel", "OnScreenChanged", root.screen.name) } } diff --git a/Widgets/NSectionEditor.qml b/Widgets/NSectionEditor.qml index b1dbefcd..59b77d85 100644 --- a/Widgets/NSectionEditor.qml +++ b/Widgets/NSectionEditor.qml @@ -285,19 +285,19 @@ NBox { if (dialog) { dialog.open() } else { - Logger.error("NSectionEditor", "Failed to create settings dialog instance") + Logger.e("NSectionEditor", "Failed to create settings dialog instance") } } if (component.status === Component.Ready) { instantiateAndOpen() } else if (component.status === Component.Error) { - Logger.error("NSectionEditor", component.errorString()) + Logger.e("NSectionEditor", component.errorString()) } else { component.statusChanged.connect(function () { if (component.status === Component.Ready) { instantiateAndOpen() } else if (component.status === Component.Error) { - Logger.error("NSectionEditor", component.errorString()) + Logger.e("NSectionEditor", component.errorString()) } }) } diff --git a/shell.qml b/shell.qml index 4c4291c4..e4645fbe 100644 --- a/shell.qml +++ b/shell.qml @@ -49,8 +49,8 @@ ShellRoot { property bool settingsLoaded: false Component.onCompleted: { - Logger.log("Shell", "---------------------------") - Logger.log("Shell", "Noctalia Hello!") + Logger.i("Shell", "---------------------------") + Logger.i("Shell", "Noctalia Hello!") } Connections { @@ -79,7 +79,7 @@ ShellRoot { sourceComponent: Item { Component.onCompleted: { - Logger.log("Shell", "---------------------------") + Logger.i("Shell", "---------------------------") WallpaperService.init() AppThemeService.init() ColorSchemeService.init() From 49f4ab114f4f47d5e7c248bcab61616938131a8a Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Thu, 16 Oct 2025 17:23:32 +0200 Subject: [PATCH 031/109] DarkModeService: Add settings and manual scheduling mode. --- Commons/Settings.qml | 3 + Modules/Settings/Tabs/ColorSchemeTab.qml | 89 +++++++++++++++++++ Services/DarkModeService.qml | 108 ++++++++++++++++++++--- Services/LocationService.qml | 2 +- 4 files changed, 189 insertions(+), 13 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 271cecb7..36b10f2a 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -354,6 +354,9 @@ Singleton { property bool useWallpaperColors: false property string predefinedScheme: "Noctalia (default)" property bool darkMode: true + property string schedulingMode: "off" + property string manualSunrise: "06:30" + property string manualSunset: "18:30" property string matugenSchemeType: "scheme-fruit-salad" property bool generateTemplatesForPredefined: true } diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 969c8769..74205d9d 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -13,6 +13,24 @@ ColumnLayout { property var schemeColorsCache: ({}) property int cacheVersion: 0 // Increment to trigger UI updates + // Time dropdown options (00:00 .. 23:30) + ListModel { + id: timeOptions + } + Component.onCompleted: { + for (var h = 0; h < 24; h++) { + for (var m = 0; m < 60; m += 30) { + var hh = ("0" + h).slice(-2) + var mm = ("0" + m).slice(-2) + var key = hh + ":" + mm + timeOptions.append({ + "key": key, + "name": key + }) + } + } + } + spacing: Style.marginL // Helper function to extract scheme name from path @@ -148,6 +166,77 @@ ColumnLayout { } } + NComboBox { + label: "Dark Mode Schedule" + description: "Enables automatic switching between light and dark mode" + + model: [{ + "name": "Off", + "key": "off" + }, { + "name": "Manual", + "key": "manual" + }, { + "name": "Sunrise/Sunset", + "key": "location" + }] + + currentKey: Settings.data.colorSchemes.schedulingMode + + onSelected: key => { + Settings.data.colorSchemes.schedulingMode = key + AppThemeService.generate() + } + } + + // Manual scheduling + ColumnLayout { + spacing: Style.marginS + visible: Settings.data.colorSchemes.schedulingMode === "manual" + + NLabel { + label: I18n.tr("settings.display.night-light.manual-schedule.label") + description: I18n.tr("settings.display.night-light.manual-schedule.description") + } + + RowLayout { + Layout.fillWidth: false + spacing: Style.marginS + + NText { + text: I18n.tr("settings.display.night-light.manual-schedule.sunrise") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + + NComboBox { + model: timeOptions + currentKey: Settings.data.colorSchemes.manualSunrise + placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start") + onSelected: key => Settings.data.colorSchemes.manualSunrise = key + minimumWidth: 120 + } + + Item { + Layout.preferredWidth: 20 + } + + NText { + text: I18n.tr("settings.display.night-light.manual-schedule.sunset") + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + + NComboBox { + model: timeOptions + currentKey: Settings.data.colorSchemes.manualSunset + placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop") + onSelected: key => Settings.data.colorSchemes.manualSunset = key + minimumWidth: 120 + } + } + } + // Use Wallpaper Colors NToggle { label: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label") diff --git a/Services/DarkModeService.qml b/Services/DarkModeService.qml index afba5481..308d393b 100644 --- a/Services/DarkModeService.qml +++ b/Services/DarkModeService.qml @@ -13,31 +13,118 @@ Singleton { Connections { target: LocationService.data + enabled: Settings.data.colorSchemes.schedulingMode == "location" function onWeatherChanged() { if (LocationService.data.weather !== null) { - const changes = root.collectChanges(LocationService.data.weather) + const changes = root.collectWeatherChanges(LocationService.data.weather) if (!root.initComplete) { root.initComplete = true - root.resetDarkMode(changes) + root.applyCurrentMode(changes) } - root.scheduleChange(changes) + root.scheduleNextMode(changes) } } } + Connections { + target: Settings.data.colorSchemes + enabled: Settings.data.colorSchemes.schedulingMode == "manual" + function onManualSunriseChanged() { + const changes = root.collectManualChanges() + root.applyCurrentMode(changes) + root.scheduleNextMode(changes) + } + function onManualSunsetChanged() { + const changes = root.collectManualChanges() + root.applyCurrentMode(changes) + root.scheduleNextMode(changes) + } + } + + Connections { + target: Settings.data.colorSchemes + function onSchedulingModeChanged() { + root.init() + } + } + Timer { id: timer onTriggered: { Settings.data.colorSchemes.darkMode = root.nextDarkModeState if (LocationService.data.weather !== null) { - const changes = root.collectChanges(LocationService.data.weather) - root.scheduleChange(changes) + const changes = root.collectWeatherChanges(LocationService.data.weather) + root.scheduleNextMode(changes) } } } - function collectChanges(weather) { + function init() { + Logger.log("DarkModeService", "Service started") + + if (Settings.data.colorSchemes.schedulingMode == "manual") { + const changes = collectManualChanges() + initComplete = true + applyCurrentMode(changes) + scheduleNextMode(changes) + } + + if (Settings.data.colorSchemes.schedulingMode == "location" && LocationService.data.weather) { + const changes = collectWeatherChanges(LocationService.data.weather) + initComplete = true + applyCurrentMode(changes) + scheduleNextMode(changes) + } + } + + function parseTime(timeString) { + const parts = timeString.split(":").map(Number) + return { + "hour": parts[0], + "minute": parts[1] + } + } + + function collectManualChanges() { + const sunriseTime = parseTime(Settings.data.colorSchemes.manualSunrise) + const sunsetTime = parseTime(Settings.data.colorSchemes.manualSunset) + + const now = new Date() + const year = now.getFullYear() + const month = now.getMonth() + const day = now.getDate() + + const yesterdaysSunset = new Date(year, month, day - 1, sunsetTime.hour, sunsetTime.minute) + const todaysSunrise = new Date(year, month, day, sunriseTime.hour, sunriseTime.minute) + const todaysSunset = new Date(year, month, day, sunsetTime.hour, sunsetTime.minute) + const tomorrowsSunrise = new Date(year, month, day + 1, sunriseTime.hour, sunriseTime.minute) + + return [{ + "time": yesterdaysSunset.getTime(), + "darkMode": true + }, { + "time": todaysSunrise.getTime(), + "darkMode": false + }, { + "time": todaysSunset.getTime(), + "darkMode": true + }, { + "time": tomorrowsSunrise.getTime(), + "darkMode": false + }] + } + + function collectWeatherChanges(weather) { const changes = [] + + if (Date.now() < Date.parse(weather.daily.sunrise[0])) { + // The sun has not risen yet + changes.push({ + "time": Date.now() - 1, + "darkMode": true + }) + } + for (var i = 0; i < weather.daily.sunrise.length; i++) { changes.push({ "time": Date.parse(weather.daily.sunrise[i]), @@ -48,10 +135,11 @@ Singleton { "darkMode": true }) } + return changes } - function resetDarkMode(changes) { + function applyCurrentMode(changes) { const now = Date.now() // changes.findLast(change => change.time < now) // not available in QML... @@ -68,7 +156,7 @@ Singleton { } } - function scheduleChange(changes) { + function scheduleNextMode(changes) { const now = Date.now() const nextChange = changes.find(change => change.time > now) if (nextChange) { @@ -78,8 +166,4 @@ Singleton { Logger.log("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`) } } - - function init() { - Logger.log("DarkModeService", "Service started") - } } diff --git a/Services/LocationService.qml b/Services/LocationService.qml index 729f05bf..4c2709bd 100644 --- a/Services/LocationService.qml +++ b/Services/LocationService.qml @@ -69,7 +69,7 @@ Singleton { Timer { id: updateTimer interval: 20 * 1000 - running: Settings.data.location.weatherEnabled + running: Settings.data.location.weatherEnabled || Settings.data.colorSchemes.schedulingMode == "location" repeat: true onTriggered: { updateWeather() From b82cdefd97a532c92ddb7d4be4e5497e91594883 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Thu, 16 Oct 2025 17:40:00 +0200 Subject: [PATCH 032/109] ColorSchemeTab: Add translations for DarkModeService settings. --- Assets/Translations/de.json | 17 +++++++++++++---- Assets/Translations/en.json | 19 ++++++++++++++----- Assets/Translations/es.json | 10 ++++++---- Assets/Translations/fr.json | 10 ++++++---- Assets/Translations/pt.json | 10 ++++++---- Assets/Translations/zh-CN.json | 10 ++++++---- Modules/Settings/Tabs/ColorSchemeTab.qml | 14 +++++++------- 7 files changed, 58 insertions(+), 32 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 2be6197b..2499d5a2 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -475,10 +475,6 @@ "label": "Farbquelle", "description": "Haupteinstellungen für Noctalias Farben." }, - "dark-mode": { - "label": "Dunkler Modus", - "description": "Wechselt zu einem dunkleren Theme für einfachere Betrachtung bei Nacht." - }, "use-wallpaper-colors": { "label": "Hintergrundbild-Farben verwenden", "description": "Farbschemata aus Ihrem Hintergrundbild mit Matugen generieren. Extrahiert automatisch Farben für ein kohärentes Design." @@ -488,6 +484,19 @@ "description": "Wähle einen Farbstil für Matugen aus." } }, + "dark-mode": { + "switch": { + "label": "Dunkler Modus", + "description": "Wechselt zu einem dunkleren Theme für einfachere Betrachtung bei Nacht." + }, + "mode": { + "label": "Automatischer dunkler Modus", + "description": "Ermöglicht automatisches Wechseln zwischen dem hellen und dunklen Modus.", + "off": "Aus", + "manual": "Manuell", + "location": "Standort" + } + }, "predefined": { "section": { "label": "Vordefinierte Farbschemata", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 915b75d1..31b9be46 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -475,10 +475,6 @@ "label": "Color source", "description": "Main settings for Noctalia's colors." }, - "dark-mode": { - "label": "Dark mode", - "description": "Switches to a darker theme for easier viewing at night." - }, "use-wallpaper-colors": { "label": "Use wallpaper colors", "description": "Generate color schemes from your wallpaper using Matugen. Automatically extracts colors to create a cohesive theme." @@ -488,6 +484,19 @@ "description": "Choose the color scheme generation algorithm for Matugen." } }, + "dark-mode": { + "switch": { + "label": "Dark mode", + "description": "Switches to a darker theme for easier viewing at night." + }, + "mode": { + "label": "Dark mode schedule", + "description": "Enables automatic switching between light and dark mode.", + "off": "Off", + "manual": "Manual", + "location": "Location" + } + }, "predefined": { "section": { "label": "Predefined color schemes", @@ -1187,7 +1196,7 @@ "scan-again": "Scan again" } }, - + "tooltips": { "refresh": "Refresh", "close": "Close", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 72251b3f..d02fa0ec 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -475,10 +475,6 @@ "label": "Fuente de color", "description": "Configuración principal de los colores de Noctalia." }, - "dark-mode": { - "label": "Modo oscuro", - "description": "Cambia a un tema más oscuro para una visualización más fácil por la noche." - }, "use-wallpaper-colors": { "label": "Usar colores del fondo de pantalla", "description": "Generar esquemas de color desde tu fondo de pantalla usando Matugen. Extrae automáticamente colores para crear un tema cohesivo." @@ -488,6 +484,12 @@ "description": "Elige el algoritmo de generación de esquema de colores para Matugen." } }, + "dark-mode": { + "switch": { + "label": "Modo oscuro", + "description": "Cambia a un tema más oscuro para una visualización más fácil por la noche." + } + }, "predefined": { "section": { "label": "Esquemas de colores predefinidos", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8399a63a..fdc0455c 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -475,10 +475,6 @@ "label": "Source des couleurs", "description": "Paramètres principaux pour les couleurs de Noctalia." }, - "dark-mode": { - "label": "Mode sombre", - "description": "Passe à un thème plus sombre pour une visualisation plus facile la nuit." - }, "use-wallpaper-colors": { "label": "Utiliser les couleurs du fond d'écran", "description": "Générer des schémas de couleurs à partir de votre fond d'écran avec Matugen. Extrait automatiquement les couleurs pour créer un thème cohérent." @@ -488,6 +484,12 @@ "description": "Choisissez l'algorithme de génération de schéma de couleurs pour Matugen." } }, + "dark-mode": { + "switch": { + "label": "Mode sombre", + "description": "Passe à un thème plus sombre pour une visualisation plus facile la nuit." + } + }, "predefined": { "section": { "label": "Jeux de couleurs prédéfinis", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index c4c3ea52..89113f07 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -437,10 +437,6 @@ "label": "Fonte de cor", "description": "Configurações principais para as cores do Noctalia." }, - "dark-mode": { - "label": "Modo escuro", - "description": "Muda para um tema mais escuro para facilitar a visualização à noite." - }, "use-wallpaper-colors": { "label": "Usar cores do papel de parede", "description": "Gerar esquemas de cores do seu papel de parede usando Matugen. Extrai automaticamente cores para criar um tema coeso." @@ -450,6 +446,12 @@ "description": "Escolha o algoritmo de geração de esquema de cores para Matugen." } }, + "dark-mode": { + "switch": { + "label": "Modo escuro", + "description": "Muda para um tema mais escuro para facilitar a visualização à noite." + } + }, "predefined": { "section": { "label": "Esquemas de cores predefinidos", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 0116a5da..8d27c6ad 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -475,10 +475,6 @@ "label": "颜色来源", "description": "Noctalia 颜色的主要设置。" }, - "dark-mode": { - "label": "深色模式", - "description": "切换到更暗的主题,便于夜间观看。" - }, "use-wallpaper-colors": { "label": "使用壁纸颜色", "description": "使用 Matugen 从壁纸生成颜色方案。自动提取颜色以创建一致的主题。" @@ -488,6 +484,12 @@ "description": "为 Matugen 选择配色方案生成算法。" } }, + "dark-mode": { + "switch": { + "label": "深色模式", + "description": "切换到更暗的主题,便于夜间观看。" + } + }, "predefined": { "section": { "label": "预定义配色方案", diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 74205d9d..fbe5a296 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -156,8 +156,8 @@ ColumnLayout { // Dark Mode Toggle NToggle { - label: I18n.tr("settings.color-scheme.color-source.dark-mode.label") - description: I18n.tr("settings.color-scheme.color-source.dark-mode.description") + label: I18n.tr("settings.color-scheme.dark-mode.switch.label") + description: I18n.tr("settings.color-scheme.dark-mode.switch.description") checked: Settings.data.colorSchemes.darkMode enabled: true onToggled: checked => { @@ -167,17 +167,17 @@ ColumnLayout { } NComboBox { - label: "Dark Mode Schedule" - description: "Enables automatic switching between light and dark mode" + label: I18n.tr("settings.color-scheme.dark-mode.mode.label") + description: I18n.tr("settings.color-scheme.dark-mode.mode.description") model: [{ - "name": "Off", + "name": I18n.tr("settings.color-scheme.dark-mode.mode.off"), "key": "off" }, { - "name": "Manual", + "name": I18n.tr("settings.color-scheme.dark-mode.mode.manual"), "key": "manual" }, { - "name": "Sunrise/Sunset", + "name": I18n.tr("settings.color-scheme.dark-mode.mode.location"), "key": "location" }] From e57b565f800567ed27d63973faa92f90a2943369 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Thu, 16 Oct 2025 17:48:11 +0200 Subject: [PATCH 033/109] DarkModeService: Update to new logging style. --- Services/DarkModeService.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Services/DarkModeService.qml b/Services/DarkModeService.qml index 308d393b..ad3eef00 100644 --- a/Services/DarkModeService.qml +++ b/Services/DarkModeService.qml @@ -60,7 +60,7 @@ Singleton { } function init() { - Logger.log("DarkModeService", "Service started") + Logger.i("DarkModeService", "Service started") if (Settings.data.colorSchemes.schedulingMode == "manual") { const changes = collectManualChanges() @@ -152,7 +152,7 @@ Singleton { if (lastChange) { Settings.data.colorSchemes.darkMode = lastChange.darkMode - Logger.log("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`) + Logger.d("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`) } } @@ -163,7 +163,7 @@ Singleton { root.nextDarkModeState = nextChange.darkMode timer.interval = nextChange.time - now timer.restart() - Logger.log("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`) + Logger.d("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`) } } } From e85801208f34346000af9f912a4c72ddd10ac986 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 16 Oct 2025 20:23:41 +0200 Subject: [PATCH 034/109] Launcher: implement wrapping versions of select next/previous --- Modules/Launcher/Launcher.qml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 271d4c4b..5d3a29b9 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -221,6 +221,18 @@ NPanel { } } + function selectNextWrapped() { + if (results.length > 0) { + selectedIndex = (selectedIndex + 1) % results.length + } + } + + function selectPreviousWrapped() { + if (results.length > 0) { + selectedIndex = (((selectedIndex - 1) % results.length) + results.length) % results.length + } + } + function selectFirst() { selectedIndex = 0 } From 0da0d460036b7e343e544dd8a94535edab30693b Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 16 Oct 2025 20:24:40 +0200 Subject: [PATCH 035/109] Launcher: implement tab bsed navigation with wrapping next/previous functions --- Modules/Launcher/Launcher.qml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 5d3a29b9..5deb4e85 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -279,6 +279,18 @@ NPanel { enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus } + Shortcut { + sequence: "Tab" + onActivated: ui.selectNextWrapped() + enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus + } + + Shortcut { + sequence: "Shift+Tab" + onActivated: ui.selectPreviousWrapped() + enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus + } + Shortcut { sequence: "PgDown" // or "PageDown" onActivated: ui.selectNextPage() From 7715dbf17a6682d9ffe4f3c9592d5f3f426fd572 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 16 Oct 2025 20:25:14 +0200 Subject: [PATCH 036/109] SessionMenu: implement wrapping select next/previous functions --- Modules/SessionMenu/SessionMenu.qml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index 7bdbf22c..abf0181b 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -131,6 +131,18 @@ NPanel { } } + function selectNextWrapped() { + if (powerOptions.length > 0) { + selectedIndex = (selectedIndex + 1) % powerOptions.length + } + } + + function selectPreviousWrapped() { + if (powerOptions.length > 0) { + selectedIndex = (((selectedIndex - 1) % powerOptions.length) + powerOptions.length) % powerOptions.length + } + } + function selectFirst() { selectedIndex = 0 } From ab59e94ee607654754b1276cbd0d05e6f6c2d1af Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Thu, 16 Oct 2025 20:25:50 +0200 Subject: [PATCH 037/109] SessionMenu: implement tab based navigation with wrapping next/previous functions --- Modules/SessionMenu/SessionMenu.qml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index abf0181b..b2a9b7fa 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -204,6 +204,18 @@ NPanel { enabled: root.opened } + Shortcut { + sequence: "Tab" + onActivated: ui.selectNextWrapped() + enabled: root.opened + } + + Shortcut { + sequence: "Shift+Tab" + onActivated: ui.selectPreviousWrapped() + enabled: root.opened + } + Shortcut { sequence: "Home" onActivated: ui.selectFirst() @@ -259,6 +271,14 @@ NPanel { root.selectLast() } + function selectNextWrapped() { + root.selectNextWrapped() + } + + function selectPreviousWrapped() { + root.selectPreviousWrapped() + } + function activate() { root.activate() } From e8f599be0a059328cb1fb75f03f8f28e1948c622 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 15:02:35 -0400 Subject: [PATCH 038/109] Added a new helpers (Debug.js) to help stringify objects with circular refs. --- Helpers/Debug.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Helpers/Debug.js diff --git a/Helpers/Debug.js b/Helpers/Debug.js new file mode 100644 index 00000000..90dc27e7 --- /dev/null +++ b/Helpers/Debug.js @@ -0,0 +1,25 @@ +// Helper function to stringify even if there are circular refs. +function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) +} + +function serializer(replacer, cycleReplacer) { + var stack = [], keys = [] + + if (cycleReplacer == null) cycleReplacer = function(key, value) { + if (stack[0] === value) return "[Circular ~]" + return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]" + } + + return function(key, value) { + if (stack.length > 0) { + var thisPos = stack.indexOf(this) + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) + if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value) + } + else stack.push(value) + + return replacer == null ? value : replacer.call(this, key, value) + } +} \ No newline at end of file From d1be2f7299806b9e7fe257be89a8a7e6e55fbc6b Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 15:10:04 -0400 Subject: [PATCH 039/109] Hyprland: fix missing classId when opening new apps. --- Services/HyprlandService.qml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index 25b4642f..dc71b42b 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -266,8 +266,21 @@ Item { if (!toplevel) return "" + var appId = "" + + // Try the wayland object first! + // From my (Lemmy) testing it works fine so we could probably get rid of all the other attempts below. + // Leaving them in for now, just in case... + try { + appId = toplevel.wayland.appId + console.lo + if (appId) + return appId + } catch (e) { + } + // Try direct properties - var appId = safeGetProperty(toplevel, "class", "") + appId = safeGetProperty(toplevel, "class", "") if (appId) return appId @@ -286,8 +299,6 @@ Item { return String(ipcData.class || ipcData.initialClass || ipcData.appId || ipcData.wm_class || "") } } catch (e) { - - // Ignore IPC errors } return "" @@ -329,7 +340,6 @@ Item { target: Hyprland enabled: initialized function onRawEvent(event) { - safeUpdateWorkspaces() workspaceChanged() updateTimer.restart() From 24cbbcbbc46e6b82846da2cfd544782f57fab92f Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 15:21:49 -0400 Subject: [PATCH 040/109] Rosepine: use proper surface color --- Assets/ColorScheme/Rosepine/Rosepine.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/ColorScheme/Rosepine/Rosepine.json b/Assets/ColorScheme/Rosepine/Rosepine.json index 5e36ba0a..6b6f2f52 100644 --- a/Assets/ColorScheme/Rosepine/Rosepine.json +++ b/Assets/ColorScheme/Rosepine/Rosepine.json @@ -8,7 +8,7 @@ "mOnTertiary": "#e0def4", "mError": "#eb6f92", "mOnError": "#191724", - "mSurface": "#191724", + "mSurface": "#1f1d2e", "mOnSurface": "#e0def4", "mSurfaceVariant": "#26233a", "mOnSurfaceVariant": "#908caa", @@ -24,7 +24,7 @@ "mOnTertiary": "#575279", "mError": "#b4637a", "mOnError": "#faf4ed", - "mSurface": "#faf4ed", + "mSurface": "#fffaf3", "mOnSurface": "#575279", "mSurfaceVariant": "#f2e9e1", "mOnSurfaceVariant": "#797593", From 3ddd5cf7a8737ce437245f776cb99f4b39c9a4f7 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 15:24:34 -0400 Subject: [PATCH 041/109] v2.18.1 --- Services/UpdateService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index a2ca41f5..8413219a 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "2.18.0" - property bool isDevelopment: true + property string baseVersion: "2.18.1" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 04c2243a9dec6b76ed45c7dd0e1c45ee480bbc04 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 15:24:53 -0400 Subject: [PATCH 042/109] dev version --- Services/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 8413219a..34b27996 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "2.18.1" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From d5b9abd9e08320c54c5e5ae1dd6c9b0944c69ae7 Mon Sep 17 00:00:00 2001 From: Kainoa Kanter Date: Thu, 16 Oct 2025 13:28:11 -0700 Subject: [PATCH 043/109] devex: precommit hook to run qmlfmt, parallelize `qmlfmt.sh` --- Bin/dev/qmlfmt.sh | 4 +++- Services/HyprlandService.qml | 4 +++- lefthook.yml | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 lefthook.yml diff --git a/Bin/dev/qmlfmt.sh b/Bin/dev/qmlfmt.sh index a7c6b548..6af303d2 100755 --- a/Bin/dev/qmlfmt.sh +++ b/Bin/dev/qmlfmt.sh @@ -4,4 +4,6 @@ # Can be installed from AUR "qmlfmt-git" # Requires qt6-5compat -find . -name "*.qml" -print -exec qmlfmt -e -b 360 -t 2 -i 2 -w {} \; +cd "$(git rev-parse --show-toplevel)" +echo "Formatting $(find . -name "*.qml" | wc -l) files..." +find . -name "*.qml" -print0 | xargs -0 -P "$(nproc)" -I {} qmlfmt -e -b 360 -t 2 -i 2 -w {} diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index dc71b42b..20a08f43 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -275,8 +275,9 @@ Item { appId = toplevel.wayland.appId console.lo if (appId) - return appId + return appId } catch (e) { + } // Try direct properties @@ -299,6 +300,7 @@ Item { return String(ipcData.class || ipcData.initialClass || ipcData.appId || ipcData.wm_class || "") } } catch (e) { + } return "" diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 00000000..b49937b1 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,4 @@ +pre-commit: + jobs: + - name: format qml + run: ./Bin/dev/qmlfmt.sh && git update-index --again From 144382f4acd2111e3ec7f28087a0d789033e4b8b Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 16:32:36 -0400 Subject: [PATCH 044/109] Hyprland: fix typo --- Services/HyprlandService.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index dc71b42b..1d50a7c6 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -273,7 +273,6 @@ Item { // Leaving them in for now, just in case... try { appId = toplevel.wayland.appId - console.lo if (appId) return appId } catch (e) { From a431d87dc1a6e85c2409abef56f554d440428f0e Mon Sep 17 00:00:00 2001 From: Kainoa Kanter Date: Thu, 16 Oct 2025 13:36:44 -0700 Subject: [PATCH 045/109] fix: cd in precommit hook, not in qmlfmt script --- Bin/dev/qmlfmt.sh | 1 - lefthook.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Bin/dev/qmlfmt.sh b/Bin/dev/qmlfmt.sh index 6af303d2..1e8bec26 100755 --- a/Bin/dev/qmlfmt.sh +++ b/Bin/dev/qmlfmt.sh @@ -4,6 +4,5 @@ # Can be installed from AUR "qmlfmt-git" # Requires qt6-5compat -cd "$(git rev-parse --show-toplevel)" echo "Formatting $(find . -name "*.qml" | wc -l) files..." find . -name "*.qml" -print0 | xargs -0 -P "$(nproc)" -I {} qmlfmt -e -b 360 -t 2 -i 2 -w {} diff --git a/lefthook.yml b/lefthook.yml index b49937b1..30dcc659 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,4 +1,4 @@ pre-commit: jobs: - name: format qml - run: ./Bin/dev/qmlfmt.sh && git update-index --again + run: cd "$(git rev-parse --show-toplevel)" && ./Bin/dev/qmlfmt.sh && git update-index --again From 9f46f8bed89bb63ddb6014493ff2999f266887a6 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 17 Oct 2025 06:30:55 +0800 Subject: [PATCH 046/109] refactor(bar): Use ternary operator for scaling property --- Modules/Bar/Extras/BarWidgetLoader.qml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml index 40610d3a..1f3b10a3 100644 --- a/Modules/Bar/Extras/BarWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -13,17 +13,7 @@ Item { property int sectionIndex: widgetProps && widgetProps.sectionWidgetIndex || 0 property string barDensity: "default" - readonly property real scaling: (function() { - switch (barDensity) { - case "mini": - return 0.8 - case "compact": - return 0.9 - case "comfortable": - default: - return 1.0 - } - })() + readonly property real scaling: barDensity === "mini" ? 0.8 : (barDensity === "compact" ? 0.9 : 1.0) // Don't reserve space unless the loaded widget is really visible implicitWidth: getImplicitSize(loader.item, "implicitWidth") From a230ec4a2df14f619782f04a8ced16741f7a15ce Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 17 Oct 2025 06:31:40 +0800 Subject: [PATCH 047/109] fix(mediamini): add applyuiscale --- Modules/Bar/Widgets/MediaMini.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 263164d5..21ab5c36 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -86,6 +86,7 @@ Item { visible: false text: titleText.text font: titleText.font + applyUiScale: false pointSize: Style.fontSizeS * scaling } @@ -299,6 +300,7 @@ Item { NText { text: hasActivePlayer ? getTitle() : placeholderText font: titleText.font + applyUiScale: false pointSize: Style.fontSizeS * scaling verticalAlignment: Text.AlignVCenter horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter From 68b34831627272186c957a52a25430791fc9ee22 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 17 Oct 2025 00:46:34 +0200 Subject: [PATCH 048/109] Launcher: replace select next/previous usage with wrapping version --- Modules/Launcher/Launcher.qml | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 5deb4e85..a89844ad 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -207,20 +207,6 @@ NPanel { // --------------------- // Navigation - function selectNext() { - if (results.length > 0) { - // Clamp the index to not exceed the last item - selectedIndex = Math.min(selectedIndex + 1, results.length - 1) - } - } - - function selectPrevious() { - if (results.length > 0) { - // Clamp the index to not go below the first item (0) - selectedIndex = Math.max(selectedIndex - 1, 0) - } - } - function selectNextWrapped() { if (results.length > 0) { selectedIndex = (selectedIndex + 1) % results.length @@ -269,13 +255,13 @@ NPanel { Shortcut { sequence: "Ctrl+K" - onActivated: ui.selectPrevious() + onActivated: ui.selectPreviousWrapped() enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus } Shortcut { sequence: "Ctrl+J" - onActivated: ui.selectNext() + onActivated: ui.selectNextWrapped() enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus } @@ -351,10 +337,10 @@ NPanel { } }) searchInput.inputItem.Keys.onDownPressed.connect(function (event) { - ui.selectNext() + ui.selectNextWrapped() }) searchInput.inputItem.Keys.onUpPressed.connect(function (event) { - ui.selectPrevious() + ui.selectPreviousWrapped() }) searchInput.inputItem.Keys.onReturnPressed.connect(function (event) { ui.activate() From 4be26bc6043339cec4ea870f1c38e1f3f36d3bda Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 17 Oct 2025 00:49:10 +0200 Subject: [PATCH 049/109] SessionMenu: replace select next/previous usage with wrapping version --- Modules/SessionMenu/SessionMenu.qml | 38 +++++++---------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index b2a9b7fa..83afaf5f 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -119,18 +119,6 @@ NPanel { } // Navigation functions - function selectNext() { - if (powerOptions.length > 0) { - selectedIndex = Math.min(selectedIndex + 1, powerOptions.length - 1) - } - } - - function selectPrevious() { - if (powerOptions.length > 0) { - selectedIndex = Math.max(selectedIndex - 1, 0) - } - } - function selectNextWrapped() { if (powerOptions.length > 0) { selectedIndex = (selectedIndex + 1) % powerOptions.length @@ -182,30 +170,24 @@ NPanel { // Keyboard shortcuts Shortcut { sequence: "Ctrl+K" - onActivated: ui.selectPrevious() + onActivated: ui.selectPreviousWrapped() enabled: root.opened } Shortcut { sequence: "Ctrl+J" - onActivated: ui.selectNext() + onActivated: ui.selectNextWrapped() enabled: root.opened } Shortcut { sequence: "Up" - onActivated: ui.selectPrevious() + onActivated: ui.selectPreviousWrapped() enabled: root.opened } Shortcut { sequence: "Down" - onActivated: ui.selectNext() - enabled: root.opened - } - - Shortcut { - sequence: "Tab" onActivated: ui.selectNextWrapped() enabled: root.opened } @@ -216,6 +198,12 @@ NPanel { enabled: root.opened } + Shortcut { + sequence: "Tab" + onActivated: ui.selectNextWrapped() + enabled: root.opened + } + Shortcut { sequence: "Home" onActivated: ui.selectFirst() @@ -255,14 +243,6 @@ NPanel { } // Navigation functions - function selectNext() { - root.selectNext() - } - - function selectPrevious() { - root.selectPrevious() - } - function selectFirst() { root.selectFirst() } From b0f6f22b2db54b70ef1cb271930d21ac16a4c8cf Mon Sep 17 00:00:00 2001 From: amadoabad Date: Fri, 17 Oct 2025 01:57:27 +0300 Subject: [PATCH 050/109] Feat: Freeze the notifications when hovering --- Modules/Notification/Notification.qml | 9 +++++++++ Services/NotificationService.qml | 22 ++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 3c97971a..757011a2 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -252,11 +252,20 @@ Variants { MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton + hoverEnabled: true onClicked: { if (mouse.button === Qt.RightButton) { animateOut() } } + + onContainsMouseChanged: { + if (containsMouse) { + NotificationService.pauseTimeout(notificationId) + } else { + NotificationService.resumeTimeout(notificationId) + } + } } // Scale, fade, and slide animation diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index 3d8c0298..4bb5042d 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -111,7 +111,9 @@ Singleton { notificationMetadata[data.id] = { "timestamp": data.timestamp.getTime(), "duration": expire, - "urgency": data.urgency + "urgency": data.urgency, + "paused": false, + "pauseTime": 0 } activeList.insert(0, data) @@ -219,7 +221,7 @@ Singleton { const notif = activeList.get(i) const meta = notificationMetadata[notif.id] - if (!meta || meta.duration === -1) + if (!meta || meta.duration === -1 || meta.paused) continue // Skip infinite notifications @@ -408,6 +410,22 @@ Singleton { return "" } + function pauseTimeout(id) { + const meta = notificationMetadata[id] + if (meta && !meta.paused) { + meta.paused = true + meta.pauseTime = Date.now() + } + } + + function resumeTimeout(id) { + const meta = notificationMetadata[id] + if (meta && meta.paused) { + meta.timestamp += Date.now() - meta.pauseTime + meta.paused = false + } + } + // Public API function dismissActiveNotification(id) { activeMap[id]?.dismiss() From 957dede6b355f8e6666d6ccbd0b9e74be6d0e9de Mon Sep 17 00:00:00 2001 From: amadoabad Date: Fri, 17 Oct 2025 02:12:21 +0300 Subject: [PATCH 051/109] Feat: Now freezes the notifications when hovering on notifications buttons too. --- Modules/Notification/Notification.qml | 35 +++++++++++++++++++++------ Widgets/NButton.qml | 4 +++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 757011a2..395672fd 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -248,24 +248,40 @@ Variants { // Staggered animation delay based on index readonly property int animationDelay: index * 100 + property int hoverCount: 0 + + onHoverCountChanged: { + if (hoverCount > 0) { + resumeTimer.stop() + NotificationService.pauseTimeout(notificationId) + } else { + resumeTimer.start() + } + } + + Timer { + id: resumeTimer + interval: 50 + repeat: false + onTriggered: { + if (hoverCount === 0) { + NotificationService.resumeTimeout(notificationId) + } + } + } + // Right-click to dismiss MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton hoverEnabled: true + onEntered: parent.hoverCount++ + onExited: parent.hoverCount-- onClicked: { if (mouse.button === Qt.RightButton) { animateOut() } } - - onContainsMouseChanged: { - if (containsMouse) { - NotificationService.pauseTimeout(notificationId) - } else { - NotificationService.resumeTimeout(notificationId) - } - } } // Scale, fade, and slide animation @@ -493,6 +509,9 @@ Variants { delegate: NButton { property var actionData: modelData + onEntered: card.hoverCount++ + onExited: card.hoverCount-- + text: { var actionText = actionData.text || "Open" // If text contains comma, take the part after the comma (the display text) diff --git a/Widgets/NButton.qml b/Widgets/NButton.qml index 654de952..9b97b5d2 100644 --- a/Widgets/NButton.qml +++ b/Widgets/NButton.qml @@ -25,6 +25,8 @@ Rectangle { signal clicked signal rightClicked signal middleClicked + signal entered + signal exited // Internal properties property bool hovered: false @@ -140,12 +142,14 @@ Rectangle { onEntered: { root.hovered = true + root.entered() if (tooltipText) { TooltipService.show(Screen, root, root.tooltipText) } } onExited: { root.hovered = false + root.exited() if (tooltipText) { TooltipService.hide() } From 0e620a683624b81d7790f4e6163eeca38f66ca6f Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 20:07:23 -0400 Subject: [PATCH 052/109] Hyprland: fix app title --- Services/HyprlandService.qml | 19 +++++++++++++++---- Services/SwayService.qml | 5 ++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index 2db855c3..07a34315 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -242,8 +242,8 @@ Item { if (!windowId) return null - const appId = extractAppId(toplevel) - const title = safeGetProperty(toplevel, "title", "") + const appId = getAppId(toplevel) + const title = getAppTitle(toplevel) const wsId = toplevel.workspace ? toplevel.workspace.id : null const focused = toplevel.activated === true const output = toplevel.monitor?.name || "" @@ -261,8 +261,19 @@ Item { } } - // Extract app ID from various possible sources - function extractAppId(toplevel) { + function getAppTitle(toplevel) { + try { + var title = toplevel.wayland.title + if (title) + return title + } catch (e) { + + } + + return safeGetProperty(toplevel, "title", "") + } + + function getAppId(toplevel) { if (!toplevel) return "" diff --git a/Services/SwayService.qml b/Services/SwayService.qml index 8306912a..1ca2e638 100644 --- a/Services/SwayService.qml +++ b/Services/SwayService.qml @@ -198,7 +198,7 @@ Item { try { // Safely extract properties - const appId = extractAppId(toplevel) + const appId = getAppId(toplevel) const title = safeGetProperty(toplevel, "title", "") const focused = toplevel.activated === true @@ -213,8 +213,7 @@ Item { } } - // Extract app ID from various possible sources - function extractAppId(toplevel) { + function getAppId(toplevel) { if (!toplevel) return "" From 61079c91031ef7982342809bb31f226e0372d599 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 20:21:30 -0400 Subject: [PATCH 053/109] MediaMini: no need to apply uiScaleRatio here. --- Modules/Bar/Widgets/MediaMini.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 21ab5c36..470bc37e 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -176,8 +176,8 @@ Item { spacing: 0 Item { - Layout.preferredWidth: Math.round(21 * Style.uiScaleRatio * scaling) - Layout.preferredHeight: Math.round(21 * Style.uiScaleRatio * scaling) + Layout.preferredWidth: Math.round(21 * scaling) + Layout.preferredHeight: Math.round(21 * scaling) NImageCircled { id: trackArt From 9f7d4d274d122bac13207c27db308adc37744561 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 20:21:44 -0400 Subject: [PATCH 054/109] autofmt --- Modules/Bar/Extras/BarWidgetLoader.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml index 93927c27..37ea0391 100644 --- a/Modules/Bar/Extras/BarWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -45,7 +45,9 @@ Item { } // Explicitly set scaling property if (item.hasOwnProperty("scaling")) { - item.scaling = Qt.binding(function() { return root.scaling; }) + item.scaling = Qt.binding(function () { + return root.scaling + }) } } From e352cc26ceebc47bdf9e158f38a605d7f800b40a Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 21:32:24 -0400 Subject: [PATCH 055/109] MediaIPC: added stop action. - Be aware that stop can't be resumed with play, from my testing once stopped the media is no longer available. --- Services/IPCService.qml | 4 ++++ Services/MediaService.qml | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/Services/IPCService.qml b/Services/IPCService.qml index 67eb2b1b..3d1d3924 100644 --- a/Services/IPCService.qml +++ b/Services/IPCService.qml @@ -215,6 +215,10 @@ Item { MediaService.play() } + function stop() { + MediaService.stop() + } + function pause() { MediaService.pause() } diff --git a/Services/MediaService.qml b/Services/MediaService.qml index 13c6a32f..30295bc5 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -200,6 +200,13 @@ Singleton { } } + function stop() { + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null + if (target) { + target.stop() + } + } + function pause() { let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null if (target && target.canPause) { From d56c197fd308c727350a3b6a11efeaaa43781079 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 16 Oct 2025 21:44:28 -0400 Subject: [PATCH 056/109] qmlfmt: restoring error reporting --- Bin/dev/qmlfmt.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Bin/dev/qmlfmt.sh b/Bin/dev/qmlfmt.sh index 1e8bec26..6f0fe315 100755 --- a/Bin/dev/qmlfmt.sh +++ b/Bin/dev/qmlfmt.sh @@ -1,8 +1,19 @@ #!/usr/bin/env -S bash +set -euo pipefail +# QML Formatter Script # Uses: https://github.com/jesperhh/qmlfmt -# Can be installed from AUR "qmlfmt-git" -# Requires qt6-5compat +# Install: AUR package "qmlfmt-git" (requires qt6-5compat) -echo "Formatting $(find . -name "*.qml" | wc -l) files..." -find . -name "*.qml" -print0 | xargs -0 -P "$(nproc)" -I {} qmlfmt -e -b 360 -t 2 -i 2 -w {} +command -v qmlfmt &>/dev/null || { echo "qmlfmt not found" >&2; exit 1; } + +format_file() { qmlfmt -e -b 360 -t 2 -i 2 -w "$1" || { echo "Failed: $1" >&2; return 1; }; } +export -f format_file + +mapfile -t files < <(find "${1:-.}" -name "*.qml" -type f) +[ ${#files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; } + +echo "Formatting ${#files[@]} files..." +printf '%s\0' "${files[@]}" | \ + xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \ + && echo "Done" || { echo "Errors occurred" >&2; exit 1; } \ No newline at end of file From 8e5e003f8ab806dd022245c038408b0f671ea4a9 Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:57:26 +0800 Subject: [PATCH 057/109] MediaMini: implement dynamic width with max width setting --- 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/zh-CN.json | 4 ++ Modules/Bar/Widgets/MediaMini.qml | 54 +++++++++++++++++-- .../Bar/WidgetSettings/MediaMiniSettings.qml | 11 ++++ Services/BarWidgetRegistry.qml | 1 + 9 files changed, 86 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 2be6197b..8991324b 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1071,6 +1071,10 @@ "label": "Visualizer-Typ", "description": "Stil des Audio-Visualizers auswählen." }, + "max-width": { + "label": "Maximale Breite", + "description": "Stellt die maximale Horizontalgröße des Widgets ein. Das Widget wird sich an kürzere Inhalte anpassen." + }, "scrolling-mode": { "label": "Scrollmodus", "description": "Steuern, wann Textscrolling für lange Track-Titel aktiviert ist." diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 915b75d1..15f35798 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1054,6 +1054,10 @@ "label": "Visualizer type", "description": "Choose the style of audio visualizer to display." }, + "max-width": { + "label": "Maximum Width", + "description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content." + }, "scrolling-mode": { "label": "Scrolling mode", "description": "Control when text scrolling is enabled for long track titles." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 72251b3f..644ed5ba 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1054,6 +1054,10 @@ "label": "Tipo de visualizador", "description": "Elegir el estilo de visualizador de audio a mostrar." }, + "max-width": { + "label": "Ancho Máximo", + "description": "Establece el tamaño horizontal máximo del widget. El widget se reducirá para adaptarse a contenido más corto." + }, "scrolling-mode": { "label": "Modo de desplazamiento", "description": "Controlar cuándo está habilitado el desplazamiento de texto para títulos de pista largos." diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8399a63a..e4a78db2 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1050,6 +1050,10 @@ "label": "Type de visualiseur", "description": "Choisir le style de visualiseur audio à afficher." }, + "max-width": { + "label": "Largeur Maximale", + "description": "Définit la taille horizontale maximale du widget. Le widget se rétrécira pour s'adapter à un contenu plus court." + }, "scrolling-mode": { "label": "Mode de défilement", "description": "Contrôler quand le défilement de texte est activé pour les titres de piste longs." diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index c4c3ea52..e2901797 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1054,6 +1054,10 @@ "label": "Tipo de visualizador", "description": "Escolher o estilo de visualizador de áudio a exibir." }, + "max-width": { + "label": "Largura Máxima", + "description": "Define o tamanho horizontal máximo do widget. O widget será reduzido para se adequar a conteúdo mais curto." + }, "scrolling-mode": { "label": "Modo de rolagem", "description": "Controlar quando a rolagem de texto está habilitada para títulos de faixa longos." diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 0116a5da..27504728 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1054,6 +1054,10 @@ "label": "可视化器类型", "description": "选择要显示的音频可视化器样式。" }, + "max-width": { + "label": "最大宽度", + "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" + }, "scrolling-mode": { "label": "滚动模式", "description": "控制何时为长曲目标题启用文本滚动。" diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 470bc37e..7f538b54 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -38,8 +38,8 @@ Item { readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType !== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode - // Fixed width - no expansion - readonly property real widgetWidth: Math.max(145, screen.width * 0.06) + // Maximum widget width with user settings support + readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) readonly property bool hasActivePlayer: MediaService.currentPlayer !== null readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player") @@ -60,7 +60,7 @@ Item { } implicitHeight: visible ? (isVerticalBar ? calculatedVerticalDimension() : Style.barHeight) : 0 - implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : widgetWidth) : 0 + implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : dynamicWidth) : 0 // "visible": Always Visible, "hidden": Hide When Empty, "transparent": Transparent When Empty visible: hideMode !== "hidden" || hasActivePlayer @@ -80,6 +80,44 @@ Item { return Math.round((Style.baseWidgetSize - 5) * scaling) } + function calculateContentWidth() { + // Calculate the actual content width based on visible elements + var contentWidth = 0 + var margins = Style.marginS * scaling * 2 // Left and right margins + + // Icon or album art width + if (!hasActivePlayer || !showAlbumArt) { + // Icon width + contentWidth += Style.fontSizeL * scaling + } else if (showAlbumArt && hasActivePlayer) { + // Album art width + contentWidth += 21 * scaling + } + + // Spacing between icon/art and text + contentWidth += Style.marginS * scaling + + // Text width (use the measured width) + contentWidth += fullTitleMetrics.contentWidth + + // Additional small margin for text + contentWidth += Style.marginXXS * 2 + + // Add container margins + contentWidth += margins + + return Math.ceil(contentWidth) + } + + // Dynamic width: adapt to content but respect maximum width setting + readonly property real dynamicWidth: { + if (!hasActivePlayer) { + return maxWidth + } + // Use content width but don't exceed user-set maximum width + return Math.min(calculateContentWidth(), maxWidth) + } + // A hidden text element to safely measure the full title width NText { id: fullTitleMetrics @@ -95,11 +133,19 @@ Item { visible: root.visible anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - width: isVerticalBar ? root.width : (widgetWidth) + width: isVerticalBar ? root.width : dynamicWidth height: isVerticalBar ? width : Style.capsuleHeight radius: isVerticalBar ? width / 2 : Style.radiusM color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent + // Smooth width transition + Behavior on width { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + Item { id: mainContainer anchors.fill: parent diff --git a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml index 1d03c87f..6d778510 100644 --- a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml @@ -19,6 +19,7 @@ ColumnLayout { property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer property string valueVisualizerType: widgetData.visualizerType || widgetMetadata.visualizerType property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode + property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth Component.onCompleted: { if (widgetData && widgetData.hideMode !== undefined) { @@ -33,6 +34,7 @@ ColumnLayout { settings.showVisualizer = valueShowVisualizer settings.visualizerType = valueVisualizerType settings.scrollingMode = valueScrollingMode + settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth return settings } @@ -87,6 +89,15 @@ ColumnLayout { minimumWidth: 200 } + NTextInput { + id: widthInput + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.media-mini.max-width.label") + description: I18n.tr("bar.widget-settings.media-mini.max-width.description") + placeholderText: widgetMetadata.maxWidth + text: valueMaxWidth + } + NComboBox { label: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.label") description: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.description") diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index bd134591..f23a7b16 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -88,6 +88,7 @@ Singleton { "hideMode": "hidden", "scrollingMode"// "visible", "hidden", "transparent" : "hover", + "maxWidth": 145, "showAlbumArt": false, "showVisualizer": false, "visualizerType": "linear" From a12fbca80b08ab0e9fa90b3d0245e26cad271a17 Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:11:53 +0800 Subject: [PATCH 058/109] ActiveWindow: implement dynamic width with max width setting --- Assets/Translations/de.json | 6 +-- Assets/Translations/en.json | 6 +-- Assets/Translations/es.json | 6 +-- Assets/Translations/fr.json | 6 +-- Assets/Translations/pt.json | 6 +-- Assets/Translations/zh-CN.json | 6 +-- Modules/Bar/Widgets/ActiveWindow.qml | 48 +++++++++++++++++-- .../WidgetSettings/ActiveWindowSettings.qml | 12 ++--- Services/BarWidgetRegistry.qml | 2 +- 9 files changed, 70 insertions(+), 28 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 2be6197b..88d32a40 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -918,9 +918,9 @@ "label": "Scrollmodus", "description": "Steuern, wann Textscrolling für lange Fenstertitel aktiviert ist." }, - "width": { - "description": "Steuert die horizontale Größe des Widgets.", - "label": "Widget-Breite" + "max-width": { + "label": "Maximale Breite", + "description": "Stellt die maximale Horizontalgröße des Widgets ein. Das Widget wird sich an kürzere Inhalte anpassen." }, "colorize-icons": { "label": "Symbole einfärben", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 915b75d1..ee7261de 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -901,9 +901,9 @@ "label": "Scrolling mode", "description": "Control when text scrolling is enabled for long window titles." }, - "width": { - "label": "Widget Width", - "description": "Controls the horizontal size of the widget." + "max-width": { + "label": "Maximum Width", + "description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content." }, "colorize-icons": { "label": "Colorize Icons", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 72251b3f..c0e891fb 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -901,9 +901,9 @@ "label": "Modo de desplazamiento", "description": "Controlar cuándo está habilitado el desplazamiento de texto para títulos de ventana largos." }, - "width": { - "description": "Controla el tamaño horizontal del widget.", - "label": "Ancho del widget" + "max-width": { + "label": "Ancho Máximo", + "description": "Establece el tamaño horizontal máximo del widget. El widget se reducirá para adaptarse a contenido más corto." }, "colorize-icons": { "label": "Colorear iconos", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8399a63a..2019a400 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -901,9 +901,9 @@ "label": "Mode de masquage", "description": "Contrôle le comportement du widget lorsqu'aucune fenêtre n'est active." }, - "width": { - "description": "Contrôle la taille horizontale du widget.", - "label": "Largeur du widget" + "max-width": { + "label": "Largeur Maximale", + "description": "Définit la taille horizontale maximale du widget. Le widget se rétrécira pour s'adapter à un contenu plus court." }, "colorize-icons": { "label": "Coloriser les icônes", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index c4c3ea52..537014f1 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -901,9 +901,9 @@ "label": "Modo de rolagem", "description": "Controlar quando a rolagem de texto está habilitada para títulos de janela longos." }, - "width": { - "description": "Controla o tamanho horizontal do widget.", - "label": "Largura do Widget" + "max-width": { + "label": "Largura Máxima", + "description": "Define o tamanho horizontal máximo do widget. O widget será reduzido para se adequar a conteúdo mais curto." }, "colorize-icons": { "label": "Colorir ícones", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 0116a5da..72e9c542 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -901,9 +901,9 @@ "label": "滚动模式", "description": "控制何时为长窗口标题启用文本滚动。" }, - "width": { - "description": "控制小部件的水平尺寸。", - "label": "小部件宽度" + "max-width": { + "label": "最大宽度", + "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" }, "colorize-icons": { "label": "着色图标", diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 145395d8..118e41cb 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -35,7 +35,9 @@ Item { readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : widgetMetadata.hideMode readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : (widgetMetadata.scrollingMode !== undefined ? widgetMetadata.scrollingMode : "hover") - readonly property int widgetWidth: (widgetSettings.width !== undefined) ? widgetSettings.width : Math.max(widgetMetadata.width, screen.width * 0.06) + + // Maximum widget width with user settings support + readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") readonly property bool hasFocusedWindow: CompositorService.getFocusedWindow() !== null @@ -43,7 +45,7 @@ Item { readonly property string fallbackIcon: "user-desktop" implicitHeight: visible ? (isVerticalBar ? calculatedVerticalDimension() : Style.barHeight) : 0 - implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : widgetWidth) : 0 + implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : dynamicWidth) : 0 // "visible": Always Visible, "hidden": Hide When Empty, "transparent": Transparent When Empty visible: hideMode !== "hidden" || hasFocusedWindow @@ -59,6 +61,38 @@ Item { return Math.round((Style.baseWidgetSize - 5) * scaling) } + function calculateContentWidth() { + // Calculate the actual content width based on visible elements + var contentWidth = 0 + var margins = Style.marginS * scaling * 2 // Left and right margins + + // Icon width (if visible) + if (showIcon) { + contentWidth += 18 * scaling + contentWidth += Style.marginS * scaling // Spacing after icon + } + + // Text width (use the measured width) + contentWidth += fullTitleMetrics.contentWidth + + // Additional small margin for text + contentWidth += Style.marginXXS * 2 + + // Add container margins + contentWidth += margins + + return Math.ceil(contentWidth) + } + + // Dynamic width: adapt to content but respect maximum width setting + readonly property real dynamicWidth: { + if (!hasFocusedWindow) { + return maxWidth + } + // Use content width but don't exceed user-set maximum width + return Math.min(calculateContentWidth(), maxWidth) + } + function getAppIcon() { try { // Try CompositorService first @@ -117,11 +151,19 @@ Item { visible: root.visible anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - width: isVerticalBar ? root.width : widgetWidth + width: isVerticalBar ? root.width : dynamicWidth height: isVerticalBar ? width : Style.capsuleHeight radius: isVerticalBar ? width / 2 : Style.radiusM color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent + // Smooth width transition + Behavior on width { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutCubic + } + } + Item { id: mainContainer anchors.fill: parent diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index 643684dd..0a4b5044 100644 --- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -17,7 +17,7 @@ ColumnLayout { property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon property string valueHideMode: "hidden" // Default to 'Hide When Empty' property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode - property int valueWidth: widgetData.width !== undefined ? widgetData.width : widgetMetadata.width + property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons Component.onCompleted: { @@ -31,7 +31,7 @@ ColumnLayout { settings.hideMode = valueHideMode settings.showIcon = valueShowIcon settings.scrollingMode = valueScrollingMode - settings.width = parseInt(widthInput.text) || widgetMetadata.width + settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth settings.colorizeIcons = valueColorizeIcons return settings } @@ -73,10 +73,10 @@ ColumnLayout { NTextInput { id: widthInput Layout.fillWidth: true - label: I18n.tr("bar.widget-settings.active-window.width.label") - description: I18n.tr("bar.widget-settings.active-window.width.description") - placeholderText: widgetMetadata.width - text: valueWidth + label: I18n.tr("bar.widget-settings.active-window.max-width.label") + description: I18n.tr("bar.widget-settings.active-window.max-width.description") + placeholderText: widgetMetadata.maxWidth + text: valueMaxWidth } NComboBox { diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index bd134591..f16bee0a 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -44,7 +44,7 @@ Singleton { "hideMode": "hidden", "scrollingMode"// "visible", "hidden", "transparent" : "hover", - "width": 145, + "maxWidth": 145, "colorizeIcons": false }, "Battery": { From 19bfeb2a40f6b5ac4f7f8e1f64ac0c719bdddc9b Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:28:32 +0800 Subject: [PATCH 059/109] MediaMini: add fixed width option --- 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/zh-CN.json | 4 ++++ Modules/Bar/Widgets/MediaMini.qml | 6 ++++++ .../Settings/Bar/WidgetSettings/MediaMiniSettings.qml | 9 +++++++++ Services/BarWidgetRegistry.qml | 1 + 9 files changed, 40 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 8991324b..21110d4f 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1075,6 +1075,10 @@ "label": "Maximale Breite", "description": "Stellt die maximale Horizontalgröße des Widgets ein. Das Widget wird sich an kürzere Inhalte anpassen." }, + "use-fixed-width": { + "label": "Feste Breite verwenden", + "description": "Wenn aktiviert, verwendet das Widget immer die maximale Breite, anstatt sich dynamisch an den Inhalt anzupassen." + }, "scrolling-mode": { "label": "Scrollmodus", "description": "Steuern, wann Textscrolling für lange Track-Titel aktiviert ist." diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 15f35798..7ad8c91b 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1058,6 +1058,10 @@ "label": "Maximum Width", "description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content." }, + "use-fixed-width": { + "label": "Use Fixed Width", + "description": "When enabled, the widget will always use the maximum width instead of dynamically adjusting to content." + }, "scrolling-mode": { "label": "Scrolling mode", "description": "Control when text scrolling is enabled for long track titles." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 644ed5ba..cbb96e68 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1058,6 +1058,10 @@ "label": "Ancho Máximo", "description": "Establece el tamaño horizontal máximo del widget. El widget se reducirá para adaptarse a contenido más corto." }, + "use-fixed-width": { + "label": "Usar Ancho Fijo", + "description": "Cuando está activado, el widget siempre usará el ancho máximo en lugar de ajustarse dinámicamente al contenido." + }, "scrolling-mode": { "label": "Modo de desplazamiento", "description": "Controlar cuándo está habilitado el desplazamiento de texto para títulos de pista largos." diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index e4a78db2..e783353d 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1054,6 +1054,10 @@ "label": "Largeur Maximale", "description": "Définit la taille horizontale maximale du widget. Le widget se rétrécira pour s'adapter à un contenu plus court." }, + "use-fixed-width": { + "label": "Utiliser une Largeur Fixe", + "description": "Lorsque activé, le widget utilisera toujours la largeur maximale au lieu de s'ajuster dynamiquement au contenu." + }, "scrolling-mode": { "label": "Mode de défilement", "description": "Contrôler quand le défilement de texte est activé pour les titres de piste longs." diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index e2901797..48bea69a 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1058,6 +1058,10 @@ "label": "Largura Máxima", "description": "Define o tamanho horizontal máximo do widget. O widget será reduzido para se adequar a conteúdo mais curto." }, + "use-fixed-width": { + "label": "Usar Largura Fixa", + "description": "Quando ativado, o widget sempre usará a largura máxima em vez de ajustar dinamicamente ao conteúdo." + }, "scrolling-mode": { "label": "Modo de rolagem", "description": "Controlar quando a rolagem de texto está habilitada para títulos de faixa longos." diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 27504728..1fc13bda 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1058,6 +1058,10 @@ "label": "最大宽度", "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" }, + "use-fixed-width": { + "label": "使用固定宽度", + "description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。" + }, "scrolling-mode": { "label": "滚动模式", "description": "控制何时为长曲目标题启用文本滚动。" diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 7f538b54..38a56a34 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -40,6 +40,7 @@ Item { // Maximum widget width with user settings support readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) + readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth readonly property bool hasActivePlayer: MediaService.currentPlayer !== null readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player") @@ -111,6 +112,11 @@ Item { // Dynamic width: adapt to content but respect maximum width setting readonly property real dynamicWidth: { + // If using fixed width mode, always use maxWidth + if (useFixedWidth) { + return maxWidth + } + // Otherwise, adapt to content if (!hasActivePlayer) { return maxWidth } diff --git a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml index 6d778510..5e109feb 100644 --- a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml @@ -20,6 +20,7 @@ ColumnLayout { property string valueVisualizerType: widgetData.visualizerType || widgetMetadata.visualizerType property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth + property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth Component.onCompleted: { if (widgetData && widgetData.hideMode !== undefined) { @@ -35,6 +36,7 @@ ColumnLayout { settings.visualizerType = valueVisualizerType settings.scrollingMode = valueScrollingMode settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth + settings.useFixedWidth = valueUseFixedWidth return settings } @@ -98,6 +100,13 @@ ColumnLayout { text: valueMaxWidth } + NToggle { + label: I18n.tr("bar.widget-settings.media-mini.use-fixed-width.label") + description: I18n.tr("bar.widget-settings.media-mini.use-fixed-width.description") + checked: valueUseFixedWidth + onToggled: checked => valueUseFixedWidth = checked + } + NComboBox { label: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.label") description: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.description") diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index f23a7b16..ad78c31f 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -89,6 +89,7 @@ Singleton { "scrollingMode"// "visible", "hidden", "transparent" : "hover", "maxWidth": 145, + "useFixedWidth": false, "showAlbumArt": false, "showVisualizer": false, "visualizerType": "linear" From 0ab65f7f7e62a732e68199618475a6ce7c1d1b61 Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:38:39 +0800 Subject: [PATCH 060/109] ActiveWindow: add fixed width option --- 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/zh-CN.json | 4 ++++ Modules/Bar/Widgets/ActiveWindow.qml | 6 ++++++ .../Bar/WidgetSettings/ActiveWindowSettings.qml | 10 ++++++++++ Services/BarWidgetRegistry.qml | 1 + 9 files changed, 41 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 88d32a40..fd7f64d7 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -922,6 +922,10 @@ "label": "Maximale Breite", "description": "Stellt die maximale Horizontalgröße des Widgets ein. Das Widget wird sich an kürzere Inhalte anpassen." }, + "use-fixed-width": { + "label": "Feste Breite verwenden", + "description": "Wenn aktiviert, verwendet das Widget immer die maximale Breite, anstatt sich dynamisch an den Inhalt anzupassen." + }, "colorize-icons": { "label": "Symbole einfärben", "description": "Theme-Farben auf das aktive Fenster-Symbol anwenden." diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index ee7261de..bd6a676e 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -905,6 +905,10 @@ "label": "Maximum Width", "description": "Sets the maximum horizontal size of the widget. The widget will shrink to fit shorter content." }, + "use-fixed-width": { + "label": "Use Fixed Width", + "description": "When enabled, the widget will always use the maximum width instead of dynamically adjusting to content." + }, "colorize-icons": { "label": "Colorize Icons", "description": "Apply theme colors to active window icon." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index c0e891fb..e8de1458 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -905,6 +905,10 @@ "label": "Ancho Máximo", "description": "Establece el tamaño horizontal máximo del widget. El widget se reducirá para adaptarse a contenido más corto." }, + "use-fixed-width": { + "label": "Usar Ancho Fijo", + "description": "Cuando está activado, el widget siempre usará el ancho máximo en lugar de ajustarse dinámicamente al contenido." + }, "colorize-icons": { "label": "Colorear iconos", "description": "Aplicar colores del tema al icono de la ventana activa." diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 2019a400..7ed8fb63 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -905,6 +905,10 @@ "label": "Largeur Maximale", "description": "Définit la taille horizontale maximale du widget. Le widget se rétrécira pour s'adapter à un contenu plus court." }, + "use-fixed-width": { + "label": "Utiliser une Largeur Fixe", + "description": "Lorsque activé, le widget utilisera toujours la largeur maximale au lieu de s'ajuster dynamiquement au contenu." + }, "colorize-icons": { "label": "Coloriser les icônes", "description": "Appliquer les couleurs du thème à l'icône de la fenêtre active." diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 537014f1..3d652ff7 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -905,6 +905,10 @@ "label": "Largura Máxima", "description": "Define o tamanho horizontal máximo do widget. O widget será reduzido para se adequar a conteúdo mais curto." }, + "use-fixed-width": { + "label": "Usar Largura Fixa", + "description": "Quando ativado, o widget sempre usará a largura máxima em vez de ajustar dinamicamente ao conteúdo." + }, "colorize-icons": { "label": "Colorir ícones", "description": "Aplicar cores do tema ao ícone da janela ativa." diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 72e9c542..96b71ed1 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -905,6 +905,10 @@ "label": "最大宽度", "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。" }, + "use-fixed-width": { + "label": "使用固定宽度", + "description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。" + }, "colorize-icons": { "label": "着色图标", "description": "将主题颜色应用到活动窗口图标。" diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 118e41cb..4fdf3d2b 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -38,6 +38,7 @@ Item { // Maximum widget width with user settings support readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) + readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") readonly property bool hasFocusedWindow: CompositorService.getFocusedWindow() !== null @@ -86,6 +87,11 @@ Item { // Dynamic width: adapt to content but respect maximum width setting readonly property real dynamicWidth: { + // If using fixed width mode, always use maxWidth + if (useFixedWidth) { + return maxWidth + } + // Otherwise, adapt to content if (!hasFocusedWindow) { return maxWidth } diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index 0a4b5044..514733dd 100644 --- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -18,6 +18,7 @@ ColumnLayout { property string valueHideMode: "hidden" // Default to 'Hide When Empty' property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth + property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons Component.onCompleted: { @@ -32,6 +33,7 @@ ColumnLayout { settings.showIcon = valueShowIcon settings.scrollingMode = valueScrollingMode settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth + settings.useFixedWidth = valueUseFixedWidth settings.colorizeIcons = valueColorizeIcons return settings } @@ -79,6 +81,14 @@ ColumnLayout { text: valueMaxWidth } + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.active-window.use-fixed-width.label") + description: I18n.tr("bar.widget-settings.active-window.use-fixed-width.description") + checked: valueUseFixedWidth + onToggled: checked => valueUseFixedWidth = checked + } + NComboBox { label: I18n.tr("bar.widget-settings.active-window.scrolling-mode.label") description: I18n.tr("bar.widget-settings.active-window.scrolling-mode.description") diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index f16bee0a..e7f2865c 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -45,6 +45,7 @@ Singleton { "scrollingMode"// "visible", "hidden", "transparent" : "hover", "maxWidth": 145, + "useFixedWidth": false, "colorizeIcons": false }, "Battery": { From 16d3220d1a89c23986cfdc26d37007054c952d49 Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 17 Oct 2025 12:59:15 +0200 Subject: [PATCH 061/109] OSD: fix input showing up on fresh start --- Modules/OSD/OSD.qml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 23c63a0b..e2316825 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -480,15 +480,19 @@ Variants { } function onInputVolumeChanged() { - if (inputVolumeInitialized) { - showOSD("inputVolume") + if (!inputVolumeInitialized) { + inputVolumeInitialized = true + return } + showOSD("inputVolume") } function onInputMutedChanged() { - if (inputMuteInitialized) { - showOSD("inputVolume") + if (!inputMuteInitialized) { + inputMuteInitialized = true + return } + showOSD("inputVolume") } } @@ -500,9 +504,7 @@ Variants { onTriggered: { volumeInitialized = true muteInitialized = true - inputVolumeInitialized = true - inputMuteInitialized = true - // Don't initialize brightness here - let it initialize on first change like volume + // Input volume/mute and brightness initialize on first change to avoid showing OSD on startup connectBrightnessMonitors() } } From 371ddbc7769d049c6498d24234b7ccb76e3dd74a Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 17 Oct 2025 13:13:23 +0200 Subject: [PATCH 062/109] HyprlandService: fix workspace detection (fixes #509) Autoformat --- Modules/Notification/Notification.qml | 26 +++++++++++++------------- Services/HyprlandService.qml | 2 ++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 395672fd..00b93297 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -251,23 +251,23 @@ Variants { property int hoverCount: 0 onHoverCountChanged: { - if (hoverCount > 0) { - resumeTimer.stop() - NotificationService.pauseTimeout(notificationId) - } else { - resumeTimer.start() - } + if (hoverCount > 0) { + resumeTimer.stop() + NotificationService.pauseTimeout(notificationId) + } else { + resumeTimer.start() + } } Timer { - id: resumeTimer - interval: 50 - repeat: false - onTriggered: { - if (hoverCount === 0) { - NotificationService.resumeTimeout(notificationId) - } + id: resumeTimer + interval: 50 + repeat: false + onTriggered: { + if (hoverCount === 0) { + NotificationService.resumeTimeout(notificationId) } + } } // Right-click to dismiss diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index 07a34315..b62d642c 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -352,6 +352,8 @@ Item { target: Hyprland enabled: initialized function onRawEvent(event) { + Hyprland.refreshWorkspaces() + safeUpdateWorkspaces() workspaceChanged() updateTimer.restart() From eae2bf14763ddb791e9d8fba34c5adc91f002adc Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 17 Oct 2025 13:33:31 +0200 Subject: [PATCH 063/109] IdleInhibitor: initialize at startup, remove persistent setting (#505) --- Assets/settings-default.json | 1 - Commons/Settings.qml | 1 - Services/IdleInhibitorService.qml | 16 ++++------------ shell.qml | 1 + 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index dbb01840..ce680d75 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -207,7 +207,6 @@ "fontFixed": "DejaVu Sans Mono", "fontDefaultScale": 1, "fontFixedScale": 1, - "idleInhibitorEnabled": false, "tooltipsEnabled": true }, "brightness": { diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 0c5fb9c6..1f925537 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -342,7 +342,6 @@ Singleton { property string fontFixed: "DejaVu Sans Mono" property real fontDefaultScale: 1.0 property real fontFixedScale: 1.0 - property bool idleInhibitorEnabled: false property bool tooltipsEnabled: true } diff --git a/Services/IdleInhibitorService.qml b/Services/IdleInhibitorService.qml index 964430c8..ab20dd94 100644 --- a/Services/IdleInhibitorService.qml +++ b/Services/IdleInhibitorService.qml @@ -16,15 +16,9 @@ Singleton { // Different inhibitor strategies property string strategy: "systemd" // "systemd", "wayland", or "auto" - Component.onCompleted: { - Logger.i("IdleInhibitor", "Service started") + function init() { + Logger.d("IdleInhibitor", "Service started") detectStrategy() - - // Restore previous state from settings - if (Settings.data.ui.idleInhibitorEnabled) { - addInhibitor("manual", "Restored from previous session") - Logger.d("IdleInhibitor", "Restored previous manual inhibition state") - } } // Auto-detect the best strategy @@ -162,15 +156,13 @@ Singleton { function manualToggle() { if (activeInhibitors.includes("manual")) { removeInhibitor("manual") - Settings.data.ui.idleInhibitorEnabled = false ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("toast.keep-awake.disabled")) - Logger.i("IdleInhibitor", "Manual inhibition disabled and saved to settings") + Logger.i("IdleInhibitor", "Manual inhibition disabled") return false } else { addInhibitor("manual", "Manually activated by user") - Settings.data.ui.idleInhibitorEnabled = true ToastService.showNotice(I18n.tr("tooltips.keep-awake"), I18n.tr("toast.keep-awake.enabled")) - Logger.i("IdleInhibitor", "Manual inhibition enabled and saved to settings") + Logger.i("IdleInhibitor", "Manual inhibition enabled (will reset on next session)") return true } } diff --git a/shell.qml b/shell.qml index e4645fbe..26022135 100644 --- a/shell.qml +++ b/shell.qml @@ -90,6 +90,7 @@ ShellRoot { HooksService.init() BluetoothService.init() BatteryService.init() + IdleInhibitorService.init() } Background {} From 51163f7d9642eca935b8518e7ef4616d990b25ea Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 17 Oct 2025 13:37:31 +0200 Subject: [PATCH 064/109] OSD: actually fix input volume on startup --- Modules/OSD/OSD.qml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index e2316825..5c98c45d 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -35,8 +35,7 @@ Variants { // Input volume properties readonly property real currentInputVolume: AudioService.inputVolume readonly property bool isInputMuted: AudioService.inputMuted - property bool inputVolumeInitialized: false - property bool inputMuteInitialized: false + property bool inputAudioInitialized: false // Brightness properties property real lastUpdatedBrightness: 0 @@ -480,16 +479,14 @@ Variants { } function onInputVolumeChanged() { - if (!inputVolumeInitialized) { - inputVolumeInitialized = true + if (!inputAudioInitialized) { return } showOSD("inputVolume") } function onInputMutedChanged() { - if (!inputMuteInitialized) { - inputMuteInitialized = true + if (!inputAudioInitialized) { return } showOSD("inputVolume") @@ -504,7 +501,8 @@ Variants { onTriggered: { volumeInitialized = true muteInitialized = true - // Input volume/mute and brightness initialize on first change to avoid showing OSD on startup + inputAudioInitialized = true + // Brightness initializes on first change to avoid showing OSD on startup connectBrightnessMonitors() } } From 87f4559764f793838d5f712014ed0653ffae6342 Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 17 Oct 2025 13:48:43 +0200 Subject: [PATCH 065/109] LockScreen: add reveal password button --- Modules/LockScreen/LockScreen.qml | 77 ++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 68d1c114..09a91ff8 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -862,6 +862,8 @@ Loader { border.color: passwordInput.activeFocus ? Color.mPrimary : Qt.alpha(Color.mOutline, 0.3) border.width: passwordInput.activeFocus ? 2 : 1 + property bool passwordVisible: false + Row { anchors.left: parent.left anchors.leftMargin: 18 @@ -884,7 +886,7 @@ Loader { enabled: !lockContext.unlockInProgress font.pointSize: Style.fontSizeM color: Color.mPrimary - echoMode: TextInput.Password + echoMode: parent.parent.passwordVisible ? TextInput.Normal : TextInput.Password passwordCharacter: "•" passwordMaskDelay: 0 text: lockContext.currentText @@ -923,23 +925,40 @@ Loader { } } - Row { - spacing: 6 - visible: passwordInput.text.length > 0 + // Password display - show dots or actual text based on passwordVisible + Item { + width: passwordDisplayContent.width + height: 20 + visible: passwordInput.text.length > 0 && !parent.parent.parent.passwordVisible anchors.verticalCenter: parent.verticalCenter - Repeater { - model: passwordInput.text.length + Row { + id: passwordDisplayContent + spacing: 6 + anchors.verticalCenter: parent.verticalCenter - NIcon { - icon: "circle-filled" - pointSize: Style.fontSizeS - color: Color.mPrimary - opacity: 1.0 + Repeater { + model: passwordInput.text.length + + NIcon { + icon: "circle-filled" + pointSize: Style.fontSizeS + color: Color.mPrimary + opacity: 1.0 + } } } } + NText { + text: passwordInput.text + color: Color.mPrimary + pointSize: Style.fontSizeM + font.weight: Font.Medium + visible: passwordInput.text.length > 0 && parent.parent.parent.passwordVisible + anchors.verticalCenter: parent.verticalCenter + } + Rectangle { width: 2 height: 20 @@ -963,7 +982,43 @@ Loader { } } + // Eye button to toggle password visibility Rectangle { + anchors.right: submitButton.left + anchors.rightMargin: 4 + anchors.verticalCenter: parent.verticalCenter + width: 36 + height: 36 + radius: width * 0.5 + color: eyeButtonArea.containsMouse ? Qt.alpha(Color.mOnSurface, 0.1) : "transparent" + visible: passwordInput.text.length > 0 + enabled: !lockContext.unlockInProgress + + NIcon { + anchors.centerIn: parent + icon: parent.parent.passwordVisible ? "eye-off" : "eye" + pointSize: Style.fontSizeM + color: Color.mOnSurfaceVariant + } + + MouseArea { + id: eyeButtonArea + anchors.fill: parent + hoverEnabled: true + onClicked: parent.parent.passwordVisible = !parent.parent.passwordVisible + } + + Behavior on color { + ColorAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } + } + + // Submit button + Rectangle { + id: submitButton anchors.right: parent.right anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter From d5360a7dde2331bf38a4b67f75dc9699bb209612 Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 17 Oct 2025 15:32:22 +0200 Subject: [PATCH 066/109] ColorSchemeTab: make discord toggle look better --- Modules/Settings/Tabs/ColorSchemeTab.qml | 60 +++++++++++++++--------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index f72597ea..7dc9db1e 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -513,29 +513,47 @@ ColumnLayout { } } - // Show individual checkboxes for each detected Discord client - Repeater { - model: ProgramCheckerService.availableDiscordClients - delegate: NCheckbox { - label: modelData.name.charAt(0).toUpperCase() + modelData.name.slice(1) - description: I18n.tr("settings.color-scheme.templates.programs.discord.description", { - "client": modelData.name.charAt(0).toUpperCase() + modelData.name.slice(1), - "filepath": modelData.themePath - }) - checked: Settings.data.templates["discord_" + modelData.name] || false - onToggled: checked => { - Settings.data.templates["discord_" + modelData.name] = checked + // Discord clients - single toggle with dynamic description + NCheckbox { + id: discordToggle + label: "Discord" + description: { + if (ProgramCheckerService.availableDiscordClients.length === 0) { + return I18n.tr("settings.color-scheme.templates.programs.discord.description-missing") + } else { + // Show detected clients + var clientInfo = [] + for (var i = 0; i < ProgramCheckerService.availableDiscordClients.length; i++) { + var client = ProgramCheckerService.availableDiscordClients[i] + clientInfo.push(client.name.charAt(0).toUpperCase() + client.name.slice(1)) + } + return "Detected: " + clientInfo.join(", ") + } + } + checked: { + // Check if any Discord client template is enabled + var anyEnabled = false + for (var i = 0; i < ProgramCheckerService.availableDiscordClients.length; i++) { + var client = ProgramCheckerService.availableDiscordClients[i] + if (Settings.data.templates["discord_" + client.name]) { + anyEnabled = true + break + } + } + return anyEnabled + } + enabled: ProgramCheckerService.availableDiscordClients.length > 0 + opacity: ProgramCheckerService.availableDiscordClients.length > 0 ? 1.0 : 0.6 + onToggled: checked => { + // Enable/disable all detected Discord clients + for (var i = 0; i < ProgramCheckerService.availableDiscordClients.length; i++) { + var client = ProgramCheckerService.availableDiscordClients[i] + Settings.data.templates["discord_" + client.name] = checked + } + if (ProgramCheckerService.availableDiscordClients.length > 0) { AppThemeService.generate() } - } - } - - // Show message if no Discord clients detected - NText { - visible: ProgramCheckerService.availableDiscordClients.length === 0 - text: I18n.tr("settings.color-scheme.templates.programs.discord.description-missing") - color: Color.mOnSurfaceVariant - pointSize: Style.fontSizeS + } } NCheckbox { From e1ec5c614c142ff94dab8beb1beb92281a77a76f Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 17 Oct 2025 15:40:44 +0200 Subject: [PATCH 067/109] Matugen: add vencord support ColorSchemeTab: add proper line break for discord templates --- 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/zh-CN.json | 2 +- Modules/Settings/Tabs/ColorSchemeTab.qml | 2 ++ Services/MatugenTemplates.qml | 15 ++++++++++++--- Services/ProgramCheckerService.qml | 19 +++++++++++++++---- 9 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 2be6197b..aed4c10c 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -537,7 +537,7 @@ }, "discord": { "description": "Schreibt {filepath} für {client}", - "description-missing": "Kein Discord-Client erkannt. Installieren Sie vesktop, webcord, armcord, equibop, lightcord oder dorion." + "description-missing": "Kein Discord-Client erkannt. Installieren Sie vencord, vesktop, webcord, armcord, equibop, lightcord oder dorion." }, "pywalfox": { "description": "Schreibt {filepath} und führt pywalfox update aus", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 915b75d1..f18e6ac9 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -541,7 +541,7 @@ }, "discord": { "description": "Write {filepath} for {client}", - "description-missing": "No Discord client detected. Install vesktop, webcord, armcord, equibop, lightcord, or dorion." + "description-missing": "No Discord client detected. Install vencord, vesktop, webcord, armcord, equibop, lightcord, or dorion." }, "pywalfox": { "description": "Write {filepath} and run pywalfox update", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 72251b3f..a00881fe 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -537,7 +537,7 @@ }, "discord": { "description": "Escribir {filepath} para {client}", - "description-missing": "No se detectó cliente de Discord. Instala vesktop, webcord, armcord, equibop, lightcord o dorion." + "description-missing": "No se detectó cliente de Discord. Instala vencord, vesktop, webcord, armcord, equibop, lightcord o dorion." }, "pywalfox": { "description": "Escribir {filepath} y ejecutar pywalfox update", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 8399a63a..ec9af8ac 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -537,7 +537,7 @@ }, "discord": { "description": "Écrire {filepath} pour {client}", - "description-missing": "Aucun client Discord détecté. Installez vesktop, webcord, armcord, equibop, lightcord ou dorion." + "description-missing": "Aucun client Discord détecté. Installez vencord, vesktop, webcord, armcord, equibop, lightcord ou dorion." }, "pywalfox": { "description": "Écrire ~/.cache/wal/colors.json et exécuter pywalfox update", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index c4c3ea52..02979bf1 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -499,7 +499,7 @@ }, "discord": { "description": "Escrever {filepath} para {client}", - "description-missing": "Nenhum cliente Discord detectado. Instale vesktop, webcord, armcord, equibop, lightcord ou dorion." + "description-missing": "Nenhum cliente Discord detectado. Instale vencord, vesktop, webcord, armcord, equibop, lightcord ou dorion." }, "pywalfox": { "description": "Escrever {filepath} e executar pywalfox update", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 0116a5da..a251a5d4 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -537,7 +537,7 @@ }, "discord": { "description": "为 {client} 写入 {filepath}", - "description-missing": "未检测到 Discord 客户端。请安装 vesktop、webcord、armcord、equibop、lightcord 或 dorion。" + "description-missing": "未检测到 Discord 客户端。请安装 vencord、vesktop、webcord、armcord、equibop、lightcord 或 dorion。" }, "pywalfox": { "description": "写入 {filepath} 并运行 pywalfox update", diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 7dc9db1e..ddf48db1 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -530,6 +530,8 @@ ColumnLayout { return "Detected: " + clientInfo.join(", ") } } + Layout.fillWidth: true + Layout.preferredWidth: -1 checked: { // Check if any Discord client template is enabled var anyEnabled = false diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index c7da9c3c..75c83db4 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -115,9 +115,9 @@ Singleton { "input": "pywalfox.json", "postHook": AppThemeService.colorsApplyScript + " pywalfox" }, { - "name": "discord_vesktop", + "name": "discord_vesktops", "templates": [{ - "version": "discord_vesktop", + "version": "discord_vesktops", "output": "~/.config/vesktop/themes/noctalia.theme.css" }], "input": "vesktop.css" @@ -156,6 +156,14 @@ Singleton { "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 }] // -------------------------------- @@ -193,7 +201,8 @@ Singleton { clients.push({ "name": clientName, "configPath": configPath, - "themePath": themePath + "themePath": themePath, + "requiresThemesFolder": app.requiresThemesFolder || false }) } } diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml index 5335ed52..790fa71a 100644 --- a/Services/ProgramCheckerService.qml +++ b/Services/ProgramCheckerService.qml @@ -28,14 +28,25 @@ Singleton { // Function to detect Discord client by checking config directories function detectDiscordClient() { - // Build list of client names from MatugenTemplates - var clientNames = [] + // Build shell script to check each client + var scriptParts = ["available_clients=\"\";"] + for (var i = 0; i < MatugenTemplates.discordClients.length; i++) { - clientNames.push(MatugenTemplates.discordClients[i].name) + var client = MatugenTemplates.discordClients[i] + var clientName = client.name + + // Check if this client requires themes folder to exist + if (client.requiresThemesFolder) { + scriptParts.push("if [ -d \"$HOME/.config/" + clientName + "/themes\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;") + } else { + scriptParts.push("if [ -d \"$HOME/.config/" + clientName + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;") + } } + scriptParts.push("echo \"$available_clients\"") + // Use a Process to check directory existence for all clients - discordDetector.command = ["sh", "-c", "available_clients=\"\"; " + "for client in " + clientNames.join(" ") + "; do " + " if [ -d \"$HOME/.config/$client\" ]; then " + " available_clients=\"$available_clients $client\"; " + " fi; " + "done; " + "echo \"$available_clients\""] + discordDetector.command = ["sh", "-c", scriptParts.join(" ")] discordDetector.running = true } From 0ed82fbae8902eab4b19a10eebc94444d3a25893 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 10:23:54 -0400 Subject: [PATCH 068/109] BarPill: fix potential wrong tooltip update --- Modules/Bar/Extras/BarPillHorizontal.qml | 4 +++- Modules/Bar/Extras/BarPillVertical.qml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Modules/Bar/Extras/BarPillHorizontal.qml b/Modules/Bar/Extras/BarPillHorizontal.qml index 0c2b5e90..5a732263 100644 --- a/Modules/Bar/Extras/BarPillHorizontal.qml +++ b/Modules/Bar/Extras/BarPillHorizontal.qml @@ -67,7 +67,9 @@ Item { Connections { target: root function onTooltipTextChanged() { - TooltipService.updateText(root.tooltipText) + if (hovered) { + TooltipService.updateText(root.tooltipText) + } } } diff --git a/Modules/Bar/Extras/BarPillVertical.qml b/Modules/Bar/Extras/BarPillVertical.qml index c8b53997..485d9732 100644 --- a/Modules/Bar/Extras/BarPillVertical.qml +++ b/Modules/Bar/Extras/BarPillVertical.qml @@ -77,7 +77,9 @@ Item { Connections { target: root function onTooltipTextChanged() { - TooltipService.updateText(root.tooltipText) + if (hovered) { + TooltipService.updateText(root.tooltipText) + } } } From 5146479225b7206bae7e1c60065e2c04cec8b56a Mon Sep 17 00:00:00 2001 From: Lysec <52084453+Ly-sec@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:31:15 +0200 Subject: [PATCH 069/109] Matugen: fix vesktop template --- Services/MatugenTemplates.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index 75c83db4..68ceec44 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -115,9 +115,9 @@ Singleton { "input": "pywalfox.json", "postHook": AppThemeService.colorsApplyScript + " pywalfox" }, { - "name": "discord_vesktops", + "name": "discord_vesktop", "templates": [{ - "version": "discord_vesktops", + "version": "discord_vesktop", "output": "~/.config/vesktop/themes/noctalia.theme.css" }], "input": "vesktop.css" From 9621dbb4d68a777c3ca66adfa56d2c1e12b04eab Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 10:32:18 -0400 Subject: [PATCH 070/109] Tooltip: proper size update when text changes --- Modules/Tooltip/Tooltip.qml | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml index 43a6850c..3f5e6ca4 100644 --- a/Modules/Tooltip/Tooltip.qml +++ b/Modules/Tooltip/Tooltip.qml @@ -321,13 +321,38 @@ PopupWindow { completeHide() } - // Update text function for binding support - function updateText(newText) { - if (visible && targetItem) { - text = newText - positionAndShow() +// Update text function for binding support +function updateText(newText) { + if (visible && targetItem) { + text = newText + + // Recalculate dimensions + const tipWidth = Math.min(tooltipText.implicitWidth + (padding * 2), maxWidth) + root.implicitWidth = tipWidth + + const tipHeight = tooltipText.implicitHeight + (padding * 2) + root.implicitHeight = tipHeight + + // Reposition if necessary + var targetGlobal = targetItem.mapToItem(null, 0, 0) + const targetWidth = targetItem.width + + // Adjust horizontal position to keep tooltip on screen if needed + const globalX = targetGlobal.x + anchorX + if (globalX < 0) { + anchorX = -targetGlobal.x + margin + } else if (globalX + tipWidth > screenWidth) { + anchorX = screenWidth - targetGlobal.x - tipWidth - margin } + + // Force anchor update + Qt.callLater(() => { + if (root.anchor && root.visible) { + root.anchor.updateAnchor() + } + }) } +} // Reset function to clean up state function reset() { From 702cd1d283e5ffec8db63b84ac70535ec0327360 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 10:33:46 -0400 Subject: [PATCH 071/109] Test commit for lefthook. --- Modules/Tooltip/Tooltip.qml | 60 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml index 3f5e6ca4..00c139b6 100644 --- a/Modules/Tooltip/Tooltip.qml +++ b/Modules/Tooltip/Tooltip.qml @@ -321,38 +321,38 @@ PopupWindow { completeHide() } -// Update text function for binding support -function updateText(newText) { - if (visible && targetItem) { - text = newText - - // Recalculate dimensions - const tipWidth = Math.min(tooltipText.implicitWidth + (padding * 2), maxWidth) - root.implicitWidth = tipWidth - - const tipHeight = tooltipText.implicitHeight + (padding * 2) - root.implicitHeight = tipHeight - - // Reposition if necessary - var targetGlobal = targetItem.mapToItem(null, 0, 0) - const targetWidth = targetItem.width - - // Adjust horizontal position to keep tooltip on screen if needed - const globalX = targetGlobal.x + anchorX - if (globalX < 0) { - anchorX = -targetGlobal.x + margin - } else if (globalX + tipWidth > screenWidth) { - anchorX = screenWidth - targetGlobal.x - tipWidth - margin - } - - // Force anchor update - Qt.callLater(() => { - if (root.anchor && root.visible) { - root.anchor.updateAnchor() + // Update text function + function updateText(newText) { + if (visible && targetItem) { + text = newText + + // Recalculate dimensions + const tipWidth = Math.min(tooltipText.implicitWidth + (padding * 2), maxWidth) + root.implicitWidth = tipWidth + + const tipHeight = tooltipText.implicitHeight + (padding * 2) + root.implicitHeight = tipHeight + + // Reposition if necessary + var targetGlobal = targetItem.mapToItem(null, 0, 0) + const targetWidth = targetItem.width + + // Adjust horizontal position to keep tooltip on screen if needed + const globalX = targetGlobal.x + anchorX + if (globalX < 0) { + anchorX = -targetGlobal.x + margin + } else if (globalX + tipWidth > screenWidth) { + anchorX = screenWidth - targetGlobal.x - tipWidth - margin } - }) + + // Force anchor update + Qt.callLater(() => { + if (root.anchor && root.visible) { + root.anchor.updateAnchor() + } + }) + } } -} // Reset function to clean up state function reset() { From fae2535d00740e20ff14800b7ed980ec50d96af3 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 11:03:48 -0400 Subject: [PATCH 072/109] qmlfmt: detect array destructuring and skip file to avoid breakage. --- Bin/dev/qmlfmt.sh | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Bin/dev/qmlfmt.sh b/Bin/dev/qmlfmt.sh index 6f0fe315..8d09aa3d 100755 --- a/Bin/dev/qmlfmt.sh +++ b/Bin/dev/qmlfmt.sh @@ -10,10 +10,25 @@ command -v qmlfmt &>/dev/null || { echo "qmlfmt not found" >&2; exit 1; } format_file() { qmlfmt -e -b 360 -t 2 -i 2 -w "$1" || { echo "Failed: $1" >&2; return 1; }; } export -f format_file -mapfile -t files < <(find "${1:-.}" -name "*.qml" -type f) -[ ${#files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; } +# Find all .qml files +mapfile -t all_files < <(find "${1:-.}" -name "*.qml" -type f) +[ ${#all_files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; } -echo "Formatting ${#files[@]} files..." -printf '%s\0' "${files[@]}" | \ +echo "Scanning ${#all_files[@]} files for array destructuring..." +safe_files=() +for file in "${all_files[@]}"; do + # Checks for a comma inside brackets followed by an equals sign aka "array destructuring" + # as this ES6 syntax is not supported by qmlfmt and will result in breakage. + if grep -qE '\[.*,.*\]\s*=' "$file"; then + echo "-> Skipping (Array destructuring detected): $file" >&2 + else + safe_files+=("$file") + fi +done + +[ ${#safe_files[@]} -eq 0 ] && { echo "No safe files to format after filtering."; exit 0; } + +echo "Formatting ${#safe_files[@]} files..." +printf '%s\0' "${safe_files[@]}" | \ xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \ && echo "Done" || { echo "Errors occurred" >&2; exit 1; } \ No newline at end of file From bda54677e1724054c3676da8f049c61378790b3a Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 11:19:19 -0400 Subject: [PATCH 073/109] SystemStat: fixing wrong memGb calculation. Fix #507 --- Services/SystemStatService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/SystemStatService.qml b/Services/SystemStatService.qml index e69cd112..1e89d5c2 100644 --- a/Services/SystemStatService.qml +++ b/Services/SystemStatService.qml @@ -205,7 +205,7 @@ Singleton { if (memTotal > 0) { const usageKb = memTotal - memAvailable - root.memGb = (usageKb / 1000000).toFixed(1) + root.memGb = (usageKb / 1048576).toFixed(1) // 1024*1024 = 1048576 root.memPercent = Math.round((usageKb / memTotal) * 100) } } From 6af7753f50d947e02aa688b8ee605c53ef6047ec Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 11:43:38 -0400 Subject: [PATCH 074/109] v2.18.2 --- Services/UpdateService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 34b27996..632a7c32 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "2.18.1" - property bool isDevelopment: true + property string baseVersion: "2.18.2" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 03ec0e9b90b1e137387b944bbe1fa57beb0abeee Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 17 Oct 2025 11:44:06 -0400 Subject: [PATCH 075/109] dev version --- Services/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 632a7c32..41c5daf4 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "2.18.2" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 26803f9588a7f5210bab65d29c3fe4102119a645 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Fri, 17 Oct 2025 19:00:52 +0200 Subject: [PATCH 076/109] Add qmlls config to gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e69de29b..dfbea23a 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +.qmlls.ini From fffae8d4a3b6d3b98e7637ed5ec6c195c69739cf Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Fri, 17 Oct 2025 19:01:08 +0200 Subject: [PATCH 077/109] Add Zed config to gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dfbea23a..2d6d8923 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .qmlls.ini +.zed From f89bc3da905925485b7696f967df0c3db3174918 Mon Sep 17 00:00:00 2001 From: Tobias Pahl Date: Sat, 18 Oct 2025 10:35:31 +0200 Subject: [PATCH 078/109] fix translation --- Assets/Translations/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index aed4c10c..2de414dd 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -746,7 +746,7 @@ "label": "Widgets für Kurzbefehle" }, "sectionLeft": "Links", - "sectionRight": "Richtig" + "sectionRight": "Rechts" } }, "user-interface": { From 5fa1481780c77af3d57bdbc6778b745aacf3561a Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:01:27 +0200 Subject: [PATCH 079/109] README: add LionHeartP to donation list <3 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8c03cb67..a577af03 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ While all donations are greatly appreciated, they are completely voluntary. * Gohma * DiscoCevapi * PikaOS +* LionHeartP --- From 6ba3b465de493e7e255df5d986607f0eb4c2defe Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:08:51 +0200 Subject: [PATCH 080/109] SessionMenu: remove lock & suspend option, add toggle in general tab to decide if lockscreen is used or not --- Assets/Translations/en.json | 10 ++++++++++ Assets/settings-default.json | 3 ++- Commons/Settings.qml | 1 + Modules/SessionMenu/SessionMenu.qml | 16 +++++++--------- Modules/Settings/Tabs/GeneralTab.qml | 24 ++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index f18e6ac9..e8ee372b 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -32,6 +32,16 @@ "reset": "Reset screen corners radius" } }, + "lockscreen": { + "section": { + "label": "Lock screen", + "description": "Configure lock screen behavior." + }, + "lock-on-suspend": { + "label": "Lock on suspend", + "description": "Automatically lock the screen when suspending the system." + } + }, "fonts": { "reset-scaling": "Reset scaling", "section": { diff --git a/Assets/settings-default.json b/Assets/settings-default.json index ce680d75..27f2b735 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -65,7 +65,8 @@ "screenRadiusRatio": 1, "animationSpeed": 1, "animationDisabled": false, - "compactLockScreen": false + "compactLockScreen": false, + "lockOnSuspend": true }, "location": { "name": "Tokyo", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 1f925537..a0306c13 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -191,6 +191,7 @@ Singleton { property real animationSpeed: 1.0 property bool animationDisabled: false property bool compactLockScreen: false + property bool lockOnSuspend: true } // location diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index 83afaf5f..558452b7 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -14,7 +14,7 @@ NPanel { id: root preferredWidth: 320 * Style.uiScaleRatio - preferredHeight: 360 * Style.uiScaleRatio + preferredHeight: 280 * Style.uiScaleRatio panelAnchorHorizontalCenter: true panelAnchorVerticalCenter: true panelKeyboardFocus: true @@ -31,10 +31,6 @@ NPanel { "action": "lock", "icon": "lock", "title": I18n.tr("session-menu.lock") - }, { - "action": "lockAndSuspend", - "icon": "lock-pause", - "title": I18n.tr("session-menu.lock-and-suspend") }, { "action": "suspend", "icon": "suspend", @@ -96,11 +92,13 @@ NPanel { lockScreen.active = true } break - case "lockAndSuspend": - CompositorService.lockAndSuspend() - break case "suspend": - CompositorService.suspend() + // Check if we should lock before suspending + if (Settings.data.general.lockOnSuspend) { + CompositorService.lockAndSuspend() + } else { + CompositorService.suspend() + } break case "reboot": CompositorService.reboot() diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index c4554145..0a63a7fd 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -189,4 +189,28 @@ ColumnLayout { Layout.topMargin: Style.marginXL Layout.bottomMargin: Style.marginXL } + + // Lock Screen + ColumnLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NHeader { + label: I18n.tr("settings.general.lockscreen.section.label") + description: I18n.tr("settings.general.lockscreen.section.description") + } + + NToggle { + label: I18n.tr("settings.general.lockscreen.lock-on-suspend.label") + description: I18n.tr("settings.general.lockscreen.lock-on-suspend.description") + checked: Settings.data.general.lockOnSuspend + onToggled: Settings.data.general.lockOnSuspend = checked + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL + Layout.bottomMargin: Style.marginXL + } } From 7d15736e4e0e0933e8188e08f48edaeaabb9d6f3 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:10:46 +0200 Subject: [PATCH 081/109] i18n: add general tab lockscreen settings to all languages --- Assets/Translations/de.json | 10 ++++++++++ Assets/Translations/es.json | 10 ++++++++++ Assets/Translations/fr.json | 10 ++++++++++ Assets/Translations/pt.json | 10 ++++++++++ Assets/Translations/zh-CN.json | 10 ++++++++++ 5 files changed, 50 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 2de414dd..f53299c2 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -32,6 +32,16 @@ "reset": "Eckenradius des Bildschirms zurücksetzen" } }, + "lockscreen": { + "section": { + "label": "Sperrbildschirm", + "description": "Sperrbildschirm-Verhalten konfigurieren." + }, + "lock-on-suspend": { + "label": "Beim Standby sperren", + "description": "Bildschirm automatisch sperren, wenn das System in den Standby-Modus wechselt." + } + }, "fonts": { "section": { "label": "Schriftarten", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index a00881fe..77bd8612 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -32,6 +32,16 @@ "reset": "Restablecer el radio de las esquinas de la pantalla" } }, + "lockscreen": { + "section": { + "label": "Pantalla de bloqueo", + "description": "Configura el comportamiento de la pantalla de bloqueo." + }, + "lock-on-suspend": { + "label": "Bloquear al suspender", + "description": "Bloquear automáticamente la pantalla al suspender el sistema." + } + }, "fonts": { "section": { "label": "Fuentes", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index ec9af8ac..f180575a 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -32,6 +32,16 @@ "reset": "Réinitialiser le rayon des coins de l'écran" } }, + "lockscreen": { + "section": { + "label": "Écran de verrouillage", + "description": "Configurer le comportement de l'écran de verrouillage." + }, + "lock-on-suspend": { + "label": "Verrouiller lors de la suspension", + "description": "Verrouiller automatiquement l'écran lors de la mise en veille du système." + } + }, "fonts": { "section": { "label": "Polices", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 02979bf1..5e2a5658 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -32,6 +32,16 @@ "reset": "Redefinir raio dos cantos da tela" } }, + "lockscreen": { + "section": { + "label": "Tela de bloqueio", + "description": "Configure o comportamento da tela de bloqueio." + }, + "lock-on-suspend": { + "label": "Bloquear ao suspender", + "description": "Bloquear automaticamente a tela ao suspender o sistema." + } + }, "fonts": { "section": { "label": "Fontes", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index a251a5d4..889266c4 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -32,6 +32,16 @@ "reset": "重置屏幕圆角半径" } }, + "lockscreen": { + "section": { + "label": "锁屏", + "description": "配置锁屏行为。" + }, + "lock-on-suspend": { + "label": "挂起时锁定", + "description": "在系统挂起时自动锁定屏幕。" + } + }, "fonts": { "reset-scaling": "恢复默认缩放", "section": { From e426180f045e8237b1325ef1332eeb49e7cbe43d Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:13:17 +0200 Subject: [PATCH 082/109] Dock: add exclusiveZone: 0 to fix maximize issue (niri) --- Modules/Dock/Dock.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 56a932e5..391dd928 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -244,6 +244,7 @@ Variants { WlrLayershell.namespace: "noctalia-dock-main" WlrLayershell.exclusionMode: exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore + exclusiveZone: 0 // Size to fit the dock container exactly implicitWidth: dockContainerWrapper.width From 91bb3e866fb6977936121be6165c9b68c66a77f1 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 18 Oct 2025 08:29:26 -0400 Subject: [PATCH 083/109] Dock: attempt to fix visible 1px peek zone. --- Modules/Dock/Dock.qml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 391dd928..b90e9275 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -199,15 +199,9 @@ Variants { color: Color.transparent WlrLayershell.namespace: "noctalia-dock-peek" - WlrLayershell.exclusionMode: ExclusionMode.Auto // Always exclusive - + WlrLayershell.exclusionMode: ExclusionMode.ignore implicitHeight: peekHeight - Rectangle { - anchors.fill: parent - color: barAtBottom ? Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) : Color.transparent - } - MouseArea { id: peekArea anchors.fill: parent @@ -244,7 +238,6 @@ Variants { WlrLayershell.namespace: "noctalia-dock-main" WlrLayershell.exclusionMode: exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore - exclusiveZone: 0 // Size to fit the dock container exactly implicitWidth: dockContainerWrapper.width From 22e8358c6960bcf35edf4cef48120643b0c823fb Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 18 Oct 2025 08:41:36 -0400 Subject: [PATCH 084/109] Dock: fixed 1px height peek zone --- Modules/Dock/Dock.qml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index b90e9275..9bdf82cc 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -199,7 +199,7 @@ Variants { color: Color.transparent WlrLayershell.namespace: "noctalia-dock-peek" - WlrLayershell.exclusionMode: ExclusionMode.ignore + WlrLayershell.exclusionMode: ExclusionMode.Ignore implicitHeight: peekHeight MouseArea { @@ -255,12 +255,6 @@ Variants { } } - // Rectangle { - // anchors.fill: parent - // color: "#000FF0" - // z: -1 - // } - // Wrapper item for scale/opacity animations Item { id: dockContainerWrapper From fcb5510e94c56557bc585200d7c28cee06d05116 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 14:42:35 +0200 Subject: [PATCH 085/109] SessionMenu: better layout --- Modules/SessionMenu/SessionMenu.qml | 121 ++++++++++++++-------------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index 558452b7..b4a2bc15 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -13,8 +13,8 @@ import qs.Widgets NPanel { id: root - preferredWidth: 320 * Style.uiScaleRatio - preferredHeight: 280 * Style.uiScaleRatio + preferredWidth: 400 * Style.uiScaleRatio + preferredHeight: 340 * Style.uiScaleRatio panelAnchorHorizontalCenter: true panelAnchorVerticalCenter: true panelKeyboardFocus: true @@ -261,74 +261,77 @@ NPanel { root.activate() } - ColumnLayout { + NBox { anchors.fill: parent - anchors.topMargin: Style.marginM - anchors.leftMargin: Style.marginM - anchors.rightMargin: Style.marginM - anchors.bottomMargin: Style.marginS - spacing: Style.marginXS + anchors.margins: Style.marginL - // Header with title and close button - RowLayout { - Layout.fillWidth: true - Layout.preferredHeight: Style.baseWidgetSize * 0.6 + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginL - NText { - text: timerActive ? I18n.tr("session-menu.action-in-seconds", { - "action": pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1), - "seconds": Math.ceil(timeRemaining / 1000) - }) : I18n.tr("session-menu.title") - font.weight: Style.fontWeightBold - pointSize: Style.fontSizeM - color: timerActive ? Color.mPrimary : Color.mOnSurface - Layout.alignment: Qt.AlignVCenter - verticalAlignment: Text.AlignVCenter - } - - Item { + // Header with title and close button + RowLayout { Layout.fillWidth: true - } + Layout.preferredHeight: Style.baseWidgetSize * 0.6 - NIconButton { - icon: timerActive ? "stop" : "close" - tooltipText: timerActive ? I18n.tr("tooltips.cancel-timer") : I18n.tr("tooltips.close") - Layout.alignment: Qt.AlignVCenter - colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : Color.transparent - colorFg: timerActive ? Color.mError : Color.mOnSurface - onClicked: { - if (timerActive) { - cancelTimer() - } else { - cancelTimer() - root.close() + NText { + text: timerActive ? I18n.tr("session-menu.action-in-seconds", { + "action": pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1), + "seconds": Math.ceil(timeRemaining / 1000) + }) : I18n.tr("session-menu.title") + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeM + color: timerActive ? Color.mPrimary : Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + verticalAlignment: Text.AlignVCenter + } + + Item { + Layout.fillWidth: true + } + + NIconButton { + icon: timerActive ? "stop" : "close" + tooltipText: timerActive ? I18n.tr("tooltips.cancel-timer") : I18n.tr("tooltips.close") + Layout.alignment: Qt.AlignVCenter + baseSize: Style.baseWidgetSize * 0.7 + colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : Color.transparent + colorFg: timerActive ? Color.mError : Color.mOnSurface + onClicked: { + if (timerActive) { + cancelTimer() + } else { + cancelTimer() + root.close() + } } } } - } - NDivider { - Layout.fillWidth: true - } + NDivider { + Layout.fillWidth: true + } - // Power options - ColumnLayout { - Layout.fillWidth: true - spacing: Style.marginS + // Power options + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS - Repeater { - model: powerOptions - delegate: PowerButton { - Layout.fillWidth: true - icon: modelData.icon - title: modelData.title - isShutdown: modelData.isShutdown || false - isSelected: index === selectedIndex - onClicked: { - selectedIndex = index - startTimer(modelData.action) + Repeater { + model: powerOptions + delegate: PowerButton { + Layout.fillWidth: true + icon: modelData.icon + title: modelData.title + isShutdown: modelData.isShutdown || false + isSelected: index === selectedIndex + onClicked: { + selectedIndex = index + startTimer(modelData.action) + } + pending: timerActive && pendingAction === modelData.action } - pending: timerActive && pendingAction === modelData.action } } } @@ -347,7 +350,7 @@ NPanel { signal clicked - height: Style.baseWidgetSize * 1.2 * Style.uiScaleRatio + height: Style.baseWidgetSize * 1.3 * Style.uiScaleRatio radius: Style.radiusS color: { if (pending) { From 94f247eefc17e9ddd130951dd45f483d9081b6dc Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 18 Oct 2025 09:26:15 -0400 Subject: [PATCH 086/109] SetupWizard: wait for proper detection of the OS before opening the wizard. --- Services/DistroService.qml | 7 +++++-- shell.qml | 30 +++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Services/DistroService.qml b/Services/DistroService.qml index 6f5a8944..568bc2f0 100644 --- a/Services/DistroService.qml +++ b/Services/DistroService.qml @@ -12,6 +12,7 @@ Singleton { property string osPretty: "" property string osLogo: "" property bool isNixOS: false + property bool isReady: false // Internal helpers function buildCandidates(name) { @@ -79,8 +80,10 @@ Singleton { const osId = (val("ID") || "").toLowerCase() root.isNixOS = osId === "nixos" || (root.osPretty || "").toLowerCase().includes("nixos") const logoName = val("LOGO") - if (logoName) - resolveLogo(logoName) + if (logoName) { + resolveLogo(logoName) + } + root.isReady = true } catch (e) { Logger.w("DistroService", "failed to read os-release", e) } diff --git a/shell.qml b/shell.qml index 26022135..e3a41352 100644 --- a/shell.qml +++ b/shell.qml @@ -190,16 +190,28 @@ ShellRoot { function onSettingsLoaded() { // Only open the setup wizard for new users if (!Settings.data.setupCompleted) { - if (DistroService && DistroService.isNixOS) { - Settings.data.setupCompleted = true - return - } - if (Settings.data.settingsVersion >= Settings.settingsVersion) { - setupWizardLoader.active = true - } else { - Settings.data.setupCompleted = true - } + checkSetupWizard() } } } + + function checkSetupWizard() { + // Wait for distro service + if (!DistroService.isReady) { + Qt.callLater(checkSetupWizard) + return + } + + // No setup wizard on NixOS + if (DistroService.isNixOS) { + Settings.data.setupCompleted = true + return + } + + if (Settings.data.settingsVersion >= Settings.settingsVersion) { + setupWizardLoader.active = true + } else { + Settings.data.setupCompleted = true + } + } } From b7f96e3abd95a716f32433ab27bf4529de651db4 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 15:37:00 +0200 Subject: [PATCH 087/109] Matugen: user defined templates can now use predefined color schemes, changed path to config (~/.config/noctalia/user-templates.toml) --- 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/zh-CN.json | 2 +- Modules/Settings/Tabs/ColorSchemeTab.qml | 3 ++ Services/AppThemeService.qml | 41 +++++++++++++++- Services/MatugenTemplates.qml | 62 ++++++++++++++++++++++++ 9 files changed, 110 insertions(+), 8 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f53299c2..8f04dee0 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -559,7 +559,7 @@ "description": "Zusätzliche Konfigurationsoptionen.", "user-templates": { "label": "Benutzer-Vorlagen", - "description": "Benutzerdefinierte Matugen-Konfiguration aus ~/.config/matugen/config.toml aktivieren" + "description": "Benutzerdefinierte Matugen-Konfiguration aktivieren. Eine Vorlagendatei wird beim ersten Aktivieren unter ~/.config/noctalia/user-templates.toml erstellt" } }, "section": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index e8ee372b..3f3b9683 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -563,7 +563,7 @@ "description": "Additional configuration options.", "user-templates": { "label": "User templates", - "description": "Enable user-defined Matugen config from ~/.config/matugen/config.toml" + "description": "Enable user-defined Matugen config. A template file will be created at ~/.config/noctalia/user-templates.toml on first enable" } } } diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 77bd8612..3193fff1 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -559,7 +559,7 @@ "description": "Opciones de configuración adicionales.", "user-templates": { "label": "Plantillas de usuario", - "description": "Habilitar configuración de Matugen definida por el usuario desde ~/.config/matugen/config.toml" + "description": "Habilitar configuración de Matugen definida por el usuario. Se creará un archivo de plantilla en ~/.config/noctalia/user-templates.toml al activar por primera vez" } }, "section": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index f180575a..aee01c11 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -559,7 +559,7 @@ "description": "Options de configuration supplémentaires.", "user-templates": { "label": "Modèles utilisateur", - "description": "Activer la configuration Matugen définie par l'utilisateur depuis ~/.config/matugen/config.toml" + "description": "Activer la configuration Matugen définie par l'utilisateur. Un fichier modèle sera créé dans ~/.config/noctalia/user-templates.toml lors de la première activation" } }, "section": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 5e2a5658..0db4489b 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -521,7 +521,7 @@ "description": "Opções de configuração adicionais.", "user-templates": { "label": "Modelos do usuário", - "description": "Ativa a configuração do Matugen definida pelo usuário em ~/.config/matugen/config.toml" + "description": "Ativa a configuração do Matugen definida pelo usuário. Um arquivo de modelo será criado em ~/.config/noctalia/user-templates.toml na primeira ativação" } }, "section": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 889266c4..ddfd0059 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -559,7 +559,7 @@ "description": "其他配置选项。", "user-templates": { "label": "用户模板", - "description": "启用来自 ~/.config/matugen/config.toml 的用户定义 Matugen 配置" + "description": "启用用户定义的 Matugen 配置。首次启用时将在 ~/.config/noctalia/user-templates.toml 创建模板文件" } }, "section": { diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index ddf48db1..5909040f 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -590,6 +590,9 @@ ColumnLayout { checked: Settings.data.templates.enableUserTemplates onToggled: checked => { Settings.data.templates.enableUserTemplates = checked + if (checked) { + MatugenTemplates.writeUserTemplatesToml() + } AppThemeService.generate() } } diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index dd24327a..fc1f01d0 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -150,7 +150,10 @@ Singleton { const colors = schemeData[mode] const matugenColors = generatePalette(colors.mPrimary, colors.mSecondary, colors.mTertiary, colors.mError, colors.mSurface, isDarkMode) - const script = processAllTemplates(matugenColors, mode) + let script = processAllTemplates(matugenColors, mode) + + // Add user templates if enabled + script += buildUserTemplateCommandForPredefined(schemeData, mode) generateProcess.command = ["bash", "-lc", script] generateProcess.running = true @@ -335,8 +338,42 @@ Singleton { 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 matugenColors = generatePalette(colors.mPrimary, colors.mSecondary, colors.mTertiary, colors.mError, colors.mSurface, isDarkMode) + + // 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": matugenColors + }, 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 (Quickshell.env("HOME") + "/.config/matugen/config.toml").replace(/'/g, "'\\''") + return (Settings.configDir + "user-templates.toml").replace(/'/g, "'\\''") } // -------------------------------------------------------------------------------- diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index 68ceec44..633f7fea 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -2,6 +2,7 @@ pragma Singleton import QtQuick import Quickshell +import Quickshell.Io import qs.Commons // Central place to define which templates we generate and where they write. @@ -32,6 +33,51 @@ Singleton { return "" } + // Build user templates TOML for ~/.config/noctalia/user-templates.toml + function buildUserTemplatesToml() { + var lines = [] + lines.push("[config]") + 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("post_hook = \"echo 'User templates enabled - replace this placeholder with your own templates'\"") + 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.i("MatugenTemplates", "User templates config written to:", userConfigPath) + } + // -------------------------------- function addWallpaperBasedTemplates(lines, mode) { // Noctalia colors @@ -208,4 +254,20 @@ Singleton { } 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() + } + } + } } From e564ec2a7cf234c491eaa94adf1109ab2a012cf3 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 21:54:41 +0800 Subject: [PATCH 088/109] feat: Add adjustable dock size setting --- Assets/Translations/en.json | 6 +++++- Assets/settings-default.json | 3 ++- Commons/Settings.qml | 1 + Modules/Dock/Dock.qml | 2 +- Modules/Settings/Tabs/DockTab.qml | 18 ++++++++++++++++++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index e8ee372b..8fb27cb5 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -269,7 +269,11 @@ }, "floating-distance": { "label": "Dock floating distance", - "description": "Adjust the floating distance from the screen edge." + "description": "Set the distance between the dock and the edge of the screen." + }, + "icon-size": { + "label": "Dock size", + "description": "Adjust the overall size of the dock." }, "colorize-icons": { "label": "Colorize Icons", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 27f2b735..a62c77eb 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -172,7 +172,8 @@ "onlySameOutput": true, "monitors": [], "pinnedApps": [], - "colorizeIcons": false + "colorizeIcons": false, + "size": 1 }, "network": { "wifiEnabled": true diff --git a/Commons/Settings.qml b/Commons/Settings.qml index a0306c13..44f30145 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -293,6 +293,7 @@ Singleton { property string displayMode: "always_visible" // "always_visible", "auto_hide", "exclusive" property real backgroundOpacity: 1.0 property real floatingRatio: 1.0 + property real size: 1 property bool onlySameOutput: true property list monitors: [] // Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop") diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 9bdf82cc..8e60bbf3 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -63,7 +63,7 @@ Variants { readonly property int hideAnimationDuration: Style.animationFast readonly property int showAnimationDuration: Style.animationFast readonly property int peekHeight: 1 - readonly property int iconSize: 36 + readonly property int iconSize: Math.round(12 + 24 * (Settings.data.dock.size ?? 1)) readonly property int floatingMargin: Settings.data.dock.floatingRatio * Style.marginL // Bar detection and positioning properties diff --git a/Modules/Settings/Tabs/DockTab.qml b/Modules/Settings/Tabs/DockTab.qml index 3203446e..c7f1d258 100644 --- a/Modules/Settings/Tabs/DockTab.qml +++ b/Modules/Settings/Tabs/DockTab.qml @@ -87,6 +87,24 @@ ColumnLayout { } } + ColumnLayout { + spacing: Style.marginXXS + Layout.fillWidth: true + NLabel { + label: I18n.tr("settings.dock.appearance.icon-size.label") + description: I18n.tr("settings.dock.appearance.icon-size.description") + } + NValueSlider { + Layout.fillWidth: true + from: 0 + to: 2 + stepSize: 0.01 + value: Settings.data.dock.size + onMoved: value => Settings.data.dock.size = value + text: Math.floor(Settings.data.dock.size * 100) + "%" + } + } + NToggle { label: I18n.tr("settings.dock.monitors.only-same-output.label") description: I18n.tr("settings.dock.monitors.only-same-output.description") From ca9b21f695772813f545270ddc5d107813908cb9 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 21:59:46 +0800 Subject: [PATCH 089/109] feat(i18n): Add dock size translation for German --- Assets/Translations/de.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f53299c2..e291fcfd 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -271,6 +271,10 @@ "label": "Dock-Schwebeabstand", "description": "Schwebeabstand vom Bildschirmrand anpassen." }, + "icon-size": { + "label": "Dock-Größe", + "description": "Gesamtgröße des Docks anpassen." + }, "colorize-icons": { "label": "Symbole einfärben", "description": "Theme-Farben auf Dock-App-Symbole anwenden (nur nicht fokussierte Apps)." From b6fe65e57e750dfed3613c236b8cd2975b24e7a3 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:00:07 +0800 Subject: [PATCH 090/109] feat(i18n): Add dock size translation for Spanish --- Assets/Translations/es.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 77bd8612..3f33f17d 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -271,6 +271,10 @@ "label": "Distancia de flotación del dock", "description": "Ajusta la distancia de flotación desde el borde de la pantalla." }, + "icon-size": { + "label": "Tamaño del Dock", + "description": "Ajusta el tamaño general del Dock." + }, "colorize-icons": { "label": "Colorear iconos", "description": "Aplicar colores del tema a los iconos de aplicaciones del dock (solo aplicaciones no enfocadas)." From 5e67d33a641574eaf52a6e18942cfe7558b75447 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:00:30 +0800 Subject: [PATCH 091/109] feat(i18n): Add dock size translation for French --- Assets/Translations/fr.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index f180575a..21d73cd4 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -271,6 +271,10 @@ "label": "Distance de flottaison du dock", "description": "Ajustez la distance de flottaison par rapport au bord de l'écran." }, + "icon-size": { + "label": "Taille du Dock", + "description": "Ajuster la taille globale du Dock." + }, "colorize-icons": { "label": "Coloriser les icônes", "description": "Appliquer les couleurs du thème aux icônes d'applications du dock (applications non focalisées uniquement)." From 814165eb38de72cf86d0adddadab9c13357b64b5 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:01:07 +0800 Subject: [PATCH 092/109] feat(i18n): Add dock size translation for Portuguese --- Assets/Translations/pt.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 5e2a5658..d696c5ce 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -271,6 +271,10 @@ "label": "Distância de flutuação da dock", "description": "Ajuste a distância de flutuação da borda da tela." }, + "icon-size": { + "label": "Tamanho do Dock", + "description": "Ajustar o tamanho geral do Dock." + }, "colorize-icons": { "label": "Colorir ícones", "description": "Aplicar cores do tema aos ícones de aplicativos da dock (apenas aplicativos não focados)." From 3556d76dc591a3ff06dcb66257a2eb1fea6c5df4 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:01:48 +0800 Subject: [PATCH 093/109] feat(i18n): Add dock size translation for Simplified Chinese --- 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 889266c4..b8b4bf7e 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -271,6 +271,10 @@ "label": "Dock 浮动距离", "description": "调整距离屏幕边缘的浮动距离。" }, + "icon-size": { + "label": "Dock 大小", + "description": "调整 Dock 的整体大小。" + }, "colorize-icons": { "label": "着色图标", "description": "将主题颜色应用到 Dock 应用图标(仅限非聚焦应用)。" From e85f4894297ac328105d332a34e0b200ead7263c Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:49:36 +0800 Subject: [PATCH 094/109] feat: Implement language selection feature --- Assets/Translations/en.json | 12 ++++++++++- Assets/settings-default.json | 5 +++-- Commons/I18n.qml | 7 +++++++ Commons/Settings.qml | 1 + Modules/Settings/Tabs/GeneralTab.qml | 31 +++++++++++++++++++++++++++- 5 files changed, 52 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index e8ee372b..5c0b9e5d 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -68,6 +68,16 @@ "description": "Increase or decrease the size of the monospaced text." } } + }, + "language": { + "section": { + "label": "Language", + "description": "Choose your preferred language for the application." + }, + "select": { + "label": "Application Language", + "description": "Select the language used in the application's interface." + } } }, "audio": { @@ -1197,7 +1207,7 @@ "scan-again": "Scan again" } }, - + "tooltips": { "refresh": "Refresh", "close": "Close", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 27f2b735..850fc6f8 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -66,7 +66,8 @@ "animationSpeed": 1, "animationDisabled": false, "compactLockScreen": false, - "lockOnSuspend": true + "lockOnSuspend": true, + "language": "en" }, "location": { "name": "Tokyo", @@ -255,4 +256,4 @@ "battery": { "chargingMode": 0 } -} \ No newline at end of file +} diff --git a/Commons/I18n.qml b/Commons/I18n.qml index 6f49f097..fe2ab0a6 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -168,6 +168,13 @@ Singleton { } } + // Check for user-defined language setting + if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) { + Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`) + setLanguage(Settings.data.general.language) + return + } + // Detect user's favorite locale - languages for (var i = 0; i < Qt.locale().uiLanguages.length; i++) { const fullUserLang = Qt.locale().uiLanguages[i] diff --git a/Commons/Settings.qml b/Commons/Settings.qml index a0306c13..6799dc8e 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -192,6 +192,7 @@ Singleton { property bool animationDisabled: false property bool compactLockScreen: false property bool lockOnSuspend: true + property string language: "en" } // location diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 0a63a7fd..3bd5de54 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -190,7 +190,36 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL } - // Lock Screen + // Language selection + ColumnLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NHeader { + label: I18n.tr("settings.general.language.section.label") + description: I18n.tr("settings.general.language.section.description") + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("settings.general.language.select.label") + description: I18n.tr("settings.general.language.select.description") + model: I18n.availableLanguages.map(function(langCode) { + return { "key": langCode, "name": langCode } + }) + currentKey: Settings.data.general.language !== "" ? Settings.data.general.language : I18n.langCode + onSelected: key => { + Settings.data.general.language = key + I18n.setLanguage(key) + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL + Layout.bottomMargin: Style.marginXL + } ColumnLayout { spacing: Style.marginL Layout.fillWidth: true From 677cd373aca8979c5019ecd22835f231b49055dc Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:52:38 +0800 Subject: [PATCH 095/109] feat(i18n): Add language selection translation for German --- Assets/Translations/de.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f53299c2..5cffaab1 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -68,6 +68,16 @@ } }, "reset-scaling": "Skalierung zurücksetzen" + }, + "language": { + "section": { + "label": "Sprache", + "description": "Wählen Sie Ihre bevorzugte Sprache für die Anwendung." + }, + "select": { + "label": "Anwendungssprache", + "description": "Wählen Sie die in der Anwendungsoberfläche verwendete Sprache." + } } }, "audio": { From 3e7386b344ea66ff5bcb1b4b9c33bbc0de604f92 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:53:48 +0800 Subject: [PATCH 096/109] feat(i18n): Add language selection translation for Spanish --- Assets/Translations/es.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 77bd8612..7df19467 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -68,6 +68,16 @@ } }, "reset-scaling": "Restablecer la escala" + }, + "language": { + "section": { + "label": "Idioma", + "description": "Elige tu idioma preferido para la aplicación." + }, + "select": { + "label": "Idioma de la aplicación", + "description": "Selecciona el idioma utilizado en la interfaz de la aplicación." + } } }, "audio": { From a594a03e584da26a7b6c8a1b5213f12cf450a729 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:55:18 +0800 Subject: [PATCH 097/109] feat(i18n): Add language selection translation for French --- Assets/Translations/fr.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index f180575a..a4ab5fe1 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -68,6 +68,16 @@ } }, "reset-scaling": "Réinitialiser l'échelle" + }, + "language": { + "section": { + "label": "Langue", + "description": "Choisissez votre langue préférée pour l'application." + }, + "select": { + "label": "Langue de l'application", + "description": "Sélectionnez la langue utilisée dans l'interface de l'application." + } } }, "audio": { From 57fe2c1a21f8568f70535163528da13b0e379df5 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:56:17 +0800 Subject: [PATCH 098/109] feat(i18n): Add language selection translation for Portuguese --- Assets/Translations/pt.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 5e2a5658..16f24122 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -68,6 +68,16 @@ } }, "reset-scaling": "Redefinir escala" + }, + "language": { + "section": { + "label": "Idioma", + "description": "Escolha o seu idioma preferido para a aplicação." + }, + "select": { + "label": "Idioma da aplicação", + "description": "Selecione o idioma usado na interface da aplicação." + } } }, "audio": { From 5cffc848033dae2e7561ac93c3096b7c3036e829 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 18 Oct 2025 22:57:19 +0800 Subject: [PATCH 099/109] feat(i18n): Add language selection translation for Simplified Chinese --- Assets/Translations/zh-CN.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 889266c4..c9532210 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -68,6 +68,16 @@ "description": "增大或减小等宽文本的尺寸" } } + }, + "language": { + "section": { + "label": "语言", + "description": "选择您偏好的应用程序语言。" + }, + "select": { + "label": "应用程序语言", + "description": "选择应用程序界面中使用的语言。" + } } }, "audio": { From ec329f3a3a81be4526f1225ffd358824ad44360f Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 18 Oct 2025 12:01:23 -0400 Subject: [PATCH 100/109] MediaMini+ActiveWindo: fix warning when no screen after suspend --- Modules/Bar/Widgets/ActiveWindow.qml | 14 +++++++------- Modules/Bar/Widgets/MediaMini.qml | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 4fdf3d2b..2058bf1a 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -35,9 +35,9 @@ Item { readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : widgetMetadata.hideMode readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : (widgetMetadata.scrollingMode !== undefined ? widgetMetadata.scrollingMode : "hover") - + // Maximum widget width with user settings support - readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) + readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0) readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") @@ -66,22 +66,22 @@ Item { // Calculate the actual content width based on visible elements var contentWidth = 0 var margins = Style.marginS * scaling * 2 // Left and right margins - + // Icon width (if visible) if (showIcon) { contentWidth += 18 * scaling contentWidth += Style.marginS * scaling // Spacing after icon } - + // Text width (use the measured width) contentWidth += fullTitleMetrics.contentWidth - + // Additional small margin for text contentWidth += Style.marginXXS * 2 - + // Add container margins contentWidth += margins - + return Math.ceil(contentWidth) } diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 38a56a34..9831626e 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -39,7 +39,7 @@ Item { readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode // Maximum widget width with user settings support - readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen.width * 0.06) + readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0) readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth readonly property bool hasActivePlayer: MediaService.currentPlayer !== null @@ -85,7 +85,7 @@ Item { // Calculate the actual content width based on visible elements var contentWidth = 0 var margins = Style.marginS * scaling * 2 // Left and right margins - + // Icon or album art width if (!hasActivePlayer || !showAlbumArt) { // Icon width @@ -94,19 +94,19 @@ Item { // Album art width contentWidth += 21 * scaling } - + // Spacing between icon/art and text contentWidth += Style.marginS * scaling - + // Text width (use the measured width) contentWidth += fullTitleMetrics.contentWidth - + // Additional small margin for text contentWidth += Style.marginXXS * 2 - + // Add container margins contentWidth += margins - + return Math.ceil(contentWidth) } From 3249febb0f3c444b1253615be85ba92323791e23 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 19:04:20 +0200 Subject: [PATCH 101/109] Matugen: fix hex_stripped for predefined color schemes NSearchableComboBox: fix warning --- Services/AppThemeService.qml | 10 +++++++++- Widgets/NSearchableComboBox.qml | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index fc1f01d0..8b6a8b38 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -162,7 +162,8 @@ Singleton { function generatePalette(primaryColor, secondaryColor, tertiaryColor, errorColor, backgroundColor, outlineColor, isDarkMode) { const c = hex => ({ "default": { - "hex": hex + "hex": hex, + "hex_stripped": hex.replace(/^#/, "") } }) @@ -383,6 +384,13 @@ Singleton { id: generateProcess workingDirectory: Quickshell.shellDir running: false + stdout: StdioCollector { + onStreamFinished: { + if (this.text) { + Logger.i("AppThemeService", "GenerateProcess stdout:", this.text) + } + } + } stderr: StdioCollector { onStreamFinished: { if (this.text) { diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index ffb4f4cc..a9a578f4 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -233,7 +233,7 @@ RowLayout { Layout.alignment: Qt.AlignRight Repeater { - model: badgeLocations + model: typeof badgeLocations !== 'undefined' ? badgeLocations : [] delegate: NBox { width: Style.baseWidgetSize * 0.7 From 80443bb74ef40f00aefa340f81afeeab679495c1 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:25:03 +0800 Subject: [PATCH 102/109] fix(languge): language defaults to an empty string --- Assets/settings-default.json | 2 +- Commons/Settings.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 850fc6f8..04aaab90 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -67,7 +67,7 @@ "animationDisabled": false, "compactLockScreen": false, "lockOnSuspend": true, - "language": "en" + "language": "" }, "location": { "name": "Tokyo", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 6799dc8e..937ab041 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -192,7 +192,7 @@ Singleton { property bool animationDisabled: false property bool compactLockScreen: false property bool lockOnSuspend: true - property string language: "en" + property string language: "" } // location From 63bd97e76f952d302dd3b38e3acc06fd8d648ff1 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:29:39 +0800 Subject: [PATCH 103/109] feat(i18n): Implement and refine language selection feature Introduces a language selection option in settings, allowing users to manually choose an application language or revert to automatic system locale detection. This includes UI updates, settings integration, and improved language detection logic. --- Assets/Translations/en.json | 3 +- Commons/I18n.qml | 55 ++++++++++++---------------- Modules/Settings/Tabs/GeneralTab.qml | 14 +++++-- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 5c0b9e5d..783dc34b 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -76,7 +76,8 @@ }, "select": { "label": "Application Language", - "description": "Select the language used in the application's interface." + "description": "Select the language used in the application's interface.", + "auto-detect": "Automatic" } } }, diff --git a/Commons/I18n.qml b/Commons/I18n.qml index fe2ab0a6..4ec5e86f 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -7,10 +7,11 @@ import qs.Commons Singleton { id: root - property string debugForceLanguage: "" + property bool isLoaded: false property string langCode: "" + property string systemDetectedLangCode: "" property var availableLanguages: [] property var translations: ({}) property var fallbackTranslations: ({}) @@ -158,48 +159,40 @@ Singleton { return } - if (Settings.isDebug && debugForceLanguage !== "") { - Logger.d("I18n", `Debug mode: forcing language to "${debugForceLanguage}"`) - if (availableLanguages.includes(debugForceLanguage)) { - setLanguage(debugForceLanguage) - return - } else { - Logger.w("I18n", `Debug language "${debugForceLanguage}" not available in [${availableLanguages.join(', ')}]`) - } - } + var detectedLang = "" - // Check for user-defined language setting - if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) { - Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`) - setLanguage(Settings.data.general.language) - return - } - - // Detect user's favorite locale - languages + // First, determine the system's preferred language for (var i = 0; i < Qt.locale().uiLanguages.length; i++) { const fullUserLang = Qt.locale().uiLanguages[i] - // Try full code match (such as zh CN, en US) if (availableLanguages.includes(fullUserLang)) { - Logger.d("I18n", `Exact match found: "${fullUserLang}"`) - setLanguage(fullUserLang) - return + detectedLang = fullUserLang + break } - // If full code match fails, try short code matching (such as zh, en) const shortUserLang = fullUserLang.substring(0, 2) if (availableLanguages.includes(shortUserLang)) { - Logger.d("I18n", `Short code match found: "${shortUserLang}" from "${fullUserLang}"`) - setLanguage(shortUserLang) - return + detectedLang = shortUserLang + break } - - Logger.d("I18n", `No match for system language: "${fullUserLang}"`) } - // Fallback to first available language (preferably "en" if available) - const fallbackLang = availableLanguages.includes("en") ? "en" : availableLanguages[0] - setLanguage(fallbackLang) + // If no system language is found among available languages, fallback + if (detectedLang === "") { + detectedLang = availableLanguages.includes("en") ? "en" : availableLanguages[0] + } + + root.systemDetectedLangCode = detectedLang + Logger.d("I18n", `System detected language: "${root.systemDetectedLangCode}"`) + + // Now, apply the language: user-defined, then system-detected + if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) { + Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`) + setLanguage(Settings.data.general.language) + } else { + Logger.d("I18n", `No user-defined language, using system detected: "${root.systemDetectedLangCode}"`) + setLanguage(root.systemDetectedLangCode) + } } // ------------------------------------------- diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 3bd5de54..40b5276d 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -204,13 +204,19 @@ ColumnLayout { Layout.fillWidth: true label: I18n.tr("settings.general.language.select.label") description: I18n.tr("settings.general.language.select.description") - model: I18n.availableLanguages.map(function(langCode) { + model: [ + { "key": "", "name": I18n.tr("settings.general.language.select.auto-detect") + " (" + I18n.systemDetectedLangCode + ")" } + ].concat(I18n.availableLanguages.map(function(langCode) { return { "key": langCode, "name": langCode } - }) - currentKey: Settings.data.general.language !== "" ? Settings.data.general.language : I18n.langCode + })) + currentKey: Settings.data.general.language onSelected: key => { Settings.data.general.language = key - I18n.setLanguage(key) + if (key === "") { + I18n.detectLanguage() // Re-detect system language if "Automatic" is selected + } else { + I18n.setLanguage(key) // Set specific language + } } } } From 66de0222b098d41e51318cd7e3834a6087fa57cf Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:39:23 +0800 Subject: [PATCH 104/109] feat(i18n): Add German translation for language auto-detect --- Assets/Translations/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 5cffaab1..f5bc5c5c 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -76,7 +76,8 @@ }, "select": { "label": "Anwendungssprache", - "description": "Wählen Sie die in der Anwendungsoberfläche verwendete Sprache." + "description": "Wählen Sie die in der Anwendungsoberfläche verwendete Sprache.", + "auto-detect": "Automatisch" } } }, From e5b46c2e2b87844bffe81c3489445820a60b751f Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:40:59 +0800 Subject: [PATCH 105/109] feat(i18n): Add Spanish translation for language auto-detect --- Assets/Translations/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 7df19467..13f4a41e 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -76,7 +76,8 @@ }, "select": { "label": "Idioma de la aplicación", - "description": "Selecciona el idioma utilizado en la interfaz de la aplicación." + "description": "Selecciona el idioma utilizado en la interfaz de la aplicación.", + "auto-detect": "Automático" } } }, From f58391ce809b384301faaa28f736f523be433c47 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:42:03 +0800 Subject: [PATCH 106/109] feat(i18n): Add French translation for language auto-detect --- Assets/Translations/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index a4ab5fe1..53c45c13 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -76,7 +76,8 @@ }, "select": { "label": "Langue de l'application", - "description": "Sélectionnez la langue utilisée dans l'interface de l'application." + "description": "Sélectionnez la langue utilisée dans l'interface de l'application.", + "auto-detect": "Automatique" } } }, From 4474e7366b7278c140a10eef21fff39719435d62 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:43:18 +0800 Subject: [PATCH 107/109] feat(i18n): Add Portuguese translation for language auto-detect --- Assets/Translations/pt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 16f24122..70f6c643 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -76,7 +76,8 @@ }, "select": { "label": "Idioma da aplicação", - "description": "Selecione o idioma usado na interface da aplicação." + "description": "Selecione o idioma usado na interface da aplicação.", + "auto-detect": "Automático" } } }, From 00d8e18a45007f66e1a94aa214d0d33436e295d3 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 19 Oct 2025 01:45:58 +0800 Subject: [PATCH 108/109] feat(i18n): Add Simplified Chinese translation for language auto-detect --- Assets/Translations/zh-CN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index c9532210..29ec3c67 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -76,7 +76,8 @@ }, "select": { "label": "应用程序语言", - "description": "选择应用程序界面中使用的语言。" + "description": "选择应用程序界面中使用的语言。", + "auto-detect": "自动检测" } } }, From ba787cf3909377b0492f7e809a40c4a70f6c752a Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 18 Oct 2025 20:47:20 +0200 Subject: [PATCH 109/109] Reduce logging noise, fix small warning --- Services/AppThemeService.qml | 7 ++++--- Services/MatugenTemplates.qml | 9 ++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index 8b6a8b38..49d7bd60 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -387,14 +387,14 @@ Singleton { stdout: StdioCollector { onStreamFinished: { if (this.text) { - Logger.i("AppThemeService", "GenerateProcess stdout:", this.text) + Logger.d("AppThemeService", "GenerateProcess stdout:", this.text) } } } stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.w("AppThemeService", "GenerateProcess stderr:", this.text) + Logger.d("AppThemeService", "GenerateProcess stderr:", this.text) } } } @@ -402,11 +402,12 @@ Singleton { Process { id: copyProcess + workingDirectory: Quickshell.shellDir running: false stderr: StdioCollector { onStreamFinished: { if (this.text) { - Logger.w("AppThemeService", "CopyProcess stderr:", this.text) + Logger.d("AppThemeService", "CopyProcess stderr:", this.text) } } } diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index 633f7fea..3475e444 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -47,10 +47,9 @@ Singleton { 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("post_hook = \"echo 'User templates enabled - replace this placeholder with 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" @@ -75,7 +74,7 @@ Singleton { // Write the config file Quickshell.execDetached(["sh", "-c", `echo '${configContent.replace(/'/g, "'\\''")}' > '${userConfigPath}'`]) - Logger.i("MatugenTemplates", "User templates config written to:", userConfigPath) + Logger.d("MatugenTemplates", "User templates config written to:", userConfigPath) } // --------------------------------