diff --git a/.gitignore b/.gitignore
index e69de29b..2d6d8923 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.qmlls.ini
+.zed
diff --git a/Assets/MatugenTemplates/vicinae.toml b/Assets/MatugenTemplates/vicinae.toml
new file mode 100644
index 00000000..e4cc6da6
--- /dev/null
+++ b/Assets/MatugenTemplates/vicinae.toml
@@ -0,0 +1,140 @@
+# Vicinae Matugen Theme Template
+# Used LLM for initial generation, then modified to a satisfactory level
+
+[meta]
+name = "Matugen"
+description = "Material You theme generated by Matugen - {{mode}} variant"
+variant = "{{mode}}"
+icon = "noctalia.svg"
+
+# ============================================================================
+# Core Colors
+# ============================================================================
+# Foundation colors that define the entire theme's personality
+# Uses Material You's semantic color system for perfect harmony
+
+[colors.core]
+accent = "{{colors.primary.default.hex}}"
+accent_foreground = "{{colors.on_primary.default.hex}}"
+background = "{{colors.surface.default.hex}}"
+foreground = "{{colors.on_surface.default.hex}}"
+secondary_background = "{{colors.surface_container.default.hex}}"
+border = "{{colors.outline_variant.default.hex}}"
+
+# ============================================================================
+# Window-Specific Colors
+# ============================================================================
+# Different borders for visual hierarchy between window types
+
+[colors.main_window]
+border = "{{colors.outline_variant.default.hex}}"
+
+[colors.settings_window]
+border = "{{colors.outline.default.hex}}"
+
+# ============================================================================
+# Accent Colors
+# ============================================================================
+# Complete color palette using Material You's harmonious color generation
+# Maps to various UI elements throughout Vicinae
+
+[colors.accents]
+blue = "{{colors.primary.default.hex}}"
+green = "{{colors.tertiary.default.hex}}"
+magenta = "{{colors.secondary.default.hex}}"
+orange = "{{colors.error.default.hex}}"
+red = "{{colors.error.default.hex}}"
+yellow = "{{colors.tertiary.default.hex}}"
+cyan = "{{colors.primary.default.hex}}"
+purple = "{{colors.secondary.default.hex}}"
+
+# ============================================================================
+# Text Colors
+# ============================================================================
+# Semantic text colors for all states and contexts
+
+[colors.text]
+default = "{{colors.on_surface.default.hex}}"
+muted = "{{colors.on_surface_variant.default.hex}}"
+danger = "{{colors.error.default.hex}}"
+success = "{{colors.tertiary.default.hex}}"
+placeholder = "{{colors.on_surface_variant.default.hex}}"
+
+[colors.text.selection]
+background = "{{colors.primary.default.hex}}"
+foreground = "{{colors.on_primary.default.hex}}"
+
+[colors.text.links]
+default = "{{colors.primary.default.hex}}"
+visited = "{{colors.secondary.default.hex}}"
+
+# ============================================================================
+# Input Fields
+# ============================================================================
+# Text inputs, search boxes with proper focus states
+
+[colors.input]
+border = "{{colors.outline.default.hex}}"
+border_focus = "{{colors.primary.default.hex}}"
+border_error = "{{colors.error.default.hex}}"
+
+# ============================================================================
+# Buttons
+# ============================================================================
+# All button states with subtle hover effects and focus indicators
+
+[colors.button.primary]
+background = "{{colors.surface_container_high.default.hex}}"
+foreground = "{{colors.on_surface.default.hex}}"
+
+[colors.button.primary.hover]
+background = { name = "{{colors.surface_container_highest.default.hex}}", opacity = 0.85 }
+
+[colors.button.primary.focus]
+outline = "colors.core.accent"
+
+# ============================================================================
+# Lists
+# ============================================================================
+# Horizontal list views with elevation-based selection
+
+[colors.list.item.hover]
+background = { name = "{{colors.surface_container_high.default.hex}}", opacity = 0.7 }
+foreground = "{{colors.on_surface.default.hex}}"
+
+[colors.list.item.selection]
+background = { name = "{{colors.primary_container.default.hex}}", opacity = 0.5 }
+foreground = "{{colors.on_primary_container.default.hex}}"
+secondary_background = "{{colors.primary_container.default.hex}}"
+secondary_foreground = "{{colors.on_primary_container.default.hex}}"
+
+# ============================================================================
+# Grid Items
+# ============================================================================
+# Grid layouts (icon grids, tiles) with outline-based selection
+
+[colors.grid.item]
+background = "{{colors.surface_container.default.hex}}"
+
+[colors.grid.item.hover]
+outline = { name = "{{colors.primary.default.hex}}", opacity = 0.6 }
+
+[colors.grid.item.selection]
+outline = { name = "{{colors.primary.default.hex}}", opacity = 0.9 }
+
+# ============================================================================
+# Scrollbars
+# ============================================================================
+# Subtle scrollbar styling
+
+[colors.scrollbars]
+background = { name = "{{colors.on_surface.default.hex}}", opacity = 0.2 }
+
+# ============================================================================
+# Loading Indicators
+# ============================================================================
+# Progress bars and spinners for loading states
+
+[colors.loading]
+bar = "{{colors.primary.default.hex}}"
+spinner = "{{colors.primary.default.hex}}"
diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json
index 26bd0515..5c1dfeec 100644
--- a/Assets/Translations/de.json
+++ b/Assets/Translations/de.json
@@ -5,13 +5,13 @@
"profile": {
"section": {
"label": "Profil",
- "description": "Bearbeiten Sie Ihre Benutzerdaten und Ihren Avatar."
+ "description": "Hier kannst du deinen Avatar ändern."
},
"picture": {
- "label": "{user}s Profilbild",
- "description": "Ihr Profilbild, das in der gesamten Benutzeroberfläche angezeigt wird."
+ "label": "{user}s Avatar",
+ "description": "Der Avatar, der in der gesamten Benutzeroberfläche angezeigt wird."
},
- "select-avatar": "Avatar-Bild auswählen"
+ "select-avatar": "Avatar auswählen"
},
"screen-corners": {
"section": {
@@ -35,15 +35,15 @@
"fonts": {
"section": {
"label": "Schriftarten",
- "description": "Wählen Sie die in der Benutzeroberfläche verwendeten Schriftarten."
+ "description": "Wählen die Schriftart an die auf der Benutzeroberfläche angezgit wird."
},
"default": {
- "label": "Standard-Schriftart",
+ "label": "Standard Schriftart",
"description": "Hauptschriftart für die gesamte Benutzeroberfläche.",
"placeholder": "Standard-Schriftart auswählen...",
"search-placeholder": "Schriftarten suchen...",
"scale": {
- "description": "Vergrößern oder verkleinern Sie die Größe des Standardtextes.",
+ "description": "Hier kannst du die größe des Standardtextes ändern.",
"label": "Standardmäßige Schriftgröße"
}
},
@@ -53,12 +53,24 @@
"placeholder": "Monospace-Schriftart auswählen...",
"search-placeholder": "Monospace-Schriftarten suchen...",
"scale": {
- "description": "Die Größe des nichtproportionalen Textes vergrößern oder verkleinern.",
+ "description": "Hier kannst du die größe des Monotextes ändern",
"label": "Schriftgröße mit fester Breite"
}
},
"reset-scaling": "Skalierung zurücksetzen"
- }
+ },
+ "language": {
+ "section": {
+ "label": "Sprache",
+ "description": "Hier kannst du die Sprache von Noctalia ändern."
+ },
+ "select": {
+ "label": "Anwendungssprache",
+ "description": "Wählen die in der Anwendungsoberfläche verwendete Sprache.",
+ "auto-detect": "Automatisch"
+ }
+ },
+ "launch-setup-wizard": "Starte den Setup-Assistenten"
},
"audio": {
"title": "Audio",
@@ -222,13 +234,13 @@
"widgets": {
"section": {
"label": "Widget-Positionierung",
- "description": "Widgets per Drag & Drop neu anordnen. Abzeichen zeigen die Verwendung an: [L]inks, [M]itte, [R]echts."
+ "description": "Widgets per Drag & Drop neu anordnen. Symoble zeigen die Verwendung an: [L]inks, [M]itte, [R]echts."
}
},
"monitors": {
"section": {
"label": "Monitor-Anzeige",
- "description": "Statusleiste auf bestimmten Monitoren anzeigen. Standard ist alle, wenn keine ausgewählt sind."
+ "description": "Leiste auf bestimmten Monitoren anzeigen. Standardmäßig auf allen."
}
},
"tray": {
@@ -261,6 +273,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)."
@@ -294,7 +310,7 @@
},
"clipboard-history": {
"label": "Zwischenablage-Verlauf aktivieren",
- "description": "Zugriff auf zuvor kopierte Elemente über den Starter."
+ "description": "Zugriff auf zuvor kopierte Elemente über den Launcher."
},
"sort-by-usage": {
"label": "Nach Häufigkeit sortieren",
@@ -318,8 +334,8 @@
"description": "Erscheinungsbild und Verhalten von Benachrichtigungen konfigurieren."
},
"do-not-disturb": {
- "label": "Nicht stören",
- "description": "Alle Benachrichtigungs-Popups deaktivieren, wenn aktiviert."
+ "label": "Bitte Nicht stören",
+ "description": "Alle Benachrichtigungs-Popups deaktivieren."
},
"enable-osd": {
"label": "Bildschirmanzeige aktivieren",
@@ -359,35 +375,35 @@
"monitors": {
"section": {
"label": "Monitor-Anzeige",
- "description": "Benachrichtigungen auf bestimmten Monitoren anzeigen. Standard ist alle, wenn keine ausgewählt sind."
+ "description": "Benachrichtigungen auf bestimmten Monitoren anzeigen. Standardmäßig werden sie auf allen Monitoren angezeigt"
}
}
},
"osd": {
- "title": "Bildschirmanzeige",
+ "title": "On-Screen Display",
"description": "Bildschirm-Overlays wie Lautstärke- und Helligkeitsanzeigen konfigurieren.",
"section": {
"general": {
"label": "Allgemein",
- "description": "Sichtbarkeit und Verhalten der OSD konfigurieren."
+ "description": "Sichtbarkeit und Verhalten vom On-Screen Display konfigurieren."
}
},
"enabled": {
- "label": "Bildschirmanzeige aktivieren",
+ "label": "On-Screen Display aktivieren",
"description": "Lautstärke- und Helligkeitsänderungen in Echtzeit anzeigen."
},
"always-on-top": {
"label": "Immer im Vordergrund",
- "description": "Bildschirmanzeige über Vollbildfenstern und anderen Ebenen anzeigen."
+ "description": "On-Screen Display über Vollbildfenstern und anderen Ebenen anzeigen."
},
"location": {
"label": "Position",
- "description": "Wo Bildschirmanzeigen erscheinen."
+ "description": "Wo On-Screen Displays erscheinen."
},
"duration": {
"section": {
"label": "Automatisches Ausblenden",
- "description": "Wie lange die OSD sichtbar bleibt, bevor sie automatisch ausgeblendet wird."
+ "description": "Wie lange das On-Screen Display sichtbar bleibt, bevor es automatisch ausgeblendet wird."
},
"auto-hide": {
"label": "Ausblenden nach",
@@ -397,7 +413,7 @@
"monitors": {
"section": {
"label": "Monitor-Anzeige",
- "description": "OSD auf bestimmten Monitoren anzeigen. Standard ist alle, wenn keine ausgewählt sind."
+ "description": "On-Screen Display auf bestimmten Monitoren anzeigen. Standardmäßig auf allen angezeigt."
}
}
},
@@ -446,7 +462,7 @@
"description": "Dauer der Übergangsanimationen in Sekunden."
},
"edge-smoothness": {
- "label": "Übergangskante weichzeichnen",
+ "label": "Übergangseffect weichzeichnen",
"description": "Wendet einen weichen, gefiederten Effekt auf die Kante von Übergängen an."
}
},
@@ -475,10 +491,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 +500,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",
@@ -517,15 +542,15 @@
"description": "Terminal-Emulator-Theming.",
"kitty": {
"description": "Schreibt {filepath} und lädt neu",
- "description-missing": "Erfordert kitty Terminal"
+ "description-missing": "Erfordert {app} Terminal"
},
"ghostty": {
"description": "Schreibt {filepath} und lädt neu",
- "description-missing": "Erfordert ghostty Terminal"
+ "description-missing": "Erfordert {app} Terminal"
},
"foot": {
"description": "Schreibt {filepath} und lädt neu",
- "description-missing": "Erfordert foot Terminal"
+ "description-missing": "Erfordert {app} Terminal"
}
},
"programs": {
@@ -533,7 +558,11 @@
"description": "Anwendungsspezifisches Theming.",
"fuzzel": {
"description": "Schreibt {filepath} und lädt neu",
- "description-missing": "Erfordert fuzzel Starter"
+ "description-missing": "Erfordert die Installation von {app}"
+ },
+ "vicinae": {
+ "description": "Schreibt {filepath} und lädt neu",
+ "description-missing": "Erfordert die Installation von {app}"
},
"discord": {
"description": "Schreibt {filepath} für {client}",
@@ -541,7 +570,7 @@
},
"pywalfox": {
"description": "Schreibt {filepath} und führt pywalfox update aus",
- "description-missing": "Erfordert pywalfox Paket"
+ "description-missing": "Erfordert die Installation von {app} "
}
},
"misc": {
@@ -549,7 +578,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": {
@@ -567,7 +596,7 @@
},
"search": {
"label": "Nach einem Standort suchen",
- "description": "z.B. Berlin, Deutschland",
+ "description": "z.B. Dortmund, Deutschland",
"placeholder": "Standortnamen eingeben"
}
},
@@ -596,7 +625,7 @@
},
"week-numbers": {
"label": "Wochennummern anzeigen",
- "description": "Zeigt die Woche des Jahres (z.B. Woche 38) im Kalender an."
+ "description": "Zeigt die Kalender Wochen an (z.B. Woche 38)"
}
}
},
@@ -689,7 +718,7 @@
"description_plural": "Ein Dankeschön an unsere {count} großartigen Mitwirkenden!"
}
},
- "support": "Unterstützen Sie uns"
+ "support": "Unterstütz uns"
},
"hooks": {
"title": "Hooks",
@@ -746,7 +775,7 @@
"label": "Widgets für Kurzbefehle"
},
"sectionLeft": "Links",
- "sectionRight": "Richtig"
+ "sectionRight": "Rechts"
}
},
"user-interface": {
@@ -768,10 +797,6 @@
"label": "Eckenradius",
"reset": "Rahmenradius zurücksetzen"
},
- "compact-lockscreen": {
- "description": "Zeige nur die Login-Eingabe und Systemsteuerung, blende Wetter- und Medien-Widgets aus.",
- "label": "Kompakter Sperrbildschirm"
- },
"dim-desktop": {
"description": "Den Desktop abdunkeln, wenn Bedienfelder oder Menüs geöffnet sind.",
"label": "Dim Desktop"
@@ -790,6 +815,17 @@
"description": "Tooltips in der gesamten Benutzeroberfläche aktivieren oder deaktivieren.",
"label": "Tooltips anzeigen"
}
+ },
+ "lock-screen": {
+ "compact-lockscreen": {
+ "description": "Zeige nur die Login-Eingabe und Systemsteuerung, blende Wetter- und Medien-Widgets aus.",
+ "label": "Kompakter Sperrbildschirm"
+ },
+ "lock-on-suspend": {
+ "description": "Den Bildschirm beim Suspendieren des Systems automatisch sperren.",
+ "label": "Sperren beim Ruhezustand"
+ },
+ "title": "Sperrbildschirm"
}
},
"general": {
@@ -802,7 +838,7 @@
"mainly-clear": "Überwiegend klar",
"partly-cloudy": "Teilweise bewölkt",
"overcast": "Bedeckt",
- "fog": "Nebel",
+ "fog": "Nebelig",
"drizzle": "Nieselregen",
"snow": "Schnee",
"rain-showers": "Regenschauer",
@@ -818,7 +854,7 @@
"select-file": "Datei auswählen",
"cancel": "Abbrechen",
"search-placeholder": "Dateien und Ordner suchen...",
- "select-current": "Aktuelle auswählen",
+ "select-current": "Aktuelles Objekt auswählen",
"title": "Dateiauswahl"
},
"datetime-tokens": {
@@ -897,6 +933,9 @@
},
"cancel": "Abbrechen",
"apply": "Anwenden"
+ },
+ "text-input": {
+ "clear": "Löschen"
}
},
"bar": {
@@ -922,9 +961,13 @@
"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."
+ },
+ "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",
@@ -1075,6 +1118,14 @@
"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."
+ },
+ "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."
@@ -1219,6 +1270,8 @@
"tooltips": {
"refresh": "Aktualisieren",
"close": "Schließen",
+ "up": "Nach oben",
+ "home": "Home",
"refresh-wallpaper-list": "Hintergrundbild-Liste aktualisieren",
"refresh-devices": "Geräte aktualisieren",
"forget-network": "Netzwerk vergessen",
diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json
index 65e46e63..b4814d84 100644
--- a/Assets/Translations/en.json
+++ b/Assets/Translations/en.json
@@ -2,6 +2,7 @@
"settings": {
"general": {
"title": "General",
+ "launch-setup-wizard": "Launch the setup wizard",
"profile": {
"section": {
"label": "Profile",
@@ -58,6 +59,17 @@
"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.",
+ "auto-detect": "Automatic"
+ }
}
},
"audio": {
@@ -259,7 +271,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",
@@ -475,10 +491,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 +500,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",
@@ -539,6 +564,10 @@
"description": "Write {filepath} and reload",
"description-missing": "Requires {app} to be installed"
},
+ "vicinae": {
+ "description": "Write {filepath} and reload",
+ "description-missing": "Requires {app} to be installed"
+ },
"discord": {
"description": "Write {filepath} for {client}",
"description-missing": "No Discord client detected. Install vencord, vesktop, webcord, armcord, equibop, lightcord, or dorion."
@@ -553,7 +582,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"
}
}
}
@@ -768,10 +797,6 @@
"label": "Dim desktop",
"description": "Dim the desktop when panels or menus are open."
},
- "compact-lockscreen": {
- "label": "Compact lock screen",
- "description": "Show only the login input and system controls, hiding weather and media widgets."
- },
"border-radius": {
"label": "Border radius",
"description": "Controls the corner roundness of windows, buttons, and other elements.",
@@ -790,6 +815,17 @@
"label": "Open panels in overlay layer",
"description": "Panels will appear above fullscreen windows"
}
+ },
+ "lock-screen": {
+ "title": "Lock screen",
+ "compact-lockscreen": {
+ "label": "Compact lock screen",
+ "description": "Show only the login input and system controls, hiding weather and media widgets."
+ },
+ "lock-on-suspend": {
+ "label": "Lock on suspend",
+ "description": "Automatically lock the screen when suspending the system."
+ }
}
},
"widgets": {
@@ -880,6 +916,9 @@
},
"cancel": "Cancel",
"apply": "Apply"
+ },
+ "text-input": {
+ "clear": "Clear"
}
},
"bar": {
@@ -905,9 +944,13 @@
"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."
+ },
+ "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",
@@ -1058,6 +1101,14 @@
"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."
+ },
+ "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."
@@ -1191,10 +1242,11 @@
"scan-again": "Scan again"
}
},
-
"tooltips": {
"refresh": "Refresh",
"close": "Close",
+ "up": "Up",
+ "home": "Home",
"refresh-wallpaper-list": "Refresh wallpaper list",
"refresh-devices": "Refresh devices",
"forget-network": "Forget network",
diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json
index 8a663fd1..10bc18bc 100644
--- a/Assets/Translations/es.json
+++ b/Assets/Translations/es.json
@@ -58,7 +58,19 @@
}
},
"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.",
+ "auto-detect": "Automático"
+ }
+ },
+ "launch-setup-wizard": "Inicie el asistente de configuración"
},
"audio": {
"title": "Audio",
@@ -261,6 +273,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)."
@@ -475,10 +491,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 +500,19 @@
"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."
+ },
+ "mode": {
+ "description": "Permite el cambio automático entre el modo claro y el modo oscuro.",
+ "label": "Programación del modo oscuro",
+ "location": "Ubicación",
+ "manual": "Manual",
+ "off": "Apagado"
+ }
+ },
"predefined": {
"section": {
"label": "Esquemas de colores predefinidos",
@@ -535,6 +560,10 @@
"description": "Escribir {filepath} y recargar",
"description-missing": "Requiere que {app} esté instalado"
},
+ "vicinae": {
+ "description": "Escribir {filepath} y recargar",
+ "description-missing": "Requiere que {app} esté instalado"
+ },
"discord": {
"description": "Escribir {filepath} para {client}",
"description-missing": "No se detectó cliente de Discord. Instala vencord, vesktop, webcord, armcord, equibop, lightcord o dorion."
@@ -549,7 +578,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": {
@@ -768,10 +797,6 @@
"label": "Radio de borde",
"reset": "Restablecer el radio del borde"
},
- "compact-lockscreen": {
- "description": "Mostrar solo el campo de inicio de sesión y los controles del sistema, ocultando los widgets del clima y multimedia.",
- "label": "Pantalla de bloqueo compacta"
- },
"dim-desktop": {
"description": "Atenuar el escritorio cuando los paneles o menús estén abiertos.",
"label": "Dim escritorio"
@@ -790,6 +815,17 @@
"description": "Activar o desactivar los avisos emergentes en toda la interfaz.",
"label": "Mostrar sugerencias"
}
+ },
+ "lock-screen": {
+ "compact-lockscreen": {
+ "description": "Mostrar solo el campo de inicio de sesión y los controles del sistema, ocultando los widgets del clima y multimedia.",
+ "label": "Pantalla de bloqueo compacta"
+ },
+ "lock-on-suspend": {
+ "description": "Bloquear la pantalla automáticamente al suspender el sistema.",
+ "label": "Bloquear al suspender"
+ },
+ "title": "Pantalla de bloqueo"
}
},
"widgets": {
@@ -880,6 +916,9 @@
},
"cancel": "Cancelar",
"apply": "Aplicar"
+ },
+ "text-input": {
+ "clear": "Borrar"
}
},
"bar": {
@@ -905,9 +944,13 @@
"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."
+ },
+ "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",
@@ -1058,6 +1101,14 @@
"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."
+ },
+ "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."
@@ -1202,6 +1253,8 @@
"tooltips": {
"refresh": "Actualizar",
"close": "Cerrar",
+ "up": "Arriba",
+ "home": "Inicio",
"refresh-wallpaper-list": "Actualizar lista de fondos de pantalla",
"refresh-devices": "Actualizar dispositivos",
"forget-network": "Olvidar red",
diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json
index 190573f5..790220ed 100644
--- a/Assets/Translations/fr.json
+++ b/Assets/Translations/fr.json
@@ -58,7 +58,19 @@
}
},
"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.",
+ "auto-detect": "Automatique"
+ }
+ },
+ "launch-setup-wizard": "Lancer l'assistant d'installation"
},
"audio": {
"title": "Audio",
@@ -261,6 +273,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)."
@@ -475,10 +491,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 +500,19 @@
"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."
+ },
+ "mode": {
+ "description": "Active la commutation automatique entre le mode clair et le mode sombre.",
+ "label": "Programmation du mode sombre",
+ "location": "Emplacement",
+ "manual": "Manuel",
+ "off": "Éteint"
+ }
+ },
"predefined": {
"section": {
"label": "Jeux de couleurs prédéfinis",
@@ -535,6 +560,10 @@
"description": "Écrire ~/.config/fuzzel/themes/noctalia et recharger",
"description-missing": "Nécessite que le lanceur fuzzel soit installé"
},
+ "vicinae": {
+ "description": "Écrire {filepath} et recharger",
+ "description-missing": "Nécessite que le lanceur {app} soit installé"
+ },
"discord": {
"description": "Écrire {filepath} pour {client}",
"description-missing": "Aucun client Discord détecté. Installez vencord, vesktop, webcord, armcord, equibop, lightcord ou dorion."
@@ -549,7 +578,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": {
@@ -768,10 +797,6 @@
"label": "Rayon de bordure",
"reset": "Réinitialiser le rayon de la bordure"
},
- "compact-lockscreen": {
- "description": "Afficher uniquement le champ de saisie de connexion et les commandes système, en masquant les widgets météo et multimédia.",
- "label": "Écran de verrouillage compact"
- },
"dim-desktop": {
"description": "Atténuer le bureau lorsque des panneaux ou des menus sont ouverts.",
"label": "Dim bureau"
@@ -790,6 +815,17 @@
"description": "Activer ou désactiver les info-bulles dans toute l'interface.",
"label": "Afficher les infobulles"
}
+ },
+ "lock-screen": {
+ "compact-lockscreen": {
+ "description": "Afficher uniquement le champ de saisie de connexion et les commandes système, en masquant les widgets météo et multimédia.",
+ "label": "Écran de verrouillage compact"
+ },
+ "lock-on-suspend": {
+ "description": "Verrouiller automatiquement l'écran lors de la mise en veille du système.",
+ "label": "Verrouiller à la suspension"
+ },
+ "title": "Écran de verrouillage"
}
},
"widgets": {
@@ -880,6 +916,9 @@
},
"cancel": "Annuler",
"apply": "Appliquer"
+ },
+ "text-input": {
+ "clear": "Effacer"
}
},
"bar": {
@@ -905,9 +944,13 @@
"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."
+ },
+ "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",
@@ -1054,6 +1097,14 @@
"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."
+ },
+ "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."
@@ -1202,6 +1253,8 @@
"tooltips": {
"refresh": "Actualiser",
"close": "Fermer",
+ "up": "Remonter",
+ "home": "Accueil",
"refresh-wallpaper-list": "Actualiser la liste des fonds d'écran",
"refresh-devices": "Actualiser les appareils",
"forget-network": "Oublier le réseau",
diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json
index 69f96962..0be6fdf2 100644
--- a/Assets/Translations/pt.json
+++ b/Assets/Translations/pt.json
@@ -58,7 +58,19 @@
}
},
"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.",
+ "auto-detect": "Automático"
+ }
+ },
+ "launch-setup-wizard": "Iniciar o assistente de configuração"
},
"audio": {
"title": "Áudio",
@@ -261,6 +273,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)."
@@ -437,10 +453,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 +462,19 @@
"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."
+ },
+ "mode": {
+ "description": "Ativa a mudança automática entre o modo claro e o modo escuro.",
+ "label": "Agendamento do modo escuro",
+ "location": "Localização",
+ "manual": "Manual",
+ "off": "Desligado"
+ }
+ },
"predefined": {
"section": {
"label": "Esquemas de cores predefinidos",
@@ -497,6 +522,10 @@
"description": "Escrever {filepath} e recarregar",
"description-missing": "Requer que o {app} esteja instalado"
},
+ "vicinae": {
+ "description": "Escrever {filepath} e recarregar",
+ "description-missing": "Requer que o {app} esteja instalado"
+ },
"discord": {
"description": "Escrever {filepath} para {client}",
"description-missing": "Nenhum cliente Discord detectado. Instale vencord, vesktop, webcord, armcord, equibop, lightcord ou dorion."
@@ -511,7 +540,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": {
@@ -768,10 +797,6 @@
"label": "Raio da borda",
"reset": "Redefinir raio da borda"
},
- "compact-lockscreen": {
- "description": "Mostrar apenas a entrada de login e os controles do sistema, ocultando widgets de clima e mídia.",
- "label": "Tela de bloqueio compacta"
- },
"dim-desktop": {
"description": "Escurecer a área de trabalho quando painéis ou menus estiverem abertos.",
"label": "Dim área de trabalho"
@@ -790,6 +815,17 @@
"description": "Ativar ou desativar dicas de ferramentas em toda a interface.",
"label": "Mostrar dicas de ferramenta"
}
+ },
+ "lock-screen": {
+ "compact-lockscreen": {
+ "description": "Mostrar apenas a entrada de login e os controles do sistema, ocultando os widgets de clima e mídia.",
+ "label": "Tela de bloqueio compacta"
+ },
+ "lock-on-suspend": {
+ "description": "Bloquear a tela automaticamente ao suspender o sistema.",
+ "label": "Bloquear ao suspender"
+ },
+ "title": "Tela de bloqueio"
}
},
"widgets": {
@@ -880,6 +916,9 @@
},
"cancel": "Cancelar",
"apply": "Aplicar"
+ },
+ "text-input": {
+ "clear": "Limpar"
}
},
"bar": {
@@ -905,9 +944,13 @@
"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."
+ },
+ "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",
@@ -1058,6 +1101,14 @@
"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."
+ },
+ "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."
@@ -1202,6 +1253,8 @@
"tooltips": {
"refresh": "Atualizar",
"close": "Fechar",
+ "up": "Acima",
+ "home": "Início",
"refresh-wallpaper-list": "Atualizar lista de papéis de parede",
"refresh-devices": "Atualizar dispositivos",
"forget-network": "Esquecer rede",
diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json
index c6c52c06..90c4cecf 100644
--- a/Assets/Translations/zh-CN.json
+++ b/Assets/Translations/zh-CN.json
@@ -58,7 +58,19 @@
"description": "增大或减小等宽文本的尺寸"
}
}
- }
+ },
+ "language": {
+ "section": {
+ "label": "语言",
+ "description": "选择您偏好的应用程序语言。"
+ },
+ "select": {
+ "label": "应用程序语言",
+ "description": "选择应用程序界面中使用的语言。",
+ "auto-detect": "自动检测"
+ }
+ },
+ "launch-setup-wizard": "启动安装向导"
},
"audio": {
"title": "音频",
@@ -261,6 +273,10 @@
"label": "Dock 浮动距离",
"description": "调整距离屏幕边缘的浮动距离。"
},
+ "icon-size": {
+ "label": "Dock 大小",
+ "description": "调整 Dock 的整体大小。"
+ },
"colorize-icons": {
"label": "着色图标",
"description": "将主题颜色应用到 Dock 应用图标(仅限非聚焦应用)。"
@@ -475,10 +491,6 @@
"label": "颜色来源",
"description": "Noctalia 颜色的主要设置。"
},
- "dark-mode": {
- "label": "深色模式",
- "description": "切换到更暗的主题,便于夜间观看。"
- },
"use-wallpaper-colors": {
"label": "使用壁纸颜色",
"description": "使用 Matugen 从壁纸生成颜色方案。自动提取颜色以创建一致的主题。"
@@ -488,6 +500,19 @@
"description": "为 Matugen 选择配色方案生成算法。"
}
},
+ "dark-mode": {
+ "switch": {
+ "label": "深色模式",
+ "description": "切换到更暗的主题,便于夜间观看。"
+ },
+ "mode": {
+ "description": "启用自动切换浅色和深色模式。",
+ "label": "深色模式计划",
+ "location": "位置",
+ "manual": "手册",
+ "off": "关"
+ }
+ },
"predefined": {
"section": {
"label": "预定义配色方案",
@@ -535,6 +560,10 @@
"description": "写入 {filepath} 并重新加载",
"description-missing": "需要安装 {app}"
},
+ "vicinae": {
+ "description": "写入 {filepath} 并重新加载",
+ "description-missing": "需要安装 {app}"
+ },
"discord": {
"description": "为 {client} 写入 {filepath}",
"description-missing": "未检测到 Discord 客户端。请安装 vencord、vesktop、webcord、armcord、equibop、lightcord 或 dorion。"
@@ -549,7 +578,7 @@
"description": "其他配置选项。",
"user-templates": {
"label": "用户模板",
- "description": "启用来自 ~/.config/matugen/config.toml 的用户定义 Matugen 配置"
+ "description": "启用用户定义的 Matugen 配置。首次启用时将在 ~/.config/noctalia/user-templates.toml 创建模板文件"
}
},
"section": {
@@ -768,10 +797,6 @@
"label": "边框半径",
"reset": "重置边框半径"
},
- "compact-lockscreen": {
- "description": "仅显示登录输入和系统控制,隐藏天气和媒体小部件。",
- "label": "紧凑型锁屏"
- },
"dim-desktop": {
"description": "当面板或菜单打开时,桌面变暗。",
"label": "昏暗的桌面"
@@ -790,6 +815,17 @@
"description": "启用或禁用整个界面的工具提示。",
"label": "显示工具提示"
}
+ },
+ "lock-screen": {
+ "compact-lockscreen": {
+ "description": "仅显示登录输入和系统控制,隐藏天气和媒体小部件。",
+ "label": "紧凑型锁屏"
+ },
+ "lock-on-suspend": {
+ "description": "系统挂起时自动锁定屏幕。",
+ "label": "挂起时锁定"
+ },
+ "title": "锁屏"
}
},
"widgets": {
@@ -880,6 +916,9 @@
},
"cancel": "取消",
"apply": "应用"
+ },
+ "text-input": {
+ "clear": "清除"
}
},
"bar": {
@@ -905,9 +944,13 @@
"label": "滚动模式",
"description": "控制何时为长窗口标题启用文本滚动。"
},
- "width": {
- "description": "控制小部件的水平尺寸。",
- "label": "小部件宽度"
+ "max-width": {
+ "label": "最大宽度",
+ "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。"
+ },
+ "use-fixed-width": {
+ "label": "使用固定宽度",
+ "description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。"
},
"colorize-icons": {
"label": "着色图标",
@@ -1058,6 +1101,14 @@
"label": "可视化器类型",
"description": "选择要显示的音频可视化器样式。"
},
+ "max-width": {
+ "label": "最大宽度",
+ "description": "设置小部件的最大水平尺寸。当内容较短时,小部件会自动收缩以适应内容。"
+ },
+ "use-fixed-width": {
+ "label": "使用固定宽度",
+ "description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。"
+ },
"scrolling-mode": {
"label": "滚动模式",
"description": "控制何时为长曲目标题启用文本滚动。"
@@ -1202,6 +1253,8 @@
"tooltips": {
"refresh": "刷新",
"close": "关闭",
+ "up": "向上",
+ "home": "主目录",
"refresh-wallpaper-list": "刷新壁纸列表",
"refresh-devices": "刷新设备",
"forget-network": "忘记网络",
diff --git a/Assets/settings-default.json b/Assets/settings-default.json
index 2c1ca7bb..904e3961 100644
--- a/Assets/settings-default.json
+++ b/Assets/settings-default.json
@@ -65,7 +65,9 @@
"screenRadiusRatio": 1,
"animationSpeed": 1,
"animationDisabled": false,
- "compactLockScreen": false
+ "compactLockScreen": false,
+ "lockOnSuspend": true,
+ "language": ""
},
"location": {
"name": "Tokyo",
@@ -171,7 +173,8 @@
"onlySameOutput": true,
"monitors": [],
"pinnedApps": [],
- "colorizeIcons": false
+ "colorizeIcons": false,
+ "size": 1
},
"network": {
"wifiEnabled": true
@@ -255,4 +258,4 @@
"battery": {
"chargingMode": 0
}
-}
\ No newline at end of file
+}
diff --git a/Bin/colors-apply.sh b/Bin/colors-apply.sh
index 4c23afa1..00fe43b6 100755
--- a/Bin/colors-apply.sh
+++ b/Bin/colors-apply.sh
@@ -72,6 +72,13 @@ case "$APP_NAME" in
fi
;;
+ vicinae)
+ echo "🎨 Applying 'matugen' theme to vicinae..."
+
+ # Apply the theme
+ vicinae theme set matugen
+ ;;
+
pywalfox)
echo "🎨 Updating pywalfox themes..."
pywalfox update
@@ -84,4 +91,4 @@ case "$APP_NAME" in
;;
esac
-echo "✅ Command sent for $APP_NAME."
\ No newline at end of file
+echo "✅ Command sent for $APP_NAME."
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
diff --git a/Commons/I18n.qml b/Commons/I18n.qml
index 6f49f097..59eeb922 100644
--- a/Commons/I18n.qml
+++ b/Commons/I18n.qml
@@ -7,10 +7,10 @@ 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,41 +158,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 = ""
- // 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/Commons/Settings.qml b/Commons/Settings.qml
index 62695685..08005398 100644
--- a/Commons/Settings.qml
+++ b/Commons/Settings.qml
@@ -191,6 +191,8 @@ Singleton {
property real animationSpeed: 1.0
property bool animationDisabled: false
property bool compactLockScreen: false
+ property bool lockOnSuspend: true
+ property string language: ""
}
// location
@@ -292,6 +294,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")
@@ -355,6 +358,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
}
@@ -376,6 +382,7 @@ Singleton {
property bool discord_lightcord: false
property bool discord_dorion: false
property bool pywalfox: false
+ property bool vicinae: false
property bool enableUserTemplates: false
}
diff --git a/Commons/TablerIcons.qml b/Commons/TablerIcons.qml
index a840d72f..59b226d3 100644
--- a/Commons/TablerIcons.qml
+++ b/Commons/TablerIcons.qml
@@ -119,6 +119,7 @@ Singleton {
"settings-notifications": "bell",
"settings-osd": "picture-in-picture",
"settings-about": "info-square-rounded",
+ "settings-lock-screen": "lock",
"bluetooth": "bluetooth",
"bt-device-generic": "bluetooth",
"bt-device-headphones": "headphones",
diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml
index 145395d8..2058bf1a 100644
--- a/Modules/Bar/Widgets/ActiveWindow.qml
+++ b/Modules/Bar/Widgets/ActiveWindow.qml
@@ -35,7 +35,10 @@ 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 ? 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")
readonly property bool hasFocusedWindow: CompositorService.getFocusedWindow() !== null
@@ -43,7 +46,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 +62,43 @@ 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 using fixed width mode, always use maxWidth
+ if (useFixedWidth) {
+ return maxWidth
+ }
+ // Otherwise, adapt to content
+ 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 +157,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/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml
index 470bc37e..9831626e 100644
--- a/Modules/Bar/Widgets/MediaMini.qml
+++ b/Modules/Bar/Widgets/MediaMini.qml
@@ -38,8 +38,9 @@ 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 ? screen.width * 0.06 : 0)
+ 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")
@@ -60,7 +61,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 +81,49 @@ 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 using fixed width mode, always use maxWidth
+ if (useFixedWidth) {
+ return maxWidth
+ }
+ // Otherwise, adapt to content
+ 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 +139,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/Dock/Dock.qml b/Modules/Dock/Dock.qml
index 56a932e5..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
@@ -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
@@ -261,12 +255,6 @@ Variants {
}
}
- // Rectangle {
- // anchors.fill: parent
- // color: "#000FF0"
- // z: -1
- // }
-
// Wrapper item for scale/opacity animations
Item {
id: dockContainerWrapper
diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml
index 83afaf5f..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: 360 * Style.uiScaleRatio
+ preferredWidth: 400 * Style.uiScaleRatio
+ preferredHeight: 340 * 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()
@@ -263,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
}
}
}
@@ -349,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) {
diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml
index 643684dd..514733dd 100644
--- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml
@@ -17,7 +17,8 @@ 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 valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth
property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons
Component.onCompleted: {
@@ -31,7 +32,8 @@ 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.useFixedWidth = valueUseFixedWidth
settings.colorizeIcons = valueColorizeIcons
return settings
}
@@ -73,10 +75,18 @@ 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
+ }
+
+ 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 {
diff --git a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml
index 1d03c87f..5e109feb 100644
--- a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml
@@ -19,6 +19,8 @@ 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
+ property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth
Component.onCompleted: {
if (widgetData && widgetData.hideMode !== undefined) {
@@ -33,6 +35,8 @@ ColumnLayout {
settings.showVisualizer = valueShowVisualizer
settings.visualizerType = valueVisualizerType
settings.scrollingMode = valueScrollingMode
+ settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth
+ settings.useFixedWidth = valueUseFixedWidth
return settings
}
@@ -87,6 +91,22 @@ 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
+ }
+
+ 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/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml
index e5b64566..47ebc40a 100644
--- a/Modules/Settings/SettingsPanel.qml
+++ b/Modules/Settings/SettingsPanel.qml
@@ -26,6 +26,7 @@ NPanel {
Audio,
Bar,
ColorScheme,
+ LockScreen,
ControlCenter,
OSD,
Display,
@@ -118,6 +119,11 @@ NPanel {
id: userInterfaceTab
UserInterfaceTab {}
}
+ Component {
+ id: lockScreenTab
+ LockScreenTab {}
+ }
+
// Order *DOES* matter
function updateTabsModel() {
let newTabs = [{
@@ -150,6 +156,11 @@ NPanel {
"label": "settings.launcher.title",
"icon": "settings-launcher",
"source": launcherTab
+ }, {
+ "id": SettingsPanel.Tab.LockScreen,
+ "label": "settings.lock-screen.title",
+ "icon": "settings-lock-screen",
+ "source": lockScreenTab
}, {
"id": SettingsPanel.Tab.Audio,
"label": "settings.audio.title",
diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml
index ddf48db1..bbd02a8e 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
@@ -138,20 +156,91 @@ 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 => {
Settings.data.colorSchemes.darkMode = checked
root.cacheVersion++ // Force UI update for dark/light variants
}
}
+ NComboBox {
+ label: I18n.tr("settings.color-scheme.dark-mode.mode.label")
+ description: I18n.tr("settings.color-scheme.dark-mode.mode.description")
+
+ model: [{
+ "name": I18n.tr("settings.color-scheme.dark-mode.mode.off"),
+ "key": "off"
+ }, {
+ "name": I18n.tr("settings.color-scheme.dark-mode.mode.manual"),
+ "key": "manual"
+ }, {
+ "name": I18n.tr("settings.color-scheme.dark-mode.mode.location"),
+ "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")
description: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.description")
+ enabled: ProgramCheckerService.matugenAvailable
checked: Settings.data.colorSchemes.useWallpaperColors
onToggled: checked => {
if (checked) {
@@ -575,6 +664,23 @@ ColumnLayout {
}
}
}
+ NCheckbox {
+ label: "Vicinae"
+ description: ProgramCheckerService.vicinaeAvailable ? I18n.tr("settings.color-scheme.templates.programs.vicinae.description", {
+ "filepath": "~/.local/share/vicinae/themes/matugen.toml"
+ }) : I18n.tr("settings.color-scheme.templates.programs.vicinae.description-missing", {
+ "app": "vicinae"
+ })
+ checked: Settings.data.templates.vicinae
+ enabled: ProgramCheckerService.vicinaeAvailable
+ opacity: ProgramCheckerService.vicinaeAvailable ? 1.0 : 0.6
+ onToggled: checked => {
+ if (ProgramCheckerService.vicinaeAvailable) {
+ Settings.data.templates.vicinae = checked
+ AppThemeService.generate()
+ }
+ }
+ }
}
// Miscellaneous
@@ -590,6 +696,9 @@ ColumnLayout {
checked: Settings.data.templates.enableUserTemplates
onToggled: checked => {
Settings.data.templates.enableUserTemplates = checked
+ if (checked) {
+ MatugenTemplates.writeUserTemplatesToml()
+ }
AppThemeService.generate()
}
}
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")
diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml
index c4554145..d750f3c7 100644
--- a/Modules/Settings/Tabs/GeneralTab.qml
+++ b/Modules/Settings/Tabs/GeneralTab.qml
@@ -189,4 +189,54 @@ ColumnLayout {
Layout.topMargin: Style.marginXL
Layout.bottomMargin: Style.marginXL
}
+
+ // 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: [{
+ "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
+ onSelected: key => {
+ Settings.data.general.language = key
+ if (key === "") {
+ I18n.detectLanguage() // Re-detect system language if "Automatic" is selected
+ } else {
+ I18n.setLanguage(key) // Set specific language
+ }
+ }
+ }
+ }
+
+ NDivider {
+ Layout.fillWidth: true
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
+ }
+
+ NButton {
+ visible: !DistroService.isNixOS
+ text: I18n.tr("settings.general.launch-setup-wizard")
+ onClicked: {
+ setupWizardLoader.active = false
+ setupWizardLoader.active = true
+ }
+ }
}
diff --git a/Modules/Settings/Tabs/LockScreenTab.qml b/Modules/Settings/Tabs/LockScreenTab.qml
new file mode 100644
index 00000000..2b9d0ea7
--- /dev/null
+++ b/Modules/Settings/Tabs/LockScreenTab.qml
@@ -0,0 +1,31 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+ColumnLayout {
+ id: root
+
+ NToggle {
+ label: I18n.tr("settings.lock-screen.lock-on-suspend.label")
+ description: I18n.tr("settings.lock-screen.lock-on-suspend.description")
+ checked: Settings.data.general.lockOnSuspend
+ onToggled: Settings.data.general.lockOnSuspend = checked
+ }
+
+ NToggle {
+ label: I18n.tr("settings.lock-screen.compact-lockscreen.label")
+ description: I18n.tr("settings.lock-screen.compact-lockscreen.description")
+ checked: Settings.data.general.compactLockScreen
+ onToggled: checked => Settings.data.general.compactLockScreen = checked
+ }
+
+ NDivider {
+ Layout.fillWidth: true
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
+ }
+}
diff --git a/Modules/Settings/Tabs/UserInterfaceTab.qml b/Modules/Settings/Tabs/UserInterfaceTab.qml
index ec9a289d..e3190927 100644
--- a/Modules/Settings/Tabs/UserInterfaceTab.qml
+++ b/Modules/Settings/Tabs/UserInterfaceTab.qml
@@ -33,13 +33,6 @@ ColumnLayout {
onToggled: checked => Settings.data.ui.tooltipsEnabled = checked
}
- NToggle {
- label: I18n.tr("settings.user-interface.compact-lockscreen.label")
- description: I18n.tr("settings.user-interface.compact-lockscreen.description")
- checked: Settings.data.general.compactLockScreen
- onToggled: checked => Settings.data.general.compactLockScreen = checked
- }
-
NToggle {
label: I18n.tr("settings.user-interface.panels-overlay.label")
description: I18n.tr("settings.user-interface.panels-overlay.description")
diff --git a/Modules/SetupWizard/SetupAppearanceStep.qml b/Modules/SetupWizard/SetupAppearanceStep.qml
index e247e070..a59b2ad3 100644
--- a/Modules/SetupWizard/SetupAppearanceStep.qml
+++ b/Modules/SetupWizard/SetupAppearanceStep.qml
@@ -124,14 +124,14 @@ ColumnLayout {
spacing: 2
NText {
- text: I18n.tr("settings.color-scheme.color-source.dark-mode.label")
+ text: I18n.tr("settings.color-scheme.dark-mode.switch.label")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
- text: I18n.tr("settings.color-scheme.color-source.dark-mode.description")
+ text: I18n.tr("settings.color-scheme.dark-mode.switch.description")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
@@ -167,7 +167,7 @@ ColumnLayout {
color: Color.mSurface
NIcon {
- icon: "color-picker"
+ icon: ProgramCheckerService.matugenAvailable ? "color-picker" : "alert-triangle"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
@@ -196,7 +196,6 @@ ColumnLayout {
NToggle {
enabled: ProgramCheckerService.matugenAvailable
- opacity: ProgramCheckerService.matugenAvailable ? 1.0 : 0.6
checked: Settings.data.colorSchemes.useWallpaperColors && ProgramCheckerService.matugenAvailable
onToggled: checked => {
if (!ProgramCheckerService.matugenAvailable)
@@ -214,34 +213,6 @@ ColumnLayout {
}
}
- // 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
diff --git a/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml
index 43a6850c..00c139b6 100644
--- a/Modules/Tooltip/Tooltip.qml
+++ b/Modules/Tooltip/Tooltip.qml
@@ -321,11 +321,36 @@ PopupWindow {
completeHide()
}
- // Update text function for binding support
+ // Update text function
function updateText(newText) {
if (visible && targetItem) {
text = newText
- positionAndShow()
+
+ // 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()
+ }
+ })
}
}
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
---
diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml
index dd24327a..c54662ce 100644
--- a/Services/AppThemeService.qml
+++ b/Services/AppThemeService.qml
@@ -66,6 +66,13 @@ Singleton {
"outputs": [{
"path": "~/.config/vesktop/themes/noctalia.theme.css"
}]
+ },
+ "vicinae": {
+ "input": "vicinae.toml",
+ "outputs": [{
+ "path": "~/.local/share/vicinae/themes/matugen.toml"
+ }],
+ "postProcess": () => `cp -n ${Quickshell.shellDir}/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && ${colorsApplyScript} vicinae\n`
}
})
@@ -150,7 +157,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
@@ -159,7 +169,8 @@ Singleton {
function generatePalette(primaryColor, secondaryColor, tertiaryColor, errorColor, backgroundColor, outlineColor, isDarkMode) {
const c = hex => ({
"default": {
- "hex": hex
+ "hex": hex,
+ "hex_stripped": hex.replace(/^#/, "")
}
})
@@ -335,8 +346,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, "'\\''")
}
// --------------------------------------------------------------------------------
@@ -346,10 +391,17 @@ Singleton {
id: generateProcess
workingDirectory: Quickshell.shellDir
running: false
+ stdout: StdioCollector {
+ onStreamFinished: {
+ if (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)
}
}
}
@@ -357,11 +409,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/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml
index bd134591..a5823db6 100644
--- a/Services/BarWidgetRegistry.qml
+++ b/Services/BarWidgetRegistry.qml
@@ -44,7 +44,8 @@ Singleton {
"hideMode": "hidden",
"scrollingMode"// "visible", "hidden", "transparent"
: "hover",
- "width": 145,
+ "maxWidth": 145,
+ "useFixedWidth": false,
"colorizeIcons": false
},
"Battery": {
@@ -88,6 +89,8 @@ Singleton {
"hideMode": "hidden",
"scrollingMode"// "visible", "hidden", "transparent"
: "hover",
+ "maxWidth": 145,
+ "useFixedWidth": false,
"showAlbumArt": false,
"showVisualizer": false,
"visualizerType": "linear"
diff --git a/Services/DarkModeService.qml b/Services/DarkModeService.qml
new file mode 100644
index 00000000..ad3eef00
--- /dev/null
+++ b/Services/DarkModeService.qml
@@ -0,0 +1,169 @@
+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
+ enabled: Settings.data.colorSchemes.schedulingMode == "location"
+ function onWeatherChanged() {
+ if (LocationService.data.weather !== null) {
+ const changes = root.collectWeatherChanges(LocationService.data.weather)
+ if (!root.initComplete) {
+ root.initComplete = true
+ root.applyCurrentMode(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.collectWeatherChanges(LocationService.data.weather)
+ root.scheduleNextMode(changes)
+ }
+ }
+ }
+
+ function init() {
+ Logger.i("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]),
+ "darkMode": false
+ })
+ changes.push({
+ "time": Date.parse(weather.daily.sunset[i]),
+ "darkMode": true
+ })
+ }
+
+ return changes
+ }
+
+ function applyCurrentMode(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.d("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`)
+ }
+ }
+
+ function scheduleNextMode(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.d("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`)
+ }
+ }
+}
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/Services/LocationService.qml b/Services/LocationService.qml
index 0886e142..7c2df5d4 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()
@@ -190,7 +190,7 @@ Singleton {
// --------------------------------
function _fetchWeather(latitude, longitude, errorCallback) {
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 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/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml
index 75c83db4..19066213 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,50 @@ 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("")
+
+ return lines.join("\n") + "\n"
+ }
+
+ // Write user templates TOML to ~/.config/noctalia/user-templates.toml
+ function writeUserTemplatesToml() {
+ var userConfigPath = Settings.configDir + "user-templates.toml"
+
+ // Check if file already exists
+ fileCheckProcess.command = ["test", "-f", userConfigPath]
+ fileCheckProcess.running = true
+ }
+
+ function doWriteUserTemplatesToml() {
+ var userConfigPath = Settings.configDir + "user-templates.toml"
+ var configContent = buildUserTemplatesToml()
+
+ // Ensure directory exists (should already exist but just in case)
+ Quickshell.execDetached(["mkdir", "-p", Settings.configDir])
+
+ // Write the config file
+ Quickshell.execDetached(["sh", "-c", `echo '${configContent.replace(/'/g, "'\\''")}' > '${userConfigPath}'`])
+
+ Logger.d("MatugenTemplates", "User templates config written to:", userConfigPath)
+ }
+
// --------------------------------
function addWallpaperBasedTemplates(lines, mode) {
// Noctalia colors
@@ -106,6 +151,14 @@ Singleton {
}],
"input": "fuzzel.conf",
"postHook": AppThemeService.colorsApplyScript + " fuzzel"
+ }, {
+ "name": "vicinae",
+ "templates": [{
+ "version": "vicinae",
+ "output": "~/.local/share/vicinae/themes/matugen.toml"
+ }],
+ "input": "vicinae.toml",
+ "postHook": "cp -n " + Quickshell.shellDir + "/Assets/noctalia.svg ~/.local/share/vicinae/themes/noctalia.svg && " + AppThemeService.colorsApplyScript + " vicinae"
}, {
"name": "pywalfox",
"templates": [{
@@ -115,9 +168,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"
@@ -208,4 +261,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()
+ }
+ }
+ }
}
diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml
index 790fa71a..153b35c9 100644
--- a/Services/ProgramCheckerService.qml
+++ b/Services/ProgramCheckerService.qml
@@ -16,6 +16,7 @@ Singleton {
property bool ghosttyAvailable: false
property bool footAvailable: false
property bool fuzzelAvailable: false
+ property bool vicinaeAvailable: false
property bool gpuScreenRecorderAvailable: false
property bool wlsunsetAvailable: false
property bool app2unitAvailable: false
@@ -97,6 +98,7 @@ Singleton {
"ghosttyAvailable": ["which", "ghostty"],
"footAvailable": ["which", "foot"],
"fuzzelAvailable": ["which", "fuzzel"],
+ "vicinaeAvailable": ["which", "vicinae"],
"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"]
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)
}
}
diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml
index 34b27996..41c5daf4 100644
--- a/Services/UpdateService.qml
+++ b/Services/UpdateService.qml
@@ -8,7 +8,7 @@ Singleton {
id: root
// Public properties
- property string baseVersion: "2.18.1"
+ property string baseVersion: "2.18.2"
property bool isDevelopment: true
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`
diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml
index cc318f02..4b82e77d 100644
--- a/Widgets/NFilePicker.qml
+++ b/Widgets/NFilePicker.qml
@@ -296,6 +296,10 @@ Popup {
text: root.currentPath
placeholderText: "Enter path..."
Layout.fillWidth: true
+
+ visible: !filePickerPanel.showSearchBar
+ enabled: !filePickerPanel.showSearchBar
+
onEditingFinished: {
const newPath = text.trim()
if (newPath !== "" && newPath !== root.currentPath) {
@@ -314,6 +318,30 @@ Popup {
}
}
+ // Search bar
+ NTextInput {
+ id: searchInput
+ inputIconName: "search"
+ placeholderText: I18n.tr("placeholders.search")
+ Layout.fillWidth: true
+
+ visible: filePickerPanel.showSearchBar
+ enabled: filePickerPanel.showSearchBar
+
+ text: filePickerPanel.searchText
+ onTextChanged: {
+ filePickerPanel.searchText = text
+ filePickerPanel.filterText = text
+ root.updateFilteredModel()
+ }
+ Keys.onEscapePressed: {
+ filePickerPanel.showSearchBar = false
+ filePickerPanel.searchText = ""
+ filePickerPanel.filterText = ""
+ root.updateFilteredModel()
+ }
+ }
+
NIconButton {
icon: filePickerPanel.viewMode ? "filepicker-list" : "filepicker-layout-grid"
tooltipText: filePickerPanel.viewMode ? "List View" : "Grid View"
@@ -336,61 +364,6 @@ Popup {
}
}
- // Search bar
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 45
- color: Color.mSurfaceVariant
- radius: Style.radiusS
- border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS)
- visible: filePickerPanel.showSearchBar
-
- RowLayout {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- anchors.leftMargin: Style.marginS
- anchors.rightMargin: Style.marginS
- spacing: Style.marginS
-
- NIcon {
- icon: "filepicker-search"
- color: Color.mOnSurfaceVariant
- pointSize: Style.fontSizeS
- }
- NTextInput {
- id: searchInput
- placeholderText: I18n.tr("widget.file-picker.search-placeholder")
- Layout.fillWidth: true
- text: filePickerPanel.searchText
- onTextChanged: {
- filePickerPanel.searchText = text
- filePickerPanel.filterText = text
- root.updateFilteredModel()
- }
- Keys.onEscapePressed: {
- filePickerPanel.showSearchBar = false
- filePickerPanel.searchText = ""
- filePickerPanel.filterText = ""
- root.updateFilteredModel()
- }
- }
- NIconButton {
- icon: "filepicker-x"
- tooltipText: I18n.tr("tooltips.clear")
- baseSize: Style.baseWidgetSize * 0.6
- visible: filePickerPanel.searchText.length > 0
- onClicked: {
- searchInput.text = ""
- filePickerPanel.searchText = ""
- filePickerPanel.filterText = ""
- root.updateFilteredModel()
- }
- }
- }
- }
-
// File list area
Rectangle {
Layout.fillWidth: true
diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml
index ffb4f4cc..7d662278 100644
--- a/Widgets/NSearchableComboBox.qml
+++ b/Widgets/NSearchableComboBox.qml
@@ -173,6 +173,7 @@ RowLayout {
// Search input
NTextInput {
id: searchInput
+ inputIconName: "search"
Layout.fillWidth: true
placeholderText: root.searchPlaceholder
text: root.searchText
@@ -233,7 +234,7 @@ RowLayout {
Layout.alignment: Qt.AlignRight
Repeater {
- model: badgeLocations
+ model: typeof badgeLocations !== 'undefined' ? badgeLocations : []
delegate: NBox {
width: Style.baseWidgetSize * 0.7
diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml
index adb83cd3..f11f9ca4 100644
--- a/Widgets/NTextInput.qml
+++ b/Widgets/NTextInput.qml
@@ -9,6 +9,7 @@ ColumnLayout {
property string label: ""
property string description: ""
+ property string inputIconName: ""
property bool readOnly: false
property bool enabled: true
property color labelColor: Color.mOnSurface
@@ -106,74 +107,115 @@ ColumnLayout {
id: inputContainer
anchors.fill: parent
anchors.leftMargin: Style.marginM
- anchors.rightMargin: Style.marginM
+ // anchors.rightMargin: Style.marginM
+ clip: true
z: 1
- TextField {
- id: input
-
+ RowLayout {
anchors.fill: parent
- verticalAlignment: TextInput.AlignVCenter
+ spacing: 0
- echoMode: TextInput.Normal
- readOnly: root.readOnly
- enabled: root.enabled
- color: Color.mOnSurface
- placeholderTextColor: Qt.alpha(Color.mOnSurfaceVariant, 0.6)
+ NIcon {
+ id: inputIcon
+ icon: root.inputIconName
- selectByMouse: true
+ visible: root.inputIconName !== ""
+ enabled: false
- topPadding: 0
- bottomPadding: 0
- leftPadding: 0
- rightPadding: 0
+ Layout.alignment: Qt.AlignVCenter
+ Layout.rightMargin: visible ? Style.marginS : 0
+ }
- background: null
+ TextField {
+ id: input
- font.family: root.fontFamily
- font.pointSize: root.fontSize * Style.uiScaleRatio
- font.weight: root.fontWeight
+ Layout.fillWidth: true
+ Layout.fillHeight: true
- onEditingFinished: root.editingFinished()
+ verticalAlignment: TextInput.AlignVCenter
- // Override mouse handling to prevent propagation
- MouseArea {
- id: textFieldMouse
- anchors.fill: parent
- acceptedButtons: Qt.AllButtons
- preventStealing: true
- propagateComposedEvents: false
- cursorShape: Qt.IBeamCursor
+ echoMode: TextInput.Normal
+ readOnly: root.readOnly
+ enabled: root.enabled
+ color: Color.mOnSurface
+ placeholderTextColor: Qt.alpha(Color.mOnSurfaceVariant, 0.6)
- property int selectionStart: 0
+ selectByMouse: true
- onPressed: mouse => {
- mouse.accepted = true
- input.forceActiveFocus()
- var pos = input.positionAt(mouse.x, mouse.y)
- input.cursorPosition = pos
- selectionStart = pos
- }
+ topPadding: 0
+ bottomPadding: 0
+ leftPadding: 0
+ rightPadding: 0
- onPositionChanged: mouse => {
- if (mouse.buttons & Qt.LeftButton) {
- mouse.accepted = true
- var pos = input.positionAt(mouse.x, mouse.y)
- input.select(selectionStart, pos)
+ background: null
+
+ font.family: root.fontFamily
+ font.pointSize: root.fontSize * Style.uiScaleRatio
+ font.weight: root.fontWeight
+
+ onEditingFinished: root.editingFinished()
+
+ // Override mouse handling to prevent propagation
+ MouseArea {
+ id: textFieldMouse
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+ preventStealing: true
+ propagateComposedEvents: false
+ cursorShape: Qt.IBeamCursor
+
+ property int selectionStart: 0
+
+ onPressed: mouse => {
+ mouse.accepted = true
+ input.forceActiveFocus()
+ var pos = input.positionAt(mouse.x, mouse.y)
+ input.cursorPosition = pos
+ selectionStart = pos
+ }
+
+ onPositionChanged: mouse => {
+ if (mouse.buttons & Qt.LeftButton) {
+ mouse.accepted = true
+ var pos = input.positionAt(mouse.x, mouse.y)
+ input.select(selectionStart, pos)
+ }
}
+
+ onDoubleClicked: mouse => {
+ mouse.accepted = true
+ input.selectAll()
}
- onDoubleClicked: mouse => {
- mouse.accepted = true
- input.selectAll()
- }
+ onReleased: mouse => {
+ mouse.accepted = true
+ }
+ onWheel: wheel => {
+ wheel.accepted = true
+ }
+ }
+ }
+ NIconButton {
+ id: clearButton
+ icon: "x"
+ tooltipText: I18n.tr("widgets.text-input.clear")
- onReleased: mouse => {
- mouse.accepted = true
- }
- onWheel: wheel => {
- wheel.accepted = true
- }
+ Layout.alignment: Qt.AlignVCenter
+
+ border.width: 0
+
+ colorBg: Color.transparent
+ colorBgHover: Color.transparent
+ colorFg: Color.mOnSurface
+ colorFgHover: Color.mError
+
+ visible: input.text.length > 0 && !root.readOnly
+ enabled: input.text.length > 0 && !root.readOnly
+
+ onClicked: {
+ input.clear()
+ input.forceActiveFocus()
+ }
}
}
}
diff --git a/Widgets/NToggle.qml b/Widgets/NToggle.qml
index 18259ed6..45ad8bc3 100644
--- a/Widgets/NToggle.qml
+++ b/Widgets/NToggle.qml
@@ -9,6 +9,7 @@ RowLayout {
property string label: ""
property string description: ""
+ property bool enabled: true
property bool checked: false
property bool hovering: false
property int baseSize: Math.round(Style.baseWidgetSize * 0.8 * Style.uiScaleRatio)
@@ -18,6 +19,7 @@ RowLayout {
signal exited
Layout.fillWidth: true
+ opacity: enabled ? 1.0 : 0.6
NLabel {
label: root.label
@@ -71,14 +73,20 @@ RowLayout {
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: {
+ if (!enabled)
+ return
hovering = true
root.entered()
}
onExited: {
+ if (!enabled)
+ return
hovering = false
root.exited()
}
onClicked: {
+ if (!enabled)
+ return
root.toggled(!root.checked)
}
}
diff --git a/shell.qml b/shell.qml
index 26022135..16227fd1 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()
@@ -190,16 +191,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
+ }
+ }
}