diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8e9d993e4..9a39dfe3d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -25,6 +25,7 @@ Add screenshots if applicable. - Distro: [e.g., CachyOS, NixOS, Arch, ...] - Compositor: [ e.g., Hyprland, Niri, ...] - Noctalia-shell Version: [e.g., 1.0.0, available in About tab] +- Noctalia QS Version: [e.g., 0.0.4, please check your package manager] - Monitor resolution: [e.g., 1920x1080] ### Additional Context diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 86cc90af4..3f9bc1987 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,6 +2,8 @@ +If this PR is not ready for review yet, please mark it as **Draft** until it's good to be reviewed. + ## Motivation Provide a clear and concise explanation of what this PR does and why it is needed. diff --git a/Assets/ColorScheme/Ayu/Ayu.json b/Assets/ColorScheme/Ayu/Ayu.json index b0ac65f66..bf0803298 100644 --- a/Assets/ColorScheme/Ayu/Ayu.json +++ b/Assets/ColorScheme/Ayu/Ayu.json @@ -9,9 +9,9 @@ "mError": "#D95757", "mOnError": "#0B0E14", "mSurface": "#0B0E14", - "mOnSurface": "#BFBDB6", + "mOnSurface": "#D1D1C7", "mSurfaceVariant": "#1e222a", - "mOnSurfaceVariant": "#636A72", + "mOnSurfaceVariant": "#8E959E", "mOutline": "#565B66", "mShadow": "#000000", "mHover": "#39BAE6", @@ -37,7 +37,7 @@ "cyan": "#95e6cb", "white": "#ffffff" }, - "foreground": "#cccac2", + "foreground": "#D1D1C7", "background": "#1f2430", "selectionFg": "#1f2430", "selectionBg": "#409fff", @@ -55,9 +55,9 @@ "mError": "#E65050", "mOnError": "#F8F9FA", "mSurface": "#F8F9FA", - "mOnSurface": "#5C6166", + "mOnSurface": "#42474C", "mSurfaceVariant": "#E4E6E9", - "mOnSurfaceVariant": "#8A9199", + "mOnSurfaceVariant": "#6E757C", "mOutline": "#8A9199", "mShadow": "#F8F9FA", "mHover": "#55B4D4", @@ -83,7 +83,7 @@ "cyan": "#4cbf99", "white": "#d1d1d1" }, - "foreground": "#5c6166", + "foreground": "#42474C", "background": "#f8f9fa", "selectionFg": "#f8f9fa", "selectionBg": "#035bd6", diff --git a/Assets/Services/fastfetch/system-info.jsonc b/Assets/Services/fastfetch/system-info.jsonc index 8a44c43d0..d9f5a2df4 100644 --- a/Assets/Services/fastfetch/system-info.jsonc +++ b/Assets/Services/fastfetch/system-info.jsonc @@ -4,6 +4,7 @@ "os", "kernel", "title", + "board", "host", "uptime", "cpu", diff --git a/Assets/Templates/cava.ini b/Assets/Templates/cava.ini index 909c886f0..a036e2276 100644 --- a/Assets/Templates/cava.ini +++ b/Assets/Templates/cava.ini @@ -1,5 +1,4 @@ [color] -background = '{{colors.surface.default.hex}}' foreground = '{{colors.primary.default.hex}}' gradient = 1 diff --git a/Assets/Templates/emacs.el b/Assets/Templates/emacs.el index 2f86135e9..f4461fc51 100644 --- a/Assets/Templates/emacs.el +++ b/Assets/Templates/emacs.el @@ -148,13 +148,13 @@ ;; Org mode with hidden asterisks `(org-level-1 ((t (:foreground ,primary :weight bold :height 1.2)))) - `(org-level-2 ((t (:foreground ,primary-container :weight bold :height 1.1)))) - `(org-level-3 ((t (:foreground ,secondary :weight bold)))) - `(org-level-4 ((t (:foreground ,secondary-container :weight bold)))) - `(org-level-5 ((t (:foreground ,tertiary :weight bold)))) - `(org-level-6 ((t (:foreground ,tertiary-container :weight bold)))) - `(org-level-7 ((t (:foreground ,primary-fixed :weight bold)))) - `(org-level-8 ((t (:foreground ,primary-fixed-dim :weight bold)))) + `(org-level-2 ((t (:foreground ,secondary :weight bold :height 1.1)))) + `(org-level-3 ((t (:foreground ,tertiary :weight bold)))) + `(org-level-4 ((t (:foreground ,primary :weight bold)))) + `(org-level-5 ((t (:foreground ,secondary :weight bold)))) + `(org-level-6 ((t (:foreground ,tertiary :weight bold)))) + `(org-level-7 ((t (:foreground ,primary :weight bold)))) + `(org-level-8 ((t (:foreground ,secondary :weight bold)))) `(org-document-title ((t (:foreground ,primary :weight bold :height 1.3)))) `(org-document-info ((t (:foreground ,primary-container)))) `(org-todo ((t (:foreground ,err :weight bold)))) diff --git a/Assets/Templates/qtct.conf b/Assets/Templates/qtct.conf index 83c965d2f..b0f5f7d42 100644 --- a/Assets/Templates/qtct.conf +++ b/Assets/Templates/qtct.conf @@ -1,4 +1,8 @@ [ColorScheme] -active_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, #ffffff, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.secondary.default.hex}} -disabled_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, #ffffff, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.secondary.default.hex}} -inactive_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, #ffffff, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.secondary.default.hex}} +#https://doc.qt.io/archives/qt-5.15/qpalette.html +#https://doc.qt.io/qt-6/qpalette.html - Qt6 didn't add/delete/change any of the color functions or docs +# functions that correspond to the colors definition location +# windowText,button,light,midlight,dark,mid,text,brightText,buttonText,base,window,shadow,highlight,highlightedText,link,linkVisited,alternateBase,NO_IDEA (just use some sane default),toolTipBase,toolTipText,placeholderText,accent +active_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, #ffffff, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.primary.default.hex}} +disabled_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, #ffffff, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.primary.default.hex}} +inactive_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, #ffffff, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.primary.default.hex}} diff --git a/Assets/Templates/terminal/alacritty-predefined.toml b/Assets/Templates/terminal/alacritty-predefined.toml new file mode 100644 index 000000000..a3ba707e9 --- /dev/null +++ b/Assets/Templates/terminal/alacritty-predefined.toml @@ -0,0 +1,33 @@ +# Colors (Noctalia) + +[colors.bright] +black = '{{colors.terminal_bright_black.default.hex}}' +blue = '{{colors.terminal_bright_blue.default.hex}}' +cyan = '{{colors.terminal_bright_cyan.default.hex}}' +green = '{{colors.terminal_bright_green.default.hex}}' +magenta = '{{colors.terminal_bright_magenta.default.hex}}' +red = '{{colors.terminal_bright_red.default.hex}}' +white = '{{colors.terminal_bright_white.default.hex}}' +yellow = '{{colors.terminal_bright_yellow.default.hex}}' + +[colors.cursor] +cursor = '{{colors.terminal_cursor.default.hex}}' +text = '{{colors.terminal_cursor_text.default.hex}}' + +[colors.normal] +black = '{{colors.terminal_normal_black.default.hex}}' +blue = '{{colors.terminal_normal_blue.default.hex}}' +cyan = '{{colors.terminal_normal_cyan.default.hex}}' +green = '{{colors.terminal_normal_green.default.hex}}' +magenta = '{{colors.terminal_normal_magenta.default.hex}}' +red = '{{colors.terminal_normal_red.default.hex}}' +white = '{{colors.terminal_normal_white.default.hex}}' +yellow = '{{colors.terminal_normal_yellow.default.hex}}' + +[colors.primary] +background = '{{colors.terminal_background.default.hex}}' +foreground = '{{colors.terminal_foreground.default.hex}}' + +[colors.selection] +background = '{{colors.terminal_selection_bg.default.hex}}' +text = '{{colors.terminal_selection_fg.default.hex}}' diff --git a/Assets/Templates/terminal/foot b/Assets/Templates/terminal/foot index 042ded187..ee4d7b1c9 100644 --- a/Assets/Templates/terminal/foot +++ b/Assets/Templates/terminal/foot @@ -1,4 +1,4 @@ -[colors] +[colors-dark] background = {{colors.surface.default.hex_stripped}} foreground = {{colors.on_surface.default.hex_stripped}} diff --git a/Assets/Templates/terminal/foot-predefined b/Assets/Templates/terminal/foot-predefined new file mode 100644 index 000000000..ccee2e35a --- /dev/null +++ b/Assets/Templates/terminal/foot-predefined @@ -0,0 +1,22 @@ +[colors-dark] +foreground={{colors.terminal_foreground.default.hex_stripped}} +background={{colors.terminal_background.default.hex_stripped}} +regular0={{colors.terminal_normal_black.default.hex_stripped}} +regular1={{colors.terminal_normal_red.default.hex_stripped}} +regular2={{colors.terminal_normal_green.default.hex_stripped}} +regular3={{colors.terminal_normal_yellow.default.hex_stripped}} +regular4={{colors.terminal_normal_blue.default.hex_stripped}} +regular5={{colors.terminal_normal_magenta.default.hex_stripped}} +regular6={{colors.terminal_normal_cyan.default.hex_stripped}} +regular7={{colors.terminal_normal_white.default.hex_stripped}} +bright0={{colors.terminal_bright_black.default.hex_stripped}} +bright1={{colors.terminal_bright_red.default.hex_stripped}} +bright2={{colors.terminal_bright_green.default.hex_stripped}} +bright3={{colors.terminal_bright_yellow.default.hex_stripped}} +bright4={{colors.terminal_bright_blue.default.hex_stripped}} +bright5={{colors.terminal_bright_magenta.default.hex_stripped}} +bright6={{colors.terminal_bright_cyan.default.hex_stripped}} +bright7={{colors.terminal_bright_white.default.hex_stripped}} +selection-foreground={{colors.terminal_selection_fg.default.hex_stripped}} +selection-background={{colors.terminal_selection_bg.default.hex_stripped}} +cursor={{colors.terminal_cursor_text.default.hex_stripped}} {{colors.terminal_cursor.default.hex_stripped}} diff --git a/Assets/Templates/terminal/ghostty-predefined b/Assets/Templates/terminal/ghostty-predefined new file mode 100644 index 000000000..6f79e9b24 --- /dev/null +++ b/Assets/Templates/terminal/ghostty-predefined @@ -0,0 +1,22 @@ +palette = 0={{colors.terminal_normal_black.default.hex}} +palette = 1={{colors.terminal_normal_red.default.hex}} +palette = 2={{colors.terminal_normal_green.default.hex}} +palette = 3={{colors.terminal_normal_yellow.default.hex}} +palette = 4={{colors.terminal_normal_blue.default.hex}} +palette = 5={{colors.terminal_normal_magenta.default.hex}} +palette = 6={{colors.terminal_normal_cyan.default.hex}} +palette = 7={{colors.terminal_normal_white.default.hex}} +palette = 8={{colors.terminal_bright_black.default.hex}} +palette = 9={{colors.terminal_bright_red.default.hex}} +palette = 10={{colors.terminal_bright_green.default.hex}} +palette = 11={{colors.terminal_bright_yellow.default.hex}} +palette = 12={{colors.terminal_bright_blue.default.hex}} +palette = 13={{colors.terminal_bright_magenta.default.hex}} +palette = 14={{colors.terminal_bright_cyan.default.hex}} +palette = 15={{colors.terminal_bright_white.default.hex}} +background = {{colors.terminal_background.default.hex}} +foreground = {{colors.terminal_foreground.default.hex}} +cursor-color = {{colors.terminal_cursor.default.hex}} +cursor-text = {{colors.terminal_cursor_text.default.hex}} +selection-background = {{colors.terminal_selection_bg.default.hex}} +selection-foreground = {{colors.terminal_selection_fg.default.hex}} diff --git a/Assets/Templates/terminal/kitty-predefined.conf b/Assets/Templates/terminal/kitty-predefined.conf new file mode 100644 index 000000000..4380b0016 --- /dev/null +++ b/Assets/Templates/terminal/kitty-predefined.conf @@ -0,0 +1,24 @@ +color0 {{colors.terminal_normal_black.default.hex}} +color1 {{colors.terminal_normal_red.default.hex}} +color2 {{colors.terminal_normal_green.default.hex}} +color3 {{colors.terminal_normal_yellow.default.hex}} +color4 {{colors.terminal_normal_blue.default.hex}} +color5 {{colors.terminal_normal_magenta.default.hex}} +color6 {{colors.terminal_normal_cyan.default.hex}} +color7 {{colors.terminal_normal_white.default.hex}} +color8 {{colors.terminal_bright_black.default.hex}} +color9 {{colors.terminal_bright_red.default.hex}} +color10 {{colors.terminal_bright_green.default.hex}} +color11 {{colors.terminal_bright_yellow.default.hex}} +color12 {{colors.terminal_bright_blue.default.hex}} +color13 {{colors.terminal_bright_magenta.default.hex}} +color14 {{colors.terminal_bright_cyan.default.hex}} +color15 {{colors.terminal_bright_white.default.hex}} +background {{colors.terminal_background.default.hex}} +selection_foreground {{colors.terminal_cursor_text.default.hex}} +cursor {{colors.terminal_cursor.default.hex}} +cursor_text_color {{colors.terminal_cursor_text.default.hex}} +foreground {{colors.terminal_foreground.default.hex}} +selection_background {{colors.terminal_foreground.default.hex}} +active_border_color {{colors.primary.default.hex}} +inactive_border_color {{colors.secondary.default.hex}} diff --git a/Assets/Templates/terminal/wezterm-predefined.toml b/Assets/Templates/terminal/wezterm-predefined.toml new file mode 100644 index 000000000..da4c2262e --- /dev/null +++ b/Assets/Templates/terminal/wezterm-predefined.toml @@ -0,0 +1,84 @@ +[colors] +ansi = [ + "{{colors.terminal_normal_black.default.hex}}", + "{{colors.terminal_normal_red.default.hex}}", + "{{colors.terminal_normal_green.default.hex}}", + "{{colors.terminal_normal_yellow.default.hex}}", + "{{colors.terminal_normal_blue.default.hex}}", + "{{colors.terminal_normal_magenta.default.hex}}", + "{{colors.terminal_normal_cyan.default.hex}}", + "{{colors.terminal_normal_white.default.hex}}", +] +background = "{{colors.terminal_background.default.hex}}" +brights = [ + "{{colors.terminal_bright_black.default.hex}}", + "{{colors.terminal_bright_red.default.hex}}", + "{{colors.terminal_bright_green.default.hex}}", + "{{colors.terminal_bright_yellow.default.hex}}", + "{{colors.terminal_bright_blue.default.hex}}", + "{{colors.terminal_bright_magenta.default.hex}}", + "{{colors.terminal_bright_cyan.default.hex}}", + "{{colors.terminal_bright_white.default.hex}}", +] +compose_cursor = "{{colors.terminal_cursor.default.hex}}" +cursor_bg = "{{colors.terminal_cursor.default.hex}}" +cursor_border = "{{colors.terminal_cursor.default.hex}}" +cursor_fg = "{{colors.terminal_cursor_text.default.hex}}" +foreground = "{{colors.terminal_foreground.default.hex}}" +scrollbar_thumb = "{{colors.terminal_selection_bg.default.hex}}" +selection_bg = "{{colors.terminal_selection_bg.default.hex}}" +selection_fg = "{{colors.terminal_selection_fg.default.hex}}" +split = "{{colors.terminal_bright_black.default.hex}}" +visual_bell = "{{colors.terminal_normal_black.default.hex}}" + +[colors.indexed] +16 = "{{colors.secondary.default.hex}}" +17 = "{{colors.terminal_cursor.default.hex}}" + +[colors.tab_bar] +background = "{{colors.terminal_background.default.hex | darken 0.1}}" +inactive_tab_edge = "{{colors.terminal_selection_bg.default.hex}}" + +[colors.tab_bar.active_tab] +bg_color = "{{colors.primary.default.hex}}" +fg_color = "{{colors.on_primary.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[colors.tab_bar.inactive_tab] +bg_color = "{{colors.terminal_background.default.hex | darken 0.05}}" +fg_color = "{{colors.terminal_foreground.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[colors.tab_bar.inactive_tab_hover] +bg_color = "{{colors.terminal_background.default.hex}}" +fg_color = "{{colors.terminal_foreground.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[colors.tab_bar.new_tab] +bg_color = "{{colors.terminal_selection_bg.default.hex}}" +fg_color = "{{colors.terminal_foreground.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[colors.tab_bar.new_tab_hover] +bg_color = "{{colors.terminal_bright_black.default.hex}}" +fg_color = "{{colors.terminal_foreground.default.hex}}" +intensity = "Normal" +italic = false +strikethrough = false +underline = "None" + +[metadata] +author = "Noctalia" +name = "Noctalia" diff --git a/Assets/Templates/yazi.toml b/Assets/Templates/yazi.toml index 598799a32..3fc26cf86 100644 --- a/Assets/Templates/yazi.toml +++ b/Assets/Templates/yazi.toml @@ -76,9 +76,9 @@ selected = { reversed = true } # : Tabs [[[ [tabs] -active = { fg = "{{colors.primary.default.hex}}", bold = true, bg = "{{colors.surface.default.hex}}" } +active = { fg = "{{colors.surface.default.hex}}", bold = true, bg = "{{colors.primary.default.hex}}" } inactive = { fg = "{{colors.secondary.default.hex}}", bg = "{{colors.surface.default.hex}}" } -sep_inner = { open = "[", close = "]" } +sep_inner = { open = "", close = "" } # : ]]] @@ -914,4 +914,4 @@ conds = [ { if = "!dir", text = "", fg = "{{colors.primary.default.hex}}" }, ] -# : }}} \ No newline at end of file +# : }}} diff --git a/Assets/Templates/zen-browser/zen-userContent.css b/Assets/Templates/zen-browser/zen-userContent.css index e845f911a..dc75d177c 100644 --- a/Assets/Templates/zen-browser/zen-userContent.css +++ b/Assets/Templates/zen-browser/zen-userContent.css @@ -44,10 +44,6 @@ --zen-primary-color: {{colors.primary.default.hex}} !important; } - groupbox , moz-card{ - background: {{colors.surface_container.default.hex}} !important; - } - button, groupbox menulist { background: {{colors.surface_container_high.default.hex}} !important; diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 80607945f..5e5d0f38c 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Wählen Sie die Farbe für den Visualisierer aus.", "color-name-label": "Füllfarbe", + "height-description": "Benutzerdefinierte Komponentenbreite.", "hide-when-idle-description": "Wenn aktiviert, wird der Visualizer ausgeblendet, sofern keine Wiedergabe läuft.", "hide-when-idle-label": "Ausblenden, wenn keine Medien wiedergegeben werden", "width-description": "Benutzerdefinierte Komponentenbreite." @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Statistiken als Mini-Balkendiagramme anstelle von Textwerten anzeigen. Verhindert Layoutverschiebungen.", "compact-mode-label": "Kompaktmodus", + "cpu-cores-description": "Die Nutzung der CPU-Kerne einzeln anzeigen.", + "cpu-cores-label": "CPU-Kerne", "cpu-frequency-description": "Die aktuelle CPU-Taktrate in GHz anzeigen.", "cpu-frequency-label": "CPU-Frequenz anzeigen", "cpu-temperature-description": "CPU-Temperaturwerte anzeigen, falls verfügbar.", @@ -291,6 +294,8 @@ "focused-color-label": "Farbe der fokussierten Arbeitsfläche", "follow-focused-screen-description": "Zeige Arbeitsflächen vom aktuell fokussierten Bildschirm an, statt vom Bildschirm, auf dem sich die Leiste befindet.", "follow-focused-screen-label": "Fokussiertem Bildschirm folgen", + "font-weight-description": "Visuelles Gewicht für Text im Arbeitsbereich festlegen.", + "font-weight-label": "Schriftstärke", "grouped-border-opacity-description": "Legen Sie die Deckkraft für Arbeitsflächen-Container-Rahmen fest.", "grouped-border-opacity-label": "Rahmen-Deckkraft", "hide-unoccupied-description": "Arbeitsflächen ohne Fenster nicht anzeigen.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Alle Akkus (kombiniert)", "battery-health": "Akkuzustand", "battery-level": "Ladezustand", "capacity-level": "Kapazität: {level}", @@ -374,6 +380,7 @@ "add": "Hinzufügen", "appearance": "Aussehen", "apply": "Anwenden", + "auto-connect": "Automatisches Verbinden", "automation": "Automatisierung", "available": "Verfügbar", "back": "Zurück", @@ -401,6 +408,7 @@ "contributors": "Mitwirkende", "copied-to-clipboard": "In die Zwischenablage kopiert", "countdown": "Countdown", + "customize": "Anpassen", "date": "Datum", "default": "Standard", "delete": "Löschen", @@ -423,6 +431,11 @@ "execute": "Ausführen", "faithful": "Originalgetreu", "focus": "Fokus", + "font-weight-bold": "Fett", + "font-weight-light": "Leicht", + "font-weight-medium": "Mittel", + "font-weight-regular": "Regulär", + "font-weight-semibold": "Halbfett", "frequency": "Frequenz", "gateway": "Gateway", "general": "Allgemein", @@ -445,6 +458,7 @@ "lock": "Sperren", "logout": "Abmelden", "look": "Aussehen", + "margins": "Ränder", "media": "Medien", "media-player": "Medienplayer", "memory": "Arbeitsspeicher", @@ -467,6 +481,7 @@ "panels": "Panels", "password": "Passwort", "pause": "Pause", + "performance": "Leistung", "pin": "Anpinnen", "play": "Wiedergabe", "polling": "Abfrageintervall", @@ -519,6 +534,7 @@ "unpin": "Loslösen", "update": "Aktualisieren", "upload": "Hochladen", + "userspace-reboot": "Userspace neu starten", "version": "Version", "vibrant": "Lebhaft", "visualizer": "Visualisierer", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Werde Unterstützer", "changelog": "Änderungsprotokoll anzeigen", + "changelog-on-startup": "Änderungsprotokoll bei Update anzeigen", + "changelog-on-startup-desc": "Automatisch den Changelog anzeigen, wenn Noctalia aktualisiert wird.", "contributors-description": "Ein Dankeschön an unseren {count} großartigen Mitwirkenden!", "contributors-description-plural": "Ein Dankeschön an unsere {count} großartigen Mitwirkenden!", "copy-info": "Informationen kopieren", "debug-disabled": "Debug-Modus deaktiviert", "debug-enabled": "Debug-Modus aktiviert", "info-copied": "Info in die Zwischenablage kopiert", + "noctalia-available": "Verfügbar:", "noctalia-desc": "Eine elegante und minimalistische Desktop-Shell, sorgfältig für Wayland entwickelt, gebaut mit Quickshell.", "noctalia-git-commit": "Git-Commit:", "noctalia-installed-version": "Installierte Version:", "noctalia-latest-version": "Neueste Version:", + "noctalia-qs-version": "Noctalia QS Version:", "noctalia-title": "Noctalia Shell", "privacy-policy": "Datenschutzbestimmungen", "support": "Unterstütze uns", @@ -720,6 +740,7 @@ "supporters-desc": "Ein riesiges Dankeschön an unseren großartigen Unterstützer!", "supporters-desc-plural": "Ein riesiges Dankeschön an unsere {count} großartigen Unterstützer!", "supporters-loading": "Supporter werden geladen...", + "system-board": "Motherboard:", "system-cpu": "CPU:", "system-disk": "Festplatte:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "Leistendichte", "appearance-desc": "Erscheinungsbild und Position der Leiste anpassen.", "appearance-display-mode-description": "Wählen Sie, wann die Leiste sichtbar ist.", + "appearance-enable-exclusion-zone-inset-description": "Reduziere die Ausschlusszone um 1 physisches Pixel, damit bündige Fenster perfekt unter den Rand der Bar überlappen.", + "appearance-enable-exclusion-zone-inset-label": "Eingezogener Ausschlussbereich", "appearance-floating-description": "Leiste als schwebende 'Pille' anzeigen.", "appearance-floating-label": "Schwebende Leiste", "appearance-font-scale-description": "Die Skalierung der Schriftgröße für Text in der Leiste anpassen.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Die Leiste ausblenden und Panels schließen, wenn die Compositor-Übersicht aktiv ist.", "appearance-hide-on-overview-label": "Leiste in der Übersicht ausblenden", "appearance-margins-description": "Ränder um die schwebende Leiste anpassen.", - "appearance-margins-horizontal": "Horizontal", - "appearance-margins-label": "Ränder", - "appearance-margins-vertical": "Vertikal", + "appearance-margins-horizontal": "Horizontaler Rand", + "appearance-margins-vertical": "Vertikaler Rand", "appearance-outer-corners-description": "Nach außen gewölbte Ecken auf der Leiste anzeigen.", "appearance-outer-corners-label": "Äußere Ecken", "appearance-position-description": "Wählen Sie, wo die Leiste auf dem Bildschirm platziert werden soll.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Separate Leisten-Deckkraft", "appearance-widget-spacing-description": "Den Abstand zwischen jedem Widget in der Leiste anpassen.", "appearance-widget-spacing-label": "Widget-Abstand", + "behavior-middle-click-command-description": "Befehl, der bei Mittelklick ausgeführt wird.", + "behavior-middle-click-command-label": "Mittelklick-Befehl", + "behavior-middle-click-command-placeholder": "niri msg action Übersicht umschalten", + "behavior-middle-click-description": "Wählen Sie, was ein mittlerer Klick auf leeren Bereichen der Leiste bewirkt.", + "behavior-middle-click-follow-mouse-description": "Öffne das ausgewählte Mittelklick-Panel an der Cursorposition.", + "behavior-middle-click-follow-mouse-label": "Mittelklick folgt Maus", + "behavior-middle-click-label": "Aktion bei Mittelklick auf die Leiste", + "behavior-right-click-command-description": "Befehl, der beim Rechtsklick ausgeführt wird.", + "behavior-right-click-command-label": "Rechtsklick-Befehl", + "behavior-right-click-command-placeholder": "notify-send \"Rechtsklick\"", + "behavior-right-click-description": "Wählen Sie, was ein Rechtsklick auf leere Bereiche der Leiste bewirkt.", + "behavior-right-click-follow-mouse-description": "Öffne das ausgewählte Rechtsklick-Panel an der Cursorposition.", + "behavior-right-click-follow-mouse-label": "Rechtsklick folgt Maus", + "behavior-right-click-label": "Aktion bei Rechtsklick auf die Leiste", + "behavior-wheel-wrap-description": "Wenn aktiviert, wird das Scrollen vom letzten zum ersten Element fortgesetzt.", + "behavior-wheel-wrap-label": "Umlaufend", + "behavior-workspace-scroll-description": "Wählen Sie, was das Mausrad auf leeren Bereichen der Leiste tut.", + "behavior-workspace-scroll-label": "Mausrad-Aktion der Leiste", + "behavior-workspace-scroll-option-content": "Inhalt", + "behavior-workspace-scroll-option-workspace": "Arbeitsbereich", "monitor-configure-widgets": "Widgets konfigurieren", "monitor-override-settings": "Globale Einstellungen überschreiben", "monitor-override-settings-description": "Benutzerdefinierte Einstellungen für diesen Monitor verwenden.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Authentifizierung erforderlich", + "bluetooth-auto-connect-description": "Automatisch mit vertrauenswürdigen gekoppelten Geräten verbinden, wenn Bluetooth aktiviert ist.", + "bluetooth-auto-connect-label": "Gekoppelte Geräte automatisch verbinden", "bluetooth-devices-unnamed": "Unbenannte Geräte werden nicht angezeigt.", "bluetooth-discoverable": "Dieses Gerät ist auffindbar als {hostName}, solange dieser Einstellungs-Tab geöffnet ist.", "bluetooth-rssi-polling-description": "Periodisch RSSI für verbundene Geräte über bluetoothctl abfragen. Möglicherweise nicht für alle Geräte verfügbar; verbraucht bei Aktivierung minimale Ressourcen.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Passen Sie an, welche Steuerelemente im Kontrollzentrum angezeigt werden und in welcher Reihenfolge.", "desc": "Konfigurieren Sie die Positionierung und das Verhalten des Kontrollzentrum-Panels.", + "open-at-mouse-description": "Beim Rechtsklick auf einen leeren Bereich der Leiste, öffne das Kontrollzentrum nahe dem Cursor. Wenn deaktiviert, wird stattdessen die obige Positionseinstellung verwendet.", + "open-at-mouse-label": "Am Mauszeiger bei Rechtsklick öffnen", "position-description": "Wählen Sie aus, wo das Kontrollzentrum-Panel angezeigt wird, wenn es geöffnet wird.", "shortcuts-custom-button-command-description": "Der Befehl, der ausgeführt werden soll, wenn die Schaltfläche geklickt wird.", "shortcuts-custom-button-enable-on-state-logic-description": "Aktiviert ein zweites Symbol und 'Aktiv'-Zustand basierend auf einem Prüfbefehl.", @@ -983,6 +1029,7 @@ "edit-mode-description": "Aktiviere den Bearbeitungsmodus, um Desktop-Widgets zu verschieben und neu zu positionieren. Im aktivierten Zustand zeigen Widgets eine Ziehumrandung und können neu positioniert werden.", "edit-mode-exit-button": "Bearbeitungsmodus verlassen", "edit-mode-grid-snap-label": "Raster einrasten", + "edit-mode-grid-snap-scale-label": "Rasterfangskala", "edit-mode-label": "Bearbeitungsmodus", "enabled-description": "Desktop-Widgets vollständig aktivieren oder deaktivieren.", "enabled-label": "Desktop-Widgets aktivieren", @@ -1017,7 +1064,7 @@ }, "display": { "monitors-backlight-device-auto-option": "Standard", - "monitors-backlight-device-description": "Wähle ein Hintergrundbeleuchtungsgerät für diesen Ausgang.", + "monitors-backlight-device-description": "Wählen Sie ein Gerät für die Hintergrundbeleuchtung für diesen Ausgang.", "monitors-backlight-device-label": "Hintergrundbeleuchtungsgerät", "monitors-brightness-step-description": "Schrittgröße für Helligkeitsänderungen anpassen (Mausrad und Tastenkürzel).", "monitors-brightness-step-label": "Helligkeits-Schrittgröße", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Automatisch ausblenden", "appearance-display-description": "Wählen Sie, wie sich das Dock verhält.", "appearance-display-exclusive": "Exklusiv", + "appearance-dock-indicator-description": "Zeige eine kleine Anzeige, wenn das Dock ausgeblendet ist.", + "appearance-dock-indicator-label": "Dock-Indikator", "appearance-floating-distance-description": "Schwebeabstand vom Bildschirmrand anpassen.", "appearance-floating-distance-label": "Dock-Schwebeabstand", - "appearance-frame-indicator-description": "Zeige eine kleine Anzeige am Rahmen, wenn das Dock ausgeblendet ist.", - "appearance-frame-indicator-label": "Gerahmter Dock-Indikator", "appearance-group-apps-description": "Gruppiere mehrere Fenster derselben App zu einem Dock-Eintrag.", "appearance-group-apps-label": "Gleiche Apps gruppieren", "appearance-group-click-action-cycle": "Fenster durchwechseln", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Dock-Größe", "appearance-inactive-indicators-description": "Anzeige von Indikator-Pillen für alle Apps, nicht nur für die aktuell aktive.", "appearance-inactive-indicators-label": "Laufende Indikatoren", + "appearance-indicator-color-description": "Wählen Sie die Farbe des Indikators für das versteckte Dock.", + "appearance-indicator-color-label": "Indikatorfarbe", + "appearance-indicator-opacity-description": "Passen Sie die Deckkraft des Indikators für das versteckte Dock an.", + "appearance-indicator-opacity-label": "Indikator-Deckkraft", + "appearance-indicator-thickness-description": "Aktivieren Sie einen dickeren Indikator für das versteckte Dock (6px statt 3px).", + "appearance-indicator-thickness-label": "Dickerer Indikator", "appearance-launcher-position-description": "Wählen Sie, wo das Launcher-Symbol im Dock erscheint.", "appearance-launcher-position-end": "Ende", "appearance-launcher-position-label": "Position des Starters", @@ -1100,7 +1153,7 @@ "appearance-show-launcher-icon-label": "App-Launcher anzeigen", "appearance-sit-on-frame-description": "Richten Sie das Dock innerhalb des Rahmenrands aus, anstatt es oben zu platzieren.", "appearance-sit-on-frame-label": "Dock sitzt am Rahmen", - "appearance-type-description": "Wähle zwischen einer schwebenden Pillenform oder einer statischen, am Rand befestigten Leiste.", + "appearance-type-description": "Wählen Sie zwischen einer schwebenden Pillenform oder einer statischen, am Rand befestigten Leiste.", "appearance-type-floating": "Schwebend", "appearance-type-label": "Dock-Stil", "appearance-type-static": "Statisch", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Automatisch", "language-select-description": "Wählen Sie die Sprache der Anwendungsoberfläche.", "language-select-label": "Anwendungssprache", - "launch-setup-wizard": "Starten Sie den Setup-Assistenten", + "launch-setup-wizard": "Setup-Assistent", "profile-desc": "Ändern Sie Ihren Avatar.", "profile-picture-description": "Der Avatar, der in der gesamten Benutzeroberfläche angezeigt wird.", "profile-picture-label": "Profilbild", @@ -1169,7 +1222,7 @@ }, "hooks": { "info-command-info-description": "• Befehle werden über Shell ausgeführt (sh -lc)
• Befehle laufen im Hintergrund (getrennt)
• Test-Buttons führen mit aktuellen Werten aus", - "info-parameters-description": "• Wallpaper-Hook: $1 = Wallpaper-Pfad, $2 = Bildschirmname
• Themenwechsel-Hook: $1 = true/false (Dunkelmodus-Status)
• Bildschirm-Sperr-/Entsperr-Hooks: Keine Parameter
• Leistungsmodus-Hooks: Keine Parameter
• Session-Hook: $1 = Aktion (Herunterfahren/Neustart)", + "info-parameters-description": "• Haken für Hintergrundbild: $1 = Pfad zum Hintergrundbild, $2 = Bildschirmname
• Haken für Design-Umschalter: $1 = true/false (Dark Mode-Status)
• Haken für Bildschirm sperren/entsperren: $1 = lock/unlock (Status der Bildschirmsperre)
• Haken für Leistungsmodus: Keine Parameter
• Haken für Sitzung: $1 = action (shutdown/reboot)", "info-parameters-label": "Verfügbare Parameter", "noctalia-started-description": "Befehl, der ausgeführt wird, wenn Noctalia vollständig geladen wurde.", "noctalia-started-label": "Noctalia gestartet", @@ -1206,24 +1259,33 @@ "custom-description": "Führe einen Shell-Befehl nach einer Zeit der Inaktivität aus.", "custom-entry-command": "Befehl", "custom-entry-delete": "Löschen", + "custom-entry-edit": "Benutzerdefinierten Befehl bearbeiten", + "custom-entry-name": "Name", + "custom-entry-name-placeholder": "z.B. Lichter ausschalten", + "custom-entry-new": "Neuer Benutzerdefinierter Befehl", "custom-entry-timeout": "Inaktivitätszeit", - "custom-label": "Benutzerdefinierte Leerlaufbefehle", - "enable-description": "Automatisch den Bildschirm ausschalten, sperren oder in den Ruhezustand versetzen nach einer Zeit der Inaktivität.", - "enable-label": "Leerlaufverwaltung aktivieren", + "custom-entry-timeout-format": "{count} Sekunde", + "custom-entry-timeout-format-plural": "{count} Sekunden", + "custom-entry-unnamed": "Unbenannter Befehl", + "custom-label": "Benutzerdefinierte Inaktivitätsbefehle", + "enable-description": "Automatisch den Bildschirm ausschalten, sperren oder in den Energiesparmodus versetzen nach einer Zeit der Inaktivität.", + "enable-label": "Inaktivitätsverwaltung aktivieren", "fade-duration-description": "Sekunden für die Schwarzblende-Animation, bevor jede Aktion ausgelöst wird. Jede Mausbewegung bricht die Überblendung ab.", "fade-duration-label": "Einblenddauer", "lock-description": "Sekunden der Inaktivität, bevor der Sperrbildschirm aktiviert wird.", "lock-label": "Bildschirmsperre", + "resume-command-label": "Befehl fortsetzen", "screen-off-description": "Sekunden der Inaktivität, bevor die Monitore ausgeschaltet werden.", "screen-off-label": "Bildschirm ausschalten", - "status-description": "Leerlaufzeit, wie vom Compositor gemeldet.", - "status-label": "Leerlaufzeit", - "suspend-description": "Sekunden der Inaktivität, bevor das System in den Ruhezustand geht.", + "status-description": "Inaktivitätszeit, wie vom Compositor gemeldet.", + "status-label": "Inaktivitätszeit", + "suspend-description": "Sekunden der Inaktivität, bevor das System in den Energiesparmodus geht.", "tab-behavior": "Verhalten", "tab-custom": "Benutzerdefiniert", - "timeouts-description": "Auf 0 setzen, um eine Stufe zu deaktivieren. Timeouts werden pausiert, während Keep Awake aktiv ist.", - "timeouts-label": "Zeitüberschreitungen", - "unavailable": "Native Leerlaufüberwachung ist auf diesem Compositor nicht verfügbar." + "timeouts-description": "Auf 0 setzen, um eine Stufe zu deaktivieren. Timeouts werden pausiert, während 'Wach halten' aktiv ist.", + "timeouts-label": "Timeouts", + "title": "Inaktivität", + "unavailable": "Native Inaktivitätsüberwachung ist auf diesem Compositor nicht verfügbar." }, "indicator": { "default-value": "Standard: {value}", @@ -1279,7 +1341,7 @@ "settings-terminal-command-label": "Terminalbefehl", "settings-use-app2unit-description": "Verwendet eine alternative Startmethode zur besseren Verwaltung von App-Prozessen und Problemvermeidung.", "settings-use-app2unit-label": "App2Unit zum Starten von Anwendungen verwenden", - "settings-view-mode-description": "Wähle das Layout für die Anwendungsstarter-Einträge.", + "settings-view-mode-description": "Wählen Sie das Layout für die Anwendungsstarter-Einträge.", "settings-view-mode-label": "Ansichtsmodus", "title": "Starter" }, @@ -1334,6 +1396,8 @@ "clock-style-label": "Uhrstil", "compact-lockscreen-description": "Zeige nur die Login-Eingabe und Systemsteuerung, blende Wetter- und Medien-Widgets aus.", "compact-lockscreen-label": "Kompakter Sperrbildschirm", + "enable-lockscreen-media-controls-description": "Interaktive Medienwiedergabesteuerung auf dem Sperrbildschirm anzeigen.", + "enable-lockscreen-media-controls-label": "Sperrbildschirm-Mediensteuerung", "lock-on-suspend-description": "Den Bildschirm beim Energiesparen automatisch sperren.", "lock-on-suspend-label": "Sperren beim Energiesparen", "lock-screen-animations-description": "Sperrbildschirm-Animationen aktivieren oder deaktivieren.", @@ -1381,7 +1445,7 @@ "monitors-desc": "Benachrichtigungen auf bestimmten Monitoren anzeigen. Standardmäßig werden sie auf allen Monitoren angezeigt.", "settings-always-on-top-description": "Benachrichtigungen über Vollbildfenstern und anderen Ebenen anzeigen.", "settings-background-opacity-description": "Transparenz der Benachrichtigungshintergründe anpassen.", - "settings-density-description": "Wähle die Dichte der Benachrichtigungskarte.", + "settings-density-description": "Wählen Sie die Dichte der Benachrichtigungskarte.", "settings-density-label": "Dichte", "settings-desc": "Erscheinungsbild und Verhalten von Benachrichtigungen konfigurieren.", "settings-do-not-disturb-description": "Alle Benachrichtigungs-Popups deaktivieren.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Tastenkombinationshinweise bei den Sitzungsoptionen anzeigen.", "show-keybinds-label": "Tastenkombinationen anzeigen" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Desktop-Widgets sichtbar lassen, während der Noctalia Leistungsmodus aktiviert ist.", + "noctalia-performance-disable-desktop-widgets-label": "Aktiviere Desktop-Widgets im Leistungsmodus", + "noctalia-performance-disable-wallpaper-description": "Halte Desktop-, Übersicht- und Sperrbildschirm-Hintergrundbilder sichtbar, während der Noctalia Leistungsmodus aktiviert ist.", + "noctalia-performance-disable-wallpaper-label": "Hintergrundbild-Rendering im Leistungsmodus aktivieren", + "title": "System" + }, "system-monitor": { "critical-color-label": "Kritische Farbe", "custom-highlight-colors-title-label": "Benutzerdefinierte Hervorhebungsfarben", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Abfrageintervall", "polling-section-description": "Konfigurieren Sie, wie oft jede Systemmetrik aktualisiert wird.", "polling-section-label": "Abfrageintervalle", - "threshold-critical": "Kritische Schwelle", - "threshold-warning": "Warnschwelle", + "threshold-critical": "Kritisch", + "threshold-warning": "Warnung", "thresholds-section-description": "Passe die Warn-/Kritisch-Schwellen und die Abfrageintervalle für jede Systemmetrik an.", "use-custom-highlight-colors-description": "Wenn deaktiviert, werden die Standard-Hervorhebungsfarben des Themes verwendet.", "use-custom-highlight-colors-label": "Benutzerdefinierte Hervorhebungsfarben verwenden", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Animationsgeschwindigkeit", "animation-speed-reset": "Animationsgeschwindigkeit zurücksetzen", "appearance-desc": "Visuelle Elemente wie Tooltips, Rahmen und Schatten anpassen.", + "blur-behind-description": "Macht den Bereich hinter Leisten und Panels unscharf mithilfe des Compositor-Blur-Protokolls.", + "blur-behind-label": "Hintergrundunschärfe", "box-border-description": "Zeige eine Umrandung um Inhaltsbereiche an.", "box-border-label": "Containerumriss", "box-border-radius-description": "Passt die Eckenrundung wichtiger Layoutbereiche an, wie z. B. Seitenleisten, Karten und Inhaltsbereiche.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Panels & Leiste oben behalten", "scaling-description": "Ändert die Größe der allgemeinen Benutzeroberfläche, mit Ausnahme der Leiste.", "scaling-label": "Oberflächenskalierung", + "scrollbar-always-visible-description": "Bildlaufleisten immer sichtbar lassen, wenn Inhalte scrollbar sind, anstatt sie nur beim Darüberfahren anzuzeigen.", + "scrollbar-always-visible-label": "Bildlaufleisten immer anzeigen", "settings-panel-header": "Einstellungs-Panel", "settings-panel-mode-description": "Wählen Sie das Layout der Einstellungen (möglicherweise ist ein Neustart erforderlich).", "settings-panel-mode-label": "Einstellungs-Panel-Modus", @@ -1619,7 +1694,9 @@ "shadows-label": "Schlagschatten", "title": "Benutzeroberfläche", "tooltips-description": "Tooltips in der gesamten Benutzeroberfläche aktivieren oder deaktivieren.", - "tooltips-label": "Tooltips anzeigen" + "tooltips-label": "Tooltips anzeigen", + "translucent-widgets-description": "Mache Schaltflächen, Tabs und andere Widgets in Panels halbtransparent.", + "translucent-widgets-label": "Transluzente Widgets" }, "wallpaper": { "automation-change-mode-alphabetical": "Alphabetisch", @@ -1725,7 +1802,7 @@ }, "skip-setup": "Einrichtung überspringen", "telemetry-wizard-done": "Verstanden!", - "telemetry-wizard-note": "Sie haben die Kontrolle — aktivieren oder deaktivieren Sie dies jederzeit in den Einstellungen", + "telemetry-wizard-note": "Sie haben die Kontrolle – aktivieren oder deaktivieren Sie dies jederzeit in den Einstellungen.", "telemetry-wizard-subtitle": "Wir haben anonyme Analysen hinzugefügt, um Noctalia zu verbessern", "telemetry-wizard-title": "Datenschutz-Update", "wallpaper": { @@ -1742,6 +1819,7 @@ "select-prompt": "Wählen Sie unten ein Hintergrundbild", "subheader": "Bestimmen Sie die Stimmung mit einem schönen Hintergrund." }, + "welcome": "Willkommen", "welcome-note": "Nur ein paar Grundeinstellungen — alle Optionen befinden sich in den Einstellungen", "welcome-subtitle": "Gestalten wir Ihren Desktop einzigartig", "welcome-title": "Willkommen bei Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "{widget} Einstellungen" }, "system-monitor": { + "core-usage": "Kern {id} Nutzung", "cpu-temp": "CPU-Temperatur", "cpu-usage": "CPU-Auslastung", "disk": "Festplatte", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Adresse in die Zwischenablage kopiert", + "auto-connect-disabled": "Automatisches Verbinden deaktiviert", + "auto-connect-enabled": "Automatisches Verbinden aktiviert", + "auto-connecting": "Verbinde mit {count} Gerät(en)...", "confirm-code": "Bestätigungscode {value} auf dem anderen Gerät bestätigen.", "connect-failed": "Verbindung mit dem Gerät fehlgeschlagen", "disconnect-failed": "Trennen vom Gerät fehlgeschlagen", @@ -1800,6 +1882,10 @@ "unavailable": "Zwischenablageverlauf nicht verfügbar", "unavailable-desc": "Die 'cliphist' Anwendung ist nicht installiert. Bitte installieren Sie sie, um Zwischenablageverlauf-Features zu nutzen" }, + "custom-command-failed": { + "description": "Befehl fehlgeschlagen: {command}\\nExit-Code: {code}", + "title": "Benutzerdefinierter Befehl fehlgeschlagen" + }, "do-not-disturb": { "disabled": "'Nicht stören' deaktiviert", "disabled-desc": "Alle Benachrichtigungen werden angezeigt", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Widget hinzufügen", + "auto-connect": "Automatisches Verbinden für dieses Gerät umschalten", + "bluetooth-auto-connect-off": "Automatisches Verbinden deaktiviert", + "bluetooth-auto-connect-on": "Automatisches Verbinden aktiviert", "bluetooth-devices": "Bluetooth-Geräte", "brightness-at": "Helligkeit: {brightness}%", "click-to-start-recording": "Bildschirmrekorder (Aufnahme starten)", @@ -1951,9 +2040,14 @@ "resolution-label": "Auflösung", "resolution-mode-label": "Modus", "solid-color-tooltip": "Einfarbiger Hintergrund", + "sort-date-asc": "Nach Ältestem zuerst sortieren", + "sort-date-desc": "Nach Neuestem zuerst sortieren", + "sort-name-asc": "Nach Name sortieren (A-Z)", + "sort-name-desc": "Nach Name sortieren (Z-A)", + "sort-random": "Nach Zufall sortieren", "sorting-date-added": "Hinzugefügt am", "sorting-favorites": "Favoriten", - "sorting-label": "Sortieren nach", + "sorting-label": "Sortiere nach", "sorting-relevance": "Relevanz", "sorting-toplist": "Topliste", "sorting-views": "Aufrufe", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 9806caf3a..a4b4d5ea4 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Select the color for the visualizer.", "color-name-label": "Fill color", + "height-description": "Custom component width.", "hide-when-idle-description": "When enabled, the visualizer is hidden unless a player is actively playing.", "hide-when-idle-label": "Hide when no media is playing", "width-description": "Custom component width." @@ -60,7 +61,7 @@ "hide-if-not-detected-description": "Hide the widget when no battery is detected on the system.", "hide-if-not-detected-label": "Hide when not detected", "low-battery-threshold-label": "Low battery warning threshold", - "show-noctalia-performance-description": "Display the Noctalia Performance Mode toggle inside the battery panel.
Disables shadows and animations in Noctalia to reduce resource usage.", + "show-noctalia-performance-description": "Display the Noctalia performance mode toggle inside the battery panel.
Disables shadows and animations in Noctalia to reduce resource usage.", "show-noctalia-performance-label": "Show Noctalia Performance toggle", "show-power-profile-description": "Display the power profile selection inside the battery panel.", "show-power-profile-label": "Show power profile controls" @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Display stats as mini bar charts instead of text values. Prevents layout shifting.", "compact-mode-label": "Compact mode", + "cpu-cores-description": "Display CPU cores usage individually.", + "cpu-cores-label": "CPU Cores", "cpu-frequency-description": "Display the current CPU clock speed in GHz.", "cpu-frequency-label": "Show CPU frequency", "cpu-temperature-description": "Show CPU temperature readings if available.", @@ -291,6 +294,8 @@ "focused-color-label": "Focused workspace color", "follow-focused-screen-description": "Display workspaces from the currently focused screen, rather than the screen where the bar is located.", "follow-focused-screen-label": "Follow focused screen", + "font-weight-description": "Set the visual weight for text within workspace.", + "font-weight-label": "Font weight", "grouped-border-opacity-description": "Set the opacity level for workspace container borders.", "grouped-border-opacity-label": "Border opacity", "hide-unoccupied-description": "Don't display workspaces without windows.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "All batteries (combined)", "battery-health": "Battery health", "battery-level": "Battery level", "capacity-level": "Capacity: {level}", @@ -374,6 +380,7 @@ "add": "Add", "appearance": "Appearance", "apply": "Apply", + "auto-connect": "Auto-connect", "automation": "Automation", "available": "Available", "back": "Back", @@ -401,6 +408,7 @@ "contributors": "Contributors", "copied-to-clipboard": "Copied to clipboard", "countdown": "Countdown", + "customize": "Customize", "date": "Date", "default": "Default", "delete": "Delete", @@ -423,6 +431,11 @@ "execute": "Execute", "faithful": "Faithful", "focus": "Focus", + "font-weight-bold": "Bold", + "font-weight-light": "Light", + "font-weight-medium": "Medium", + "font-weight-regular": "Regular", + "font-weight-semibold": "Semi-bold", "frequency": "Frequency", "gateway": "Gateway", "general": "General", @@ -445,6 +458,7 @@ "lock": "Lock", "logout": "Logout", "look": "Look", + "margins": "Margins", "media": "Media", "media-player": "Media Player", "memory": "Memory", @@ -467,6 +481,7 @@ "panels": "Panels", "password": "Password", "pause": "Pause", + "performance": "Performance", "pin": "Pin", "play": "Play", "polling": "Polling", @@ -519,6 +534,7 @@ "unpin": "Unpin", "update": "Update", "upload": "Upload", + "userspace-reboot": "Userspace Reboot", "version": "Version", "vibrant": "Vibrant", "visualizer": "Visualizer", @@ -703,23 +719,28 @@ "about": { "become-supporter": "Become a supporter", "changelog": "View changelog", + "changelog-on-startup": "Show changelog on update", + "changelog-on-startup-desc": "Automatically show the changelog when Noctalia is updated", "contributors-description": "Shout-out to our {count} awesome contributor!", "contributors-description-plural": "Shout-out to our {count} awesome contributors!", "copy-info": "Copy info", "debug-disabled": "Debug mode disabled", "debug-enabled": "Debug mode enabled", "info-copied": "Info copied to clipboard", + "noctalia-available": "Available:", "noctalia-desc": "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell.", "noctalia-git-commit": "Git commit:", "noctalia-installed-version": "Installed version:", "noctalia-latest-version": "Latest version:", - "noctalia-title": "Noctalia shell", + "noctalia-qs-version": "Noctalia QS version:", + "noctalia-title": "Noctalia Shell", "privacy-policy": "Privacy policy", "support": "Support us", "supporter-badge": "Supporter", "supporters-desc": "A huge thank you to our amazing supporter!", "supporters-desc-plural": "A huge thank you to our {count} amazing supporters!", "supporters-loading": "Loading supporters...", + "system-board": "Board:", "system-cpu": "CPU:", "system-disk": "Disk:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "Bar density", "appearance-desc": "Customize the bar's appearance and position.", "appearance-display-mode-description": "Choose when the bar is visible.", + "appearance-enable-exclusion-zone-inset-description": "Reduce the exclusion zone by 1 physical pixel so flush windows bleed perfectly under the bar edge.", + "appearance-enable-exclusion-zone-inset-label": "Inset exclusion zone", "appearance-floating-description": "Display the bar as a floating 'pill'.", "appearance-floating-label": "Floating bar", "appearance-font-scale-description": "Adjust the font size scale for text displayed in the bar.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Hide the bar and close panels when the compositor overview is active.", "appearance-hide-on-overview-label": "Hide bar on overview", "appearance-margins-description": "Adjust the margins around the floating bar.", - "appearance-margins-horizontal": "Horizontal", - "appearance-margins-label": "Margins", - "appearance-margins-vertical": "Vertical", + "appearance-margins-horizontal": "Horizontal Margin ", + "appearance-margins-vertical": "Vertical Margin", "appearance-outer-corners-description": "Display outwardly curved corners on the bar.", "appearance-outer-corners-label": "Outer corners", "appearance-position-description": "Choose where to place the bar on the screen.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Use separate bar opacity", "appearance-widget-spacing-description": "Adjust the spacing between each widget in the bar.", "appearance-widget-spacing-label": "Widget spacing", + "behavior-middle-click-command-description": "Command to execute on middle click.", + "behavior-middle-click-command-label": "Middle click command", + "behavior-middle-click-command-placeholder": "niri msg action toggle-overview", + "behavior-middle-click-description": "Choose what middle click does on empty areas of the bar.", + "behavior-middle-click-follow-mouse-description": "Open the selected middle-click panel at the cursor position.", + "behavior-middle-click-follow-mouse-label": "Middle click follow mouse", + "behavior-middle-click-label": "Bar middle click action", + "behavior-right-click-command-description": "Command to execute on right click.", + "behavior-right-click-command-label": "Right click command", + "behavior-right-click-command-placeholder": "notify-send \"Right click\"", + "behavior-right-click-description": "Choose what right click does on empty areas of the bar.", + "behavior-right-click-follow-mouse-description": "Open the selected right-click panel at the cursor position.", + "behavior-right-click-follow-mouse-label": "Right click follow mouse", + "behavior-right-click-label": "Bar right click action", + "behavior-wheel-wrap-description": "When enabled, scrolling continues from the last item to the first.", + "behavior-wheel-wrap-label": "Wrap around", + "behavior-workspace-scroll-description": "Choose what the mouse wheel does on empty areas of the bar.", + "behavior-workspace-scroll-label": "Bar mouse wheel action", + "behavior-workspace-scroll-option-content": "Content", + "behavior-workspace-scroll-option-workspace": "Workspace", "monitor-configure-widgets": "Configure widgets", "monitor-override-settings": "Override global settings", "monitor-override-settings-description": "Use custom settings for this monitor.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Authentication required", + "bluetooth-auto-connect-description": "Automatically connect to trusted paired devices when Bluetooth is enabled.", + "bluetooth-auto-connect-label": "Auto-connect paired devices", "bluetooth-devices-unnamed": "Unnamed devices are not shown.", "bluetooth-discoverable": "This device is discoverable as {hostName} while this settings tab is open.", "bluetooth-rssi-polling-description": "Periodically sample RSSI for connected devices via bluetoothctl. May not be available for all devices; uses minimal resources when enabled.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Customize which controls appear in the control center and in what order.", "desc": "Configure the control center panel positioning and behavior.", + "open-at-mouse-description": "When right-clicking an empty area of the bar, open the control center near the cursor. When disabled, the position setting above is used instead.", + "open-at-mouse-label": "Open at cursor on right-click", "position-description": "Choose where the control center panel appears when opened.", "shortcuts-custom-button-command-description": "The command to execute when the button is clicked.", "shortcuts-custom-button-enable-on-state-logic-description": "Enable a second icon and 'hot' state based on a check command.", @@ -983,6 +1029,7 @@ "edit-mode-description": "Enable edit mode to move and reposition desktop widgets. When enabled, widgets show a drag outline and can be repositioned.", "edit-mode-exit-button": "Exit edit mode", "edit-mode-grid-snap-label": "Grid snap", + "edit-mode-grid-snap-scale-label": "Grid snap scale", "edit-mode-label": "Edit mode", "enabled-description": "Enable or disable desktop widgets entirely.", "enabled-label": "Enable desktop widgets", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Auto hide", "appearance-display-description": "Choose how the dock behaves.", "appearance-display-exclusive": "Exclusive", + "appearance-dock-indicator-description": "Show a small indicator when the dock is hidden.", + "appearance-dock-indicator-label": "Dock indicator", "appearance-floating-distance-description": "Set the distance between the dock and the edge of the screen.", "appearance-floating-distance-label": "Dock floating distance", - "appearance-frame-indicator-description": "Show a small indicator on the frame when the dock is hidden.", - "appearance-frame-indicator-label": "Frame dock indicator", "appearance-group-apps-description": "Group multiple windows from the same app into one dock entry.", "appearance-group-apps-label": "Group same apps", "appearance-group-click-action-cycle": "Cycle windows", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Dock size", "appearance-inactive-indicators-description": "Display indicator pills for all apps, not just the currently active one.", "appearance-inactive-indicators-label": "Running indicators", + "appearance-indicator-color-description": "Choose the color of the hidden dock indicator.", + "appearance-indicator-color-label": "Indicator color", + "appearance-indicator-opacity-description": "Adjust the opacity of the hidden dock indicator.", + "appearance-indicator-opacity-label": "Indicator opacity", + "appearance-indicator-thickness-description": "Toggle a thicker hidden dock indicator (6px instead of 3px).", + "appearance-indicator-thickness-label": "Thicker indicator", "appearance-launcher-position-description": "Choose where the launcher icon appears in the dock.", "appearance-launcher-position-end": "End", "appearance-launcher-position-label": "Launcher position", @@ -1169,15 +1222,15 @@ }, "hooks": { "info-command-info-description": "• Commands are executed via shell (sh -lc)
• Commands run in background (detached)
• Test buttons execute with current values", - "info-parameters-description": "• Wallpaper hook: $1 = wallpaper path, $2 = screen name
• Theme toggle hook: $1 = true/false (Dark Mode state)
• Screen lock/unlock hooks: No parameters
• Performance mode hooks: No parameters
• Session hook: $1 = action (shutdown/reboot)", + "info-parameters-description": "• Wallpaper hook: $1 = wallpaper path, $2 = screen name
• Theme toggle hook: $1 = true/false (Dark Mode state)
• Screen lock/unlock hooks: $1 = lock/unlock (screen lock state)
• Performance mode hooks: No parameters
• Session hook: $1 = action (shutdown/reboot)", "info-parameters-label": "Available parameters", "noctalia-started-description": "Command to execute when Noctalia has finished loading.", - "noctalia-started-label": "Noctalia Started", + "noctalia-started-label": "Noctalia started", "noctalia-started-placeholder": "e.g. notify-send 'Noctalia Loaded'", - "performance-mode-disabled-description": "Command to be executed when Noctalia Performance Mode is disabled.", + "performance-mode-disabled-description": "Command to be executed when Noctalia performance mode is disabled.", "performance-mode-disabled-label": "Performance mode disabled", "performance-mode-disabled-placeholder": "e.g. notify-send \"Performance\" \"Mode disabled\"", - "performance-mode-enabled-description": "Command to be executed when Noctalia Performance Mode is enabled.", + "performance-mode-enabled-description": "Command to be executed when Noctalia performance mode is enabled.", "performance-mode-enabled-label": "Performance mode enabled", "performance-mode-enabled-placeholder": "e.g., notify-send \"Performance\" \"Mode enabled\"", "screen-lock-description": "Command to be executed when the screen is locked.", @@ -1206,7 +1259,14 @@ "custom-description": "Run a shell command after a period of inactivity.", "custom-entry-command": "Command", "custom-entry-delete": "Delete", + "custom-entry-edit": "Edit Custom Command", + "custom-entry-name": "Name", + "custom-entry-name-placeholder": "e.g. Turn off lights", + "custom-entry-new": "New Custom Command", "custom-entry-timeout": "Inactivity time", + "custom-entry-timeout-format": "{count} second", + "custom-entry-timeout-format-plural": "{count} seconds", + "custom-entry-unnamed": "Unnamed command", "custom-label": "Custom idle commands", "enable-description": "Automatically turn off the screen, lock, or suspend after a period of inactivity.", "enable-label": "Enable idle management", @@ -1214,6 +1274,7 @@ "fade-duration-label": "Fade duration", "lock-description": "Seconds of inactivity before the lock screen activates.", "lock-label": "Lock screen", + "resume-command-label": "Resume command", "screen-off-description": "Seconds of inactivity before monitors are turned off.", "screen-off-label": "Turn off screen", "status-description": "Idle time as reported by the compositor.", @@ -1223,6 +1284,7 @@ "tab-custom": "Custom", "timeouts-description": "Set to 0 to disable a stage. Timeouts are paused while Keep Awake is active.", "timeouts-label": "Timeouts", + "title": "Idle", "unavailable": "Native idle monitoring is not available on this compositor." }, "indicator": { @@ -1340,6 +1402,8 @@ "clock-style-label": "Clock style", "compact-lockscreen-description": "Show only the login input and system controls, hiding weather and media widgets.", "compact-lockscreen-label": "Compact lock screen", + "enable-lockscreen-media-controls-description": "Show interactive media playback controls on the lock screen.", + "enable-lockscreen-media-controls-label": "Lock screen media controls", "lock-on-suspend-description": "Automatically lock the screen when suspending the system.", "lock-on-suspend-label": "Lock on suspend", "lock-screen-animations-description": "Enable or disable lockscreen animations.", @@ -1563,6 +1627,13 @@ "show-keybinds-description": "Display keybind hints on session options.", "show-keybinds-label": "Show keybinds" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Keep desktop widgets visible while Noctalia performance mode is enabled.", + "noctalia-performance-disable-desktop-widgets-label": "Enable desktop widgets in performance mode", + "noctalia-performance-disable-wallpaper-description": "Keep desktop, overview, and lock screen wallpapers visible while Noctalia performance mode is enabled.", + "noctalia-performance-disable-wallpaper-label": "Enable wallpaper rendering in performance mode", + "title": "System" + }, "system-monitor": { "critical-color-label": "Critical color", "custom-highlight-colors-title-label": "Custom highlight colors", @@ -1595,6 +1666,8 @@ "animation-speed-label": "Animation speed", "animation-speed-reset": "Reset animation speed", "appearance-desc": "Customize visual elements like tooltips, borders, and shadows.", + "blur-behind-description": "Blurs the area behind bars and panels using the compositor blur protocol.", + "blur-behind-label": "Blur behind", "box-border-description": "Display an outline around content areas.", "box-border-label": "Container outline", "box-border-radius-description": "Adjusts the corner roundness of major layout sections, such as sidebars, cards, and content panels.", @@ -1614,6 +1687,8 @@ "panels-overlay-label": "Keep panels & bar on top", "scaling-description": "Changes the size of the general user interface, excluding the bar.", "scaling-label": "Interface scaling", + "scrollbar-always-visible-description": "Keep scrollbars visible whenever content is scrollable, instead of only showing them on hover.", + "scrollbar-always-visible-label": "Always show scrollbars", "settings-panel-header": "Settings panel", "settings-panel-mode-description": "Choose settings layout (may require reopening).", "settings-panel-mode-label": "Settings panel mode", @@ -1625,7 +1700,9 @@ "shadows-label": "Drop shadows", "title": "User Interface", "tooltips-description": "Enable or disable tooltips throughout the interface.", - "tooltips-label": "Show tooltips" + "tooltips-label": "Show tooltips", + "translucent-widgets-description": "Make buttons, tabs, and other widgets inside panels semi-transparent.", + "translucent-widgets-label": "Translucent widgets" }, "wallpaper": { "automation-change-mode-alphabetical": "Alphabetical", @@ -1748,6 +1825,7 @@ "select-prompt": "Select a wallpaper below", "subheader": "Set the mood with a beautiful background." }, + "welcome": "Welcome", "welcome-note": "Just a few basics to get you started — full options are in the settings", "welcome-subtitle": "Let's make your desktop uniquely yours", "welcome-title": "Welcome to Noctalia!" @@ -1767,6 +1845,7 @@ "widget-settings-title": "{widget} Settings" }, "system-monitor": { + "core-usage": "Core {id} usage", "cpu-temp": "CPU temp", "cpu-usage": "CPU usage", "disk": "Disk", @@ -1788,6 +1867,9 @@ }, "bluetooth": { "address-copied": "Address copied to clipboard", + "auto-connect-disabled": "Auto-connect disabled", + "auto-connect-enabled": "Auto-connect enabled", + "auto-connecting": "Connecting to {count} device(s)...", "confirm-code": "Confirm code {value} on the other device.", "connect-failed": "Failed to connect to device", "disconnect-failed": "Failed to disconnect from device", @@ -1806,6 +1888,10 @@ "unavailable": "Clipboard history unavailable", "unavailable-desc": "The 'cliphist' application is not installed. Please install it to use clipboard history features" }, + "custom-command-failed": { + "description": "Command failed: {command}\nExit code: {code}", + "title": "Custom command failed" + }, "do-not-disturb": { "disabled": "Do Not Disturb disabled", "disabled-desc": "Showing all notifications", @@ -1860,6 +1946,9 @@ }, "tooltips": { "add-widget": "Add widget", + "auto-connect": "Toggle auto-connection for this device", + "bluetooth-auto-connect-off": "Auto-connect is off", + "bluetooth-auto-connect-on": "Auto-connect is on", "bluetooth-devices": "Bluetooth devices", "brightness-at": "Brightness: {brightness}%", "click-to-start-recording": "Screen recorder (start recording)", @@ -1889,7 +1978,7 @@ "next-media": "Next track", "next-month": "Next month", "night-light-not-installed": "Night Light (not available)", - "noctalia-performance-enabled": "Noctalia Performance Mode", + "noctalia-performance-enabled": "Noctalia performance mode", "open-annotation-tool": "Open with annotation tool", "open-control-center": "Control center", "open-notification-history-enable-dnd": "Notification history", @@ -1957,6 +2046,11 @@ "resolution-label": "Resolution", "resolution-mode-label": "Mode", "solid-color-tooltip": "Solid color background", + "sort-date-asc": "Sort by oldest first", + "sort-date-desc": "Sort by newest first", + "sort-name-asc": "Sort by name (A-Z)", + "sort-name-desc": "Sort by name (Z-A)", + "sort-random": "Sort by random", "sorting-date-added": "Date added", "sorting-favorites": "Favorites", "sorting-label": "Sort by", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index e742d8480..a99a5f4e9 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Selecciona el color para el visualizador.", "color-name-label": "Color de relleno", + "height-description": "Ancho de componente personalizado.", "hide-when-idle-description": "Si está activado, el visualizador se oculta salvo que haya reproducción activa.", "hide-when-idle-label": "Ocultar cuando no se reproduce", "width-description": "Ancho del componente personalizado." @@ -61,7 +62,7 @@ "hide-if-not-detected-label": "Ocultar si no se detecta", "low-battery-threshold-label": "Umbral de advertencia de batería baja", "show-noctalia-performance-description": "Muestra el interruptor del modo de rendimiento Noctalia en el panel de batería.
Desactiva sombras y animaciones en Noctalia para reducir el uso de recursos.", - "show-noctalia-performance-label": "Mostrar interruptor de Noctalia Rendimiento", + "show-noctalia-performance-label": "Mostrar interruptor Noctalia Rendimiento", "show-power-profile-description": "Muestra la selección de perfil de energía en el panel de batería.", "show-power-profile-label": "Mostrar controles de perfil de energía" }, @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Mostrar las estadísticas como mini gráficos de barras en lugar de valores de texto. Evita el desplazamiento del diseño.", "compact-mode-label": "Modo compacto", + "cpu-cores-description": "Mostrar el uso de los núcleos de la CPU individualmente.", + "cpu-cores-label": "Núcleos de CPU", "cpu-frequency-description": "Mostrar la velocidad de reloj actual de la CPU en GHz.", "cpu-frequency-label": "Mostrar frecuencia de la CPU", "cpu-temperature-description": "Mostrar las lecturas de temperatura de CPU si están disponibles.", @@ -291,6 +294,8 @@ "focused-color-label": "Color del espacio de trabajo enfocado", "follow-focused-screen-description": "Mostrar espacios de trabajo de la pantalla enfocada, en lugar de la pantalla donde se encuentra la barra.", "follow-focused-screen-label": "Seguir pantalla enfocada", + "font-weight-description": "Establecer el peso visual para el texto dentro del espacio de trabajo.", + "font-weight-label": "Peso de fuente", "grouped-border-opacity-description": "Establecer el nivel de opacidad para los bordes del contenedor del espacio de trabajo.", "grouped-border-opacity-label": "Opacidad del borde", "hide-unoccupied-description": "No mostrar espacios de trabajo sin ventanas.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Todas las baterías (combinadas)", "battery-health": "Estado de la batería", "battery-level": "Nivel de batería", "capacity-level": "Capacidad: {level}", @@ -374,6 +380,7 @@ "add": "Añadir", "appearance": "Apariencia", "apply": "Aplicar", + "auto-connect": "Conexión automática", "automation": "Automatización", "available": "Disponible", "back": "Atrás", @@ -401,6 +408,7 @@ "contributors": "Colaboradores", "copied-to-clipboard": "Copiado al portapapeles", "countdown": "Cuenta regresiva", + "customize": "Personalizar", "date": "Fecha", "default": "Predeterminado", "delete": "Borrar", @@ -423,6 +431,11 @@ "execute": "Ejecutar", "faithful": "Fiel", "focus": "Enfoque", + "font-weight-bold": "Negrita", + "font-weight-light": "Fina", + "font-weight-medium": "Medio", + "font-weight-regular": "Regular", + "font-weight-semibold": "Seminegrita", "frequency": "Frecuencia", "gateway": "Puerta de enlace", "general": "General", @@ -445,6 +458,7 @@ "lock": "Bloquear", "logout": "Cerrar sesión", "look": "Aspecto", + "margins": "Márgenes", "media": "Medios", "media-player": "Reproductor multimedia", "memory": "Memoria", @@ -467,6 +481,7 @@ "panels": "Paneles", "password": "Contraseña", "pause": "Pausa", + "performance": "Rendimiento", "pin": "Anclar", "play": "Reproducir", "polling": "Sondeo", @@ -519,6 +534,7 @@ "unpin": "Desanclar", "update": "Actualizar", "upload": "Subir", + "userspace-reboot": "Reiniciar espacio de usuario", "version": "Versión", "vibrant": "Vibrante", "visualizer": "Visualizador", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Hazte patrocinador", "changelog": "Ver registro de cambios", + "changelog-on-startup": "Mostrar registro de cambios al actualizar", + "changelog-on-startup-desc": "Mostrar automáticamente el registro de cambios cuando Noctalia se actualice.", "contributors-description": "¡Un saludo a nuestro increíble colaborador número {count}!", "contributors-description-plural": "¡Un saludo a nuestros {count} increíbles colaboradores!", "copy-info": "Copiar información", "debug-disabled": "Modo de depuración deshabilitado", "debug-enabled": "Modo de depuración activado", "info-copied": "Información copiada al portapapeles", + "noctalia-available": "Disponible:", "noctalia-desc": "Un shell de escritorio elegante y minimalista cuidadosamente diseñado para Wayland, construido con Quickshell.", "noctalia-git-commit": "Commit de Git:", "noctalia-installed-version": "Versión instalada:", "noctalia-latest-version": "Última versión:", + "noctalia-qs-version": "Noctalia QS versión:", "noctalia-title": "Shell Noctalia", "privacy-policy": "Política de privacidad", "support": "Apóyanos", @@ -720,6 +740,7 @@ "supporters-desc": "¡Un enorme agradecimiento a nuestro increíble colaborador!", "supporters-desc-plural": "¡Muchísimas gracias a nuestros {count} increíbles seguidores!", "supporters-loading": "Cargando patrocinadores...", + "system-board": "Placa:", "system-cpu": "CPU:", "system-disk": "Disco:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "Densidad de la barra", "appearance-desc": "Personaliza la apariencia y posición de la barra.", "appearance-display-mode-description": "Elige cuando será visible la barra.", + "appearance-enable-exclusion-zone-inset-description": "Reduce la zona de exclusión en 1 píxel físico para que las ventanas a ras se extiendan perfectamente por debajo del borde de la Bar.", + "appearance-enable-exclusion-zone-inset-label": "Zona de exclusión interior", "appearance-floating-description": "Muestra la barra como una 'píldora' flotante.", "appearance-floating-label": "Barra flotante", "appearance-font-scale-description": "Ajustar la escala del tamaño de fuente para el texto mostrado en la barra.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Ocultar la barra y cerrar los paneles cuando la vista general del compositor esté activa.", "appearance-hide-on-overview-label": "Ocultar barra en la vista general", "appearance-margins-description": "Ajusta los márgenes alrededor de la barra flotante.", - "appearance-margins-horizontal": "Horizontal", - "appearance-margins-label": "Márgenes", - "appearance-margins-vertical": "Vertical", + "appearance-margins-horizontal": "Margen horizontal", + "appearance-margins-vertical": "Margen vertical", "appearance-outer-corners-description": "Muestra esquinas curvadas hacia afuera en la barra.", "appearance-outer-corners-label": "Esquinas exteriores", "appearance-position-description": "Elige dónde colocar la barra en la pantalla.", @@ -836,10 +858,30 @@ "appearance-show-outline-label": "Mostrar contornos de widgets", "appearance-type-description": "Elige el estilo de la barra: Simple, Flotante o Con marco.", "appearance-type-label": "Tipo de barra", - "appearance-use-separate-opacity-description": "Permitir usar un valor de opacidad separado para el fondo de la barra.", - "appearance-use-separate-opacity-label": "Usar opacidad de barra separada", + "appearance-use-separate-opacity-description": "Permitir usar un valor de opacidad distinto para el fondo de la barra.", + "appearance-use-separate-opacity-label": "La barra usa opacidad independiente", "appearance-widget-spacing-description": "Ajusta el espaciado entre cada widget en la barra.", "appearance-widget-spacing-label": "Espaciado de widgets", + "behavior-middle-click-command-description": "Comando a ejecutar al hacer clic central.", + "behavior-middle-click-command-label": "Comando de Clic Central", + "behavior-middle-click-command-placeholder": "niri msg action alternar vista general", + "behavior-middle-click-description": "Elige qué hace el clic central en las áreas vacías de la Bar.", + "behavior-middle-click-follow-mouse-description": "Abrir el panel de clic central seleccionado en la posición del cursor.", + "behavior-middle-click-follow-mouse-label": "Clic central sigue al ratón", + "behavior-middle-click-label": "Acción de clic central en la Barra", + "behavior-right-click-command-description": "Comando a ejecutar al hacer clic derecho.", + "behavior-right-click-command-label": "Comando de Clic Derecho", + "behavior-right-click-command-placeholder": "notify-send \"Clic derecho\"", + "behavior-right-click-description": "Elige qué hace el clic derecho en las áreas vacías de la barra.", + "behavior-right-click-follow-mouse-description": "Abre el panel de clic derecho seleccionado en la posición del cursor.", + "behavior-right-click-follow-mouse-label": "Clic derecho sigue al ratón", + "behavior-right-click-label": "Acción de clic derecho en la Barra", + "behavior-wheel-wrap-description": "Cuando está activado, el desplazamiento continúa del último elemento al primero.", + "behavior-wheel-wrap-label": "Circular", + "behavior-workspace-scroll-description": "Elige qué hace la rueda del ratón en las zonas vacías de la barra.", + "behavior-workspace-scroll-label": "Acción de la rueda del ratón en la barra", + "behavior-workspace-scroll-option-content": "Contenido", + "behavior-workspace-scroll-option-workspace": "Espacio de trabajo", "monitor-configure-widgets": "Configurar widgets", "monitor-override-settings": "Anular la configuración global", "monitor-override-settings-description": "Usar ajustes personalizados para este monitor.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Autenticación requerida", + "bluetooth-auto-connect-description": "Conectarse automáticamente a dispositivos emparejados de confianza cuando Bluetooth esté activado.", + "bluetooth-auto-connect-label": "Conectar automáticamente dispositivos emparejados", "bluetooth-devices-unnamed": "Los dispositivos sin nombre no se muestran.", "bluetooth-discoverable": "Este dispositivo es detectable como {hostName} mientras esta pestaña de ajustes esté abierta.", "bluetooth-rssi-polling-description": "Muestrea periódicamente el RSSI de los dispositivos conectados a través de bluetoothctl. Puede que no esté disponible para todos los dispositivos; usa recursos mínimos cuando está activado.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Personaliza qué controles aparecen en el centro de control y en qué orden.", "desc": "Configurar el posicionamiento y el comportamiento del panel del centro de control.", + "open-at-mouse-description": "Al hacer clic derecho en un área vacía de la Bar, abre el Centro de Control cerca del cursor. Cuando está deshabilitado, se usa la configuración de posición anterior en su lugar.", + "open-at-mouse-label": "Abrir en el cursor al hacer clic derecho", "position-description": "Elige dónde aparece el panel del centro de control cuando se abre.", "shortcuts-custom-button-command-description": "El comando a ejecutar cuando se hace clic en el botón.", "shortcuts-custom-button-enable-on-state-logic-description": "Habilita un segundo icono y un estado 'caliente' basado en un comando de comprobación.", @@ -952,7 +998,7 @@ "shortcuts-title": "Widgets de accesos directos", "system-monitor-disk-path-description": "Selecciona qué punto de montaje de disco debe monitorear la tarjeta del monitor del sistema en el centro de control.", "system-monitor-disk-path-label": "Ruta del disco del monitor del sistema", - "title": "Centro de Control" + "title": "Centro de control" }, "desktop-widgets": { "clock-enabled-description": "Mostrar un widget de reloj en el escritorio.", @@ -983,6 +1029,7 @@ "edit-mode-description": "Habilita el modo de edición para mover y cambiar la posición de los widgets del escritorio. Cuando está habilitado, los widgets muestran un contorno de arrastre y se pueden reposicionar.", "edit-mode-exit-button": "Salir del modo de edición", "edit-mode-grid-snap-label": "Ajustar a cuadrícula", + "edit-mode-grid-snap-scale-label": "Escala de ajuste a la cuadrícula", "edit-mode-label": "Modo de edición", "enabled-description": "Activar o desactivar los widgets de escritorio por completo.", "enabled-label": "Habilitar widgets de escritorio", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Ocultar autom.", "appearance-display-description": "Elige cómo se comporta el dock.", "appearance-display-exclusive": "Exclusivo", + "appearance-dock-indicator-description": "Mostrar un pequeño indicador cuando el dock está oculto.", + "appearance-dock-indicator-label": "Indicador del dock", "appearance-floating-distance-description": "Ajusta la distancia de flotación desde el borde de la pantalla.", "appearance-floating-distance-label": "Distancia de flotación del dock", - "appearance-frame-indicator-description": "Mostrar un pequeño indicador en el marco cuando el dock está oculto.", - "appearance-frame-indicator-label": "Indicador de muelle enmarcado", "appearance-group-apps-description": "Agrupar varias ventanas de la misma app en una entrada del Dock.", "appearance-group-apps-label": "Agrupar aplicaciones similares", "appearance-group-click-action-cycle": "Alternar ventanas", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Tamaño del dock", "appearance-inactive-indicators-description": "Mostrar indicadores en forma de pastillas para todas las aplicaciones, no solo para la que está activa actualmente.", "appearance-inactive-indicators-label": "Indicadores de ejecución", + "appearance-indicator-color-description": "Elige el color del indicador del dock oculto.", + "appearance-indicator-color-label": "Color del indicador", + "appearance-indicator-opacity-description": "Ajusta la opacidad del indicador del dock oculto.", + "appearance-indicator-opacity-label": "Opacidad del indicador", + "appearance-indicator-thickness-description": "Usa un indicador del dock oculto más grueso (6 px en lugar de 3 px).", + "appearance-indicator-thickness-label": "Indicador más grueso", "appearance-launcher-position-description": "Elige dónde aparece el icono del lanzador en el dock.", "appearance-launcher-position-end": "Fin", "appearance-launcher-position-label": "Posición del lanzador", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Automático", "language-select-description": "Selecciona el idioma utilizado en la interfaz de la aplicación.", "language-select-label": "Idioma de la aplicación", - "launch-setup-wizard": "Inicie el asistente de configuración", + "launch-setup-wizard": "Asistente de configuración", "profile-desc": "Edita los detalles de tu usuario y tu avatar.", "profile-picture-description": "Tu foto de perfil que aparece en toda la interfaz.", "profile-picture-label": "Foto de perfil", @@ -1164,20 +1217,20 @@ "screen-corners-solid-black-label": "Esquinas negras sólidas", "screen-corners-title": "Esquinas de la pantalla", "settings-copied": "Ajustes copiados al portapapeles", - "tab-basics": "Conceptos Básicos", - "tab-keybinds": "Atajos de Teclado" + "tab-basics": "Básico", + "tab-keybinds": "Atajos de teclado" }, "hooks": { "info-command-info-description": "• Los comandos se ejecutan a través de shell (sh -lc)
• Los comandos se ejecutan en segundo plano (desvinculados)
• Los botones de prueba se ejecutan con los valores actuales", - "info-parameters-description": "• Hook de fondo de pantalla: $1 = ruta del fondo de pantalla, $2 = nombre de la pantalla
• Hook de alternancia de tema: $1 = verdadero/falso (estado del modo oscuro)
• Hooks de bloqueo/desbloqueo de pantalla: Sin parámetros
• Hooks de modo de rendimiento: Sin parámetros
• Hook de sesión: $1 = acción (apagado/reinicio)", + "info-parameters-description": "• Hook de fondo de pantalla: $1 = ruta del fondo de pantalla, $2 = nombre de pantalla
• Hook de alternancia de tema: $1 = true/false (estado de Dark Mode)
• Hooks de bloqueo/desbloqueo de pantalla: $1 = lock/unlock (estado de bloqueo de pantalla)
• Hooks de modo de rendimiento: Sin parámetros
• Hook de sesión: $1 = action (shutdown/reboot)", "info-parameters-label": "Parámetros disponibles", "noctalia-started-description": "Comando para ejecutar cuando Noctalia ha terminado de cargarse.", - "noctalia-started-label": "Noctalia Iniciado", + "noctalia-started-label": "Noctalia iniciado", "noctalia-started-placeholder": "p. ej. notify-send 'Noctalia cargado'", - "performance-mode-disabled-description": "Comando a ejecutar cuando se deshabilita el modo de rendimiento de Noctalia.", + "performance-mode-disabled-description": "Comando a ejecutar cuando se deshabilita el modo de rendimiento Noctalia.", "performance-mode-disabled-label": "Modo de rendimiento deshabilitado", "performance-mode-disabled-placeholder": "p. ej. notify-send \"Rendimiento\" \"Modo deshabilitado\"", - "performance-mode-enabled-description": "Comando a ejecutar cuando se habilita el modo de rendimiento de Noctalia.", + "performance-mode-enabled-description": "Comando a ejecutar cuando se habilita el modo de rendimiento Noctalia.", "performance-mode-enabled-label": "Modo de rendimiento habilitado", "performance-mode-enabled-placeholder": "p. ej. notify-send \"Rendimiento\" \"Modo habilitado\"", "screen-lock-description": "Comando a ejecutar cuando se bloquea la pantalla.", @@ -1206,23 +1259,32 @@ "custom-description": "Ejecuta un comando de shell después de un período de inactividad.", "custom-entry-command": "Comando", "custom-entry-delete": "Eliminar", + "custom-entry-edit": "Editar Comando Personalizado", + "custom-entry-name": "Nombre", + "custom-entry-name-placeholder": "p. ej. Apagar luces", + "custom-entry-new": "Nuevo Comando Personalizado", "custom-entry-timeout": "Tiempo de inactividad", - "custom-label": "Comandos de Inactividad Personalizados", + "custom-entry-timeout-format": "{count} segundo", + "custom-entry-timeout-format-plural": "{count} segundos", + "custom-entry-unnamed": "Comando sin nombre", + "custom-label": "Comandos de inactividad personalizados", "enable-description": "Apagar la pantalla, bloquear o suspender automáticamente después de un período de inactividad.", "enable-label": "Activar gestión de inactividad", "fade-duration-description": "Segundos para la animación de fundido a negro antes de que se active cada acción. Cualquier movimiento del ratón cancela el fundido.", "fade-duration-label": "Duración del fundido", "lock-description": "Segundos de inactividad antes de que se active la pantalla de bloqueo.", "lock-label": "Bloquear pantalla", + "resume-command-label": "Reanudar comando", "screen-off-description": "Segundos de inactividad antes de que los monitores se apaguen.", "screen-off-label": "Apagar pantalla", - "status-description": "Tiempo de inactividad según lo informado por el Compositor.", - "status-label": "Tiempo de Inactividad", + "status-description": "Tiempo de inactividad según lo informado por el compositor.", + "status-label": "Tiempo de inactividad", "suspend-description": "Segundos de inactividad antes de que el sistema se suspenda.", "tab-behavior": "Comportamiento", "tab-custom": "Personalizado", - "timeouts-description": "Establece en 0 para deshabilitar una etapa. Los tiempos de espera se pausan mientras Keep Awake está activo.", - "timeouts-label": "Tiempos de Espera", + "timeouts-description": "Establece en 0 para deshabilitar una etapa. Los tiempos de espera se pausan mientras 'Mantener despierto' está activo.", + "timeouts-label": "Tiempos de espera", + "title": "Inactividad", "unavailable": "La monitorización nativa de inactividad no está disponible en este Compositor." }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "Estilo de Reloj", "compact-lockscreen-description": "Mostrar solo el campo de inicio de sesión y los controles del sistema, ocultando los widgets del clima y multimedia.", "compact-lockscreen-label": "Pantalla de bloqueo compacta", + "enable-lockscreen-media-controls-description": "Mostrar controles interactivos de reproducción multimedia en la pantalla de bloqueo.", + "enable-lockscreen-media-controls-label": "Controles Multimedia de la Pantalla de Bloqueo", "lock-on-suspend-description": "Bloquear la pantalla automáticamente al suspender el sistema.", "lock-on-suspend-label": "Bloquear al suspender", "lock-screen-animations-description": "Activar o desactivar las animaciones de la pantalla de bloqueo.", @@ -1438,7 +1502,7 @@ "duration-desc": "Cuánto tiempo permanece visible el OSD antes de ocultarse automáticamente.", "duration-title": "Tiempo de ocultación automática", "enabled-description": "Mostrar cambios de volumen y brillo en tiempo real.", - "enabled-label": "Activar visualización en pantalla", + "enabled-label": "Activar visualización en pantalla (OSD)", "events-desc": "Seleccione qué eventos activan la visualización en pantalla.", "general-desc": "Configurar la visibilidad y el comportamiento del OSD.", "location-description": "Dónde aparece la visualización en pantalla.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Mostrar sugerencias de atajos de teclado en las opciones de sesión.", "show-keybinds-label": "Mostrar atajos de teclado" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Mantener los widgets de escritorio visibles mientras el modo de rendimiento Noctalia esté habilitado.", + "noctalia-performance-disable-desktop-widgets-label": "Habilitar widgets de escritorio en modo de rendimiento", + "noctalia-performance-disable-wallpaper-description": "Mantén visibles los fondos de pantalla del escritorio, la vista general y la pantalla de bloqueo mientras el modo de rendimiento Noctalia esté activado.", + "noctalia-performance-disable-wallpaper-label": "Habilitar el renderizado de fondo de pantalla en modo de rendimiento", + "title": "Sistema" + }, "system-monitor": { "critical-color-label": "Color crítico", "custom-highlight-colors-title-label": "Colores de resaltado personalizados", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Intervalo de sondeo", "polling-section-description": "Configure la frecuencia con la que se actualiza cada métrica del sistema.", "polling-section-label": "Intervalos de sondeo", - "threshold-critical": "Umbral crítico", - "threshold-warning": "Umbral de advertencia", + "threshold-critical": "Crítico", + "threshold-warning": "Advertencia", "thresholds-section-description": "Ajusta los umbrales de advertencia/crítico y los intervalos de sondeo para cada métrica del sistema.", "use-custom-highlight-colors-description": "Cuando está desactivado, se usan los colores de resaltado predeterminados del tema.", "use-custom-highlight-colors-label": "Usar colores de resaltado personalizados", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Velocidad de animación", "animation-speed-reset": "Restablecer la velocidad de la animación", "appearance-desc": "Personaliza elementos visuales como texto emergente, bordes y sombras.", + "blur-behind-description": "Difumina el área detrás de las barras y los paneles usando el protocolo de desenfoque del compositor.", + "blur-behind-label": "Desenfoque de fondo", "box-border-description": "Muestra un contorno alrededor de las áreas de contenido.", "box-border-label": "Contorno del contenedor", "box-border-radius-description": "Ajusta la redondez de las esquinas de las secciones principales del diseño, como barras laterales, tarjetas y paneles de contenido.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Mantener paneles y barra en la parte superior", "scaling-description": "Cambia el tamaño de la interfaz de usuario general, excluyendo la barra.", "scaling-label": "Escalado de la interfaz", + "scrollbar-always-visible-description": "Mantener las barras de desplazamiento visibles siempre que el contenido sea desplazable, en lugar de mostrarlas solo al pasar el ratón.", + "scrollbar-always-visible-label": "Mostrar siempre barras de desplazamiento", "settings-panel-header": "Panel de Configuración", "settings-panel-mode-description": "Elegir diseño de configuración (puede requerir reapertura).", "settings-panel-mode-label": "Modo del panel de configuración", @@ -1619,7 +1694,9 @@ "shadows-label": "Sombras paralelas", "title": "Interfaz de usuario", "tooltips-description": "Activar o desactivar los avisos emergentes en toda la interfaz.", - "tooltips-label": "Mostrar textos emergentes" + "tooltips-label": "Mostrar textos emergentes", + "translucent-widgets-description": "Haz que los botones, pestañas y otros widgets dentro de los paneles sean semitransparentes.", + "translucent-widgets-label": "Widgets translúcidos" }, "wallpaper": { "automation-change-mode-alphabetical": "Alfabético", @@ -1742,6 +1819,7 @@ "select-prompt": "Selecciona un fondo abajo", "subheader": "Define el ambiente con un bonito fondo." }, + "welcome": "Bienvenido", "welcome-note": "Solo algunos ajustes básicos para empezar — las opciones al completo están en configuración", "welcome-subtitle": "Hagamos que tu escritorio sea único", "welcome-title": "¡Bienvenido a Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "Configuración de {widget}" }, "system-monitor": { + "core-usage": "Uso del núcleo {id}", "cpu-temp": "Temperatura de CPU", "cpu-usage": "Uso de CPU", "disk": "Disco", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Dirección copiada al portapapeles", + "auto-connect-disabled": "Conexión automática desactivada", + "auto-connect-enabled": "Conexión automática activada", + "auto-connecting": "Conectando a {count} dispositivo(s)...", "confirm-code": "Confirma el código {value} en el otro dispositivo.", "connect-failed": "Error al conectar con el dispositivo", "disconnect-failed": "Error al desconectar del dispositivo", @@ -1800,6 +1882,10 @@ "unavailable": "Historial del portapapeles no disponible", "unavailable-desc": "La aplicación 'cliphist' no está instalada. Por favor, instálala para usar las funciones de historial del portapapeles" }, + "custom-command-failed": { + "description": "El comando falló: {command}\\nCódigo de salida: {code}", + "title": "Comando personalizado falló" + }, "do-not-disturb": { "disabled": "'No molestar' desactivado", "disabled-desc": "Mostrando todas las notificaciones", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Añadir widget", + "auto-connect": "Alternar conexión automática para este dispositivo", + "bluetooth-auto-connect-off": "Conexión automática desactivada", + "bluetooth-auto-connect-on": "Conexión automática activada", "bluetooth-devices": "Dispositivos Bluetooth", "brightness-at": "Brillo: {brightness}%", "click-to-start-recording": "Grabadora de pantalla (iniciar grabación)", @@ -1951,6 +2040,11 @@ "resolution-label": "Resolución", "resolution-mode-label": "Modo", "solid-color-tooltip": "Fondo de color sólido", + "sort-date-asc": "Ordenar por más antiguos primero", + "sort-date-desc": "Ordenar por más reciente primero", + "sort-name-asc": "Ordenar por nombre (A-Z)", + "sort-name-desc": "Ordenar por nombre (Z-A)", + "sort-random": "Ordenar aleatoriamente", "sorting-date-added": "Fecha de adición", "sorting-favorites": "Favoritos", "sorting-label": "Ordenar por", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 9e58b84c7..3a221c91d 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Sélectionnez la couleur du visualiseur.", "color-name-label": "Couleur de remplissage", + "height-description": "Largeur de composant personnalisée.", "hide-when-idle-description": "Si activé, le visualiseur est masqué sauf lorsqu'un lecteur est en lecture.", "hide-when-idle-label": "Masquer lorsqu'aucun média n'est en lecture", "width-description": "Largeur personnalisée du composant." @@ -79,8 +80,8 @@ "horizontal-bar-description": "Astuce : Utilisez \\n pour créer un saut de ligne.", "horizontal-bar-label": "Barre horizontale", "preview": "Aperçu", - "tooltip-format-description": "Chaîne de format pour l'info-bulle affichée lors du survol de l'horloge. Laissez vide pour utiliser l'info-bulle par défaut.", - "tooltip-format-label": "Format de l'info-bulle", + "tooltip-format-description": "Chaîne de format pour l'infobulle affichée lors du survol de l'horloge. Laissez vide pour utiliser l'infobulle par défaut.", + "tooltip-format-label": "Format de l'infobulle", "use-custom-font-description": "Remplacez la police par défaut par une police personnalisée pour l'horloge.", "use-custom-font-label": "Utiliser une police personnalisée", "use-monospaced-font-description": "Lorsque cette option est activée, l'horloge utilisera la police monospace.", @@ -109,8 +110,8 @@ "dynamic-text": "Texte dynamique", "enable-colorization-description": "Activer la colorisation pour l'icône et le texte du bouton personnalisé, en appliquant les couleurs du thème.", "enable-colorization-label": "Activer la colorisation", - "general-tooltip-text-description": "Texte personnalisé à afficher dans l'info-bulle du bouton.", - "general-tooltip-text-label": "Texte d'info-bulle personnalisé", + "general-tooltip-text-description": "Texte personnalisé à afficher dans l'infobulle du bouton.", + "general-tooltip-text-label": "Texte d'infobulle personnalisé", "hide-mode-always-expanded": "Toujours étendu", "hide-mode-description": "Contrôle la visibilité du widget quand la commande n'a pas de sortie.", "hide-mode-expand-with-output": "Étendre quand il y a une sortie", @@ -136,12 +137,12 @@ "right-click-description": "Commande à exécuter quand le bouton est cliqué à droite.", "right-click-label": "Clic droit", "right-click-update-text": "Mettre à jour le texte affiché lors d'un clic droit", - "show-exec-tooltip-description": "Afficher les info-bulles avec les détails de la commande (clic gauche/droit/milieu, molette).", + "show-exec-tooltip-description": "Afficher les infobulles avec les détails de la commande (clic gauche/droit/milieu, molette).", "show-exec-tooltip-label": "Afficher les infobulles de commande", "show-icon-description": "Bascule la visibilité de l'icône du widget.", "show-icon-label": "Afficher l'icône", - "show-text-tooltip-description": "Afficher les info-bulles avec la sortie de la commande texte.", - "show-text-tooltip-label": "Afficher les info-bulles de texte dynamiques", + "show-text-tooltip-description": "Afficher les infobulles avec la sortie de la commande texte.", + "show-text-tooltip-label": "Afficher les infobulles de texte dynamiques", "text-stream-description": "Les lignes diffusées depuis la commande seront affichées sous forme de texte sur le bouton.", "text-stream-label": "Flux", "wheel-description": "Commande à exécuter lorsque la molette est utilisée.
Utilisez $delta pour le delta de la molette dans la commande.", @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Afficher les statistiques sous forme de mini-graphiques à barres au lieu de valeurs textuelles. Empêche les décalages de mise en page.", "compact-mode-label": "Mode compact", + "cpu-cores-description": "Afficher l'utilisation des cœurs du CPU individuellement.", + "cpu-cores-label": "Cœurs du CPU", "cpu-frequency-description": "Afficher la vitesse d'horloge actuelle du CPU en GHz.", "cpu-frequency-label": "Afficher la fréquence du CPU", "cpu-temperature-description": "Afficher les lectures de température du CPU si disponibles.", @@ -283,7 +286,7 @@ "workspace": { "character-count-description": "Nombre de caractères à afficher des noms d'espaces de travail (1-10).", "character-count-label": "Nombre de caractères", - "empty-color-description": "Définir la couleur d'arrière-plan pour les Workspaces vides.", + "empty-color-description": "Définir la couleur d'arrière-plan pour les espaces de travail vides.", "empty-color-label": "Couleur d'espace de travail vide", "enable-scrollwheel-description": "Basculer entre les espaces de travail avec la molette de la souris.", "enable-scrollwheel-label": "Faites défiler pour changer d'espace de travail", @@ -291,13 +294,15 @@ "focused-color-label": "Couleur de l'espace de travail sélectionné", "follow-focused-screen-description": "Afficher les espaces de travail de l'écran actuellement ciblé, plutôt que de l'écran où se trouve la barre.", "follow-focused-screen-label": "Suivre l'écran ciblé", + "font-weight-description": "Définir le poids visuel du texte dans l'espace de travail.", + "font-weight-label": "Graisse de la police", "grouped-border-opacity-description": "Définir le niveau d'opacité des bordures des conteneurs d'espace de travail.", "grouped-border-opacity-label": "Opacité de la bordure", "hide-unoccupied-description": "Ne pas afficher les espaces de travail sans fenêtres.", "hide-unoccupied-label": "Masquer les inoccupés", "label-mode-description": "Choisir comment les étiquettes d'espace de travail sont affichées.", "label-mode-label": "Mode d'étiquette", - "occupied-color-description": "Définir la couleur d'arrière-plan pour les Workspaces occupés.", + "occupied-color-description": "Définir la couleur d'arrière-plan pour les espaces de travail occupés.", "occupied-color-label": "Couleur de l'espace de travail occupé", "pill-size-description": "Ajustez la taille des pilules d'espace de travail (50%-100%).", "pill-size-label": "Taille de la pilule", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Toutes les batteries (combinées)", "battery-health": "État de la batterie", "battery-level": "Niveau de batterie", "capacity-level": "Capacité : {level}", @@ -374,6 +380,7 @@ "add": "Ajouter", "appearance": "Apparence", "apply": "Appliquer", + "auto-connect": "Connexion automatique", "automation": "Automatisation", "available": "Disponible", "back": "Retour", @@ -401,6 +408,7 @@ "contributors": "Contributeurs", "copied-to-clipboard": "Copié dans le presse-papier", "countdown": "Compte à rebours", + "customize": "Personnaliser", "date": "Date", "default": "Par défaut", "delete": "Supprimer", @@ -423,6 +431,11 @@ "execute": "Exécuter", "faithful": "Fidèle", "focus": "Concentration", + "font-weight-bold": "Gras", + "font-weight-light": "Légère", + "font-weight-medium": "Moyen", + "font-weight-regular": "Régulier", + "font-weight-semibold": "Semi-gras", "frequency": "Fréquence", "gateway": "Passerelle", "general": "Général", @@ -445,6 +458,7 @@ "lock": "Verrouiller", "logout": "Déconnexion", "look": "Apparence", + "margins": "Marges", "media": "Médias", "media-player": "Lecteur multimédia", "memory": "Mémoire", @@ -467,6 +481,7 @@ "panels": "Panneaux", "password": "Mot de passe", "pause": "Pause", + "performance": "Performances", "pin": "Épingle", "play": "Lire", "polling": "Interrogation", @@ -519,6 +534,7 @@ "unpin": "Détacher", "update": "Mise à jour", "upload": "Téléverser", + "userspace-reboot": "Redémarrer l'espace utilisateur", "version": "Version", "vibrant": "Vibrant", "visualizer": "Visualiseur", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Devenir un soutien", "changelog": "Afficher le journal des modifications", + "changelog-on-startup": "Afficher le journal des modifications lors de la mise à jour", + "changelog-on-startup-desc": "Afficher automatiquement le journal des modifications lors de la mise à jour de Noctalia.", "contributors-description": "Un grand merci à notre {count} super contributeur !", "contributors-description-plural": "Un grand merci à nos {count} super contributeurs !", "copy-info": "Copier les informations", "debug-disabled": "Mode débogage désactivé", "debug-enabled": "Mode débogage activé", "info-copied": "Info copiée dans le presse-papiers", + "noctalia-available": "Disponible :", "noctalia-desc": "Un shell de bureau élégant et minimaliste, conçu avec soin pour Wayland et bâti avec Quickshell.", "noctalia-git-commit": "Commit Git :", "noctalia-installed-version": "Version installée :", "noctalia-latest-version": "Dernière version :", + "noctalia-qs-version": "Noctalia QS version :", "noctalia-title": "Shell Noctalia", "privacy-policy": "Politique de confidentialité", "support": "Soutenez-nous", @@ -720,6 +740,7 @@ "supporters-desc": "Un immense merci à notre incroyable soutien !", "supporters-desc-plural": "Un grand merci à nos {count} incroyables supporters !", "supporters-loading": "Chargement des contributeurs...", + "system-board": "Carte mère :", "system-cpu": "Processeur :", "system-disk": "Disque :", "system-gpu": "GPU :", @@ -810,6 +831,8 @@ "appearance-density-label": "Densité de la barre", "appearance-desc": "Personnalisez l'apparence et la position de la barre.", "appearance-display-mode-description": "Choisir quand la barre est visible.", + "appearance-enable-exclusion-zone-inset-description": "Réduisez la zone d'exclusion de 1 pixel physique afin que les fenêtres affleurantes se fondent parfaitement sous le bord de la Bar.", + "appearance-enable-exclusion-zone-inset-label": "Zone d'exclusion en retrait", "appearance-floating-description": "Afficher la barre sous forme de 'pilule' flottante.", "appearance-floating-label": "Barre flottante", "appearance-font-scale-description": "Ajuster l'échelle de la taille de police pour le texte affiché dans la barre.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Masquer la barre et fermer les panneaux lorsque l'aperçu du compositeur est actif.", "appearance-hide-on-overview-label": "Masquer la barre dans l'aperçu", "appearance-margins-description": "Ajustez les marges autour de la barre flottante.", - "appearance-margins-horizontal": "Horizontale", - "appearance-margins-label": "Marges", - "appearance-margins-vertical": "Verticale", + "appearance-margins-horizontal": "Marge horizontale", + "appearance-margins-vertical": "Marge verticale", "appearance-outer-corners-description": "Afficher des coins incurvés vers l'extérieur sur la barre.", "appearance-outer-corners-label": "Coins extérieurs", "appearance-position-description": "Choisissez où placer la barre sur l'écran.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Utiliser l'opacité des barres séparément", "appearance-widget-spacing-description": "Ajustez l'espacement entre chaque widget dans la barre.", "appearance-widget-spacing-label": "Espacement des widgets", + "behavior-middle-click-command-description": "Commande à exécuter au clic du milieu.", + "behavior-middle-click-command-label": "Commande du clic du Milieu", + "behavior-middle-click-command-placeholder": "niri msg action basculer l'aperçu", + "behavior-middle-click-description": "Choisissez ce que fait le clic du milieu sur les zones vides de la Barre.", + "behavior-middle-click-follow-mouse-description": "Ouvrir le panneau de clic central sélectionné à la position du curseur.", + "behavior-middle-click-follow-mouse-label": "Clic molette suit la souris", + "behavior-middle-click-label": "Action du clic du milieu sur la Barre", + "behavior-right-click-command-description": "Commande à exécuter au clic droit.", + "behavior-right-click-command-label": "Commande du Clic Droit", + "behavior-right-click-command-placeholder": "notify-send \"Clic droit\"", + "behavior-right-click-description": "Choisissez ce que fait un clic droit sur les zones vides de la barre.", + "behavior-right-click-follow-mouse-description": "Ouvre le panneau de clic droit sélectionné à la position du curseur.", + "behavior-right-click-follow-mouse-label": "Clic droit suit la souris", + "behavior-right-click-label": "Action du clic droit sur la Barre", + "behavior-wheel-wrap-description": "Lorsque activé, le défilement continue du dernier élément au premier.", + "behavior-wheel-wrap-label": "Boucler", + "behavior-workspace-scroll-description": "Choisissez ce que fait la molette de la souris sur les zones vides de la barre.", + "behavior-workspace-scroll-label": "Action de la molette de la souris de la Barre", + "behavior-workspace-scroll-option-content": "Contenu", + "behavior-workspace-scroll-option-workspace": "Espace de travail", "monitor-configure-widgets": "Configurer les widgets", "monitor-override-settings": "Remplacer les paramètres globaux", "monitor-override-settings-description": "Utiliser des paramètres personnalisés pour ce moniteur.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Authentification requise", + "bluetooth-auto-connect-description": "Se connecter automatiquement aux appareils appairés de confiance lorsque le Bluetooth est activé.", + "bluetooth-auto-connect-label": "Connecter automatiquement les appareils appairés", "bluetooth-devices-unnamed": "Les appareils sans nom ne sont pas affichés.", "bluetooth-discoverable": "Cet appareil est détectable en tant que {hostName} tant que cet onglet de paramètres est ouvert.", "bluetooth-rssi-polling-description": "Échantillonne périodiquement le RSSI des appareils connectés via bluetoothctl. Peut ne pas être disponible pour tous les appareils ; utilise des ressources minimales lorsqu'il est activé.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Personnalisez les commandes qui apparaissent dans le centre de contrôle et leur ordre d'affichage.", "desc": "Configurer le positionnement et le comportement du panneau du centre de contrôle.", + "open-at-mouse-description": "Lors d'un clic droit sur une zone vide de la Bar, ouvre le Centre de Contrôle près du curseur. Si désactivé, le réglage de position ci-dessus est utilisé à la place.", + "open-at-mouse-label": "Ouvrir au niveau du curseur lors d'un clic droit", "position-description": "Choisissez où le panneau du centre de contrôle apparaît lorsqu'il est ouvert.", "shortcuts-custom-button-command-description": "La commande à exécuter lorsque le bouton est cliqué.", "shortcuts-custom-button-enable-on-state-logic-description": "Active une deuxième icône et un état 'chaud' basés sur une commande de vérification.", @@ -947,8 +993,8 @@ "shortcuts-custom-button-state-checks-command": "Commande à exécuter pour cette vérification d'état", "shortcuts-custom-button-state-checks-label": "Vérifications d'état", "shortcuts-custom-button-state-checks-remove": "Supprimer", - "shortcuts-custom-button-tooltip-description": "L'info-bulle à afficher lors du survol du bouton.", - "shortcuts-custom-button-tooltip-label": "Info-bulle", + "shortcuts-custom-button-tooltip-description": "L'infobulle à afficher lors du survol du bouton.", + "shortcuts-custom-button-tooltip-label": "Infobulle", "shortcuts-title": "Widgets de raccourcis", "system-monitor-disk-path-description": "Sélectionnez le point de montage du disque que la carte du moniteur système dans le centre de contrôle doit surveiller.", "system-monitor-disk-path-label": "Chemin du disque du moniteur système", @@ -983,6 +1029,7 @@ "edit-mode-description": "Activer le mode édition pour déplacer et repositionner les widgets du bureau. Une fois activé, les widgets affichent un contour de glissement et peuvent être repositionnés.", "edit-mode-exit-button": "Quitter le mode édition", "edit-mode-grid-snap-label": "Aligner sur la grille", + "edit-mode-grid-snap-scale-label": "Échelle d'accrochage à la grille", "edit-mode-label": "Mode édition", "enabled-description": "Activer ou désactiver complètement les widgets du bureau.", "enabled-label": "Activer les widgets de bureau", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Masquer automatiquement", "appearance-display-description": "Choisissez comment le dock se comporte.", "appearance-display-exclusive": "Exclusif", + "appearance-dock-indicator-description": "Afficher un petit indicateur lorsque le dock est masqué.", + "appearance-dock-indicator-label": "Indicateur du dock", "appearance-floating-distance-description": "Ajustez la distance de flottaison par rapport au bord de l'écran.", "appearance-floating-distance-label": "Distance de flottaison du dock", - "appearance-frame-indicator-description": "Afficher un petit indicateur sur le cadre lorsque le dock est masqué.", - "appearance-frame-indicator-label": "Indicateur de dock encadré", "appearance-group-apps-description": "Regrouper plusieurs fenêtres de la même app en une seule entrée du Dock.", "appearance-group-apps-label": "Regrouper les mêmes applications", "appearance-group-click-action-cycle": "Basculer entre les fenêtres", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Taille du dock", "appearance-inactive-indicators-description": "Afficher les pastilles d'indicateur pour toutes les applications, pas seulement celle qui est active.", "appearance-inactive-indicators-label": "Indicateurs de fonctionnement", + "appearance-indicator-color-description": "Choisissez la couleur de l'indicateur du dock masqué.", + "appearance-indicator-color-label": "Couleur de l'indicateur", + "appearance-indicator-opacity-description": "Ajustez l'opacité de l'indicateur du dock masqué.", + "appearance-indicator-opacity-label": "Opacité de l'indicateur", + "appearance-indicator-thickness-description": "Activez un indicateur du dock masqué plus épais (6 px au lieu de 3 px).", + "appearance-indicator-thickness-label": "Indicateur plus épais", "appearance-launcher-position-description": "Choisissez où l'icône du lanceur apparaît dans le dock.", "appearance-launcher-position-end": "Fin", "appearance-launcher-position-label": "Position du lanceur", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Automatique", "language-select-description": "Sélectionnez la langue utilisée dans l'interface de l'application.", "language-select-label": "Langue de l'application", - "launch-setup-wizard": "Lancer l'assistant d'installation", + "launch-setup-wizard": "Assistant de configuration", "profile-desc": "Modifiez vos informations utilisateur et votre avatar.", "profile-picture-description": "Votre photo de profil qui apparaît dans toute l'interface.", "profile-picture-label": "Photo de profil", @@ -1165,17 +1218,17 @@ "screen-corners-title": "Coins de l'écran", "settings-copied": "Paramètres copiés dans le presse-papier", "tab-basics": "Bases", - "tab-keybinds": "Raccourcis Clavier" + "tab-keybinds": "Raccourcis clavier" }, "hooks": { "info-command-info-description": "• Les commandes sont exécutées via le shell (sh -lc)
• Les commandes s'exécutent en arrière-plan (détachées)
• Les boutons de test s'exécutent avec les valeurs actuelles", - "info-parameters-description": "• Crochet de fond d'écran : $1 = chemin d'accès au fond d'écran, $2 = nom de l'écran
• Crochet de bascule de thème : $1 = vrai/faux (état du mode sombre)
• Crochets de verrouillage/déverrouillage d'écran : Aucun paramètre
• Crochets de mode de performance : Aucun paramètre
• Crochet de session : $1 = action (arrêt/redémarrage)", + "info-parameters-description": "• Hook de fond d'écran : $1 = chemin du fond d'écran, $2 = nom de l'écran
• Hook de bascule de thème : $1 = true/false (état du Dark Mode)
• Hooks de verrouillage/déverrouillage d'écran : $1 = lock/unlock (état de verrouillage de l'écran)
• Hooks de mode performance : Aucun paramètre
• Hook de session : $1 = action (shutdown/reboot)", "info-parameters-label": "Paramètres disponibles", "noctalia-started-description": "Commande à exécuter lorsque Noctalia a fini de charger.", "noctalia-started-label": "Noctalia démarré", "noctalia-started-placeholder": "ex : notify-send 'Noctalia chargé'", "performance-mode-disabled-description": "Commande à exécuter lorsque le mode performance Noctalia est désactivé.", - "performance-mode-disabled-label": "mode performance désactivé", + "performance-mode-disabled-label": "Mode performance désactivé", "performance-mode-disabled-placeholder": "ex : notify-send \"Performance\" \"Mode désactivé\"", "performance-mode-enabled-description": "Commande à exécuter lorsque le mode performance Noctalia est activé.", "performance-mode-enabled-label": "Mode performance activé", @@ -1206,24 +1259,33 @@ "custom-description": "Exécute une commande shell après une période d'inactivité.", "custom-entry-command": "Commande", "custom-entry-delete": "Supprimer", + "custom-entry-edit": "Modifier la Commande Personnalisée", + "custom-entry-name": "Nom", + "custom-entry-name-placeholder": "ex : Éteindre les lumières", + "custom-entry-new": "Nouvelle Commande Personnalisée", "custom-entry-timeout": "Temps d'inactivité", - "custom-label": "Commandes d'Inactivité Personnalisées", + "custom-entry-timeout-format": "{count} seconde", + "custom-entry-timeout-format-plural": "{count} secondes", + "custom-entry-unnamed": "Commande sans nom", + "custom-label": "Commandes d'inactivité personnalisées", "enable-description": "Éteindre l'écran, verrouiller ou suspendre automatiquement après une période d'inactivité.", "enable-label": "Activer la gestion de l'inactivité", "fade-duration-description": "Secondes pour l'animation de fondu au noir avant que chaque action ne se déclenche. Tout mouvement de souris annule le fondu.", "fade-duration-label": "Durée du fondu", "lock-description": "Secondes d'inactivité avant l'activation de l'écran de verrouillage.", "lock-label": "Verrouiller l'écran", + "resume-command-label": "Reprendre la commande", "screen-off-description": "Secondes d'inactivité avant que les moniteurs ne s'éteignent.", "screen-off-label": "Éteindre l'écran", - "status-description": "Temps d'inactivité tel que rapporté par le Compositor.", - "status-label": "Temps d'Inactivité", + "status-description": "Temps d'inactivité tel que rapporté par le compositeur.", + "status-label": "Temps d'inactivité", "suspend-description": "Secondes d'inactivité avant que le système ne se suspende.", "tab-behavior": "Comportement", "tab-custom": "Personnalisé", - "timeouts-description": "Réglez sur 0 pour désactiver une étape. Les délais d'attente sont mis en pause tant que Keep Awake est actif.", - "timeouts-label": "Délais d'Expiration", - "unavailable": "La surveillance native de l'inactivité n'est pas disponible sur ce Compositor." + "timeouts-description": "Réglez sur 0 pour désactiver une étape. Les délais d'attente sont mis en pause tant que 'Rester éveillé' est actif.", + "timeouts-label": "Délais d'expiration", + "title": "Inactivité", + "unavailable": "La surveillance native de l'inactivité n'est pas disponible sur ce compositeur." }, "indicator": { "default-value": "Par défaut : {value}", @@ -1334,6 +1396,8 @@ "clock-style-label": "Style de l'Horloge", "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.", "compact-lockscreen-label": "Écran de verrouillage compact", + "enable-lockscreen-media-controls-description": "Afficher les commandes interactives de lecture multimédia sur l'écran de verrouillage.", + "enable-lockscreen-media-controls-label": "Commandes Multimédia de l'Écran de Verrouillage", "lock-on-suspend-description": "Verrouiller automatiquement l'écran lors de la mise en veille du système.", "lock-on-suspend-label": "Verrouiller à la suspension", "lock-screen-animations-description": "Activer ou désactiver les animations de l'écran de verrouillage.", @@ -1438,7 +1502,7 @@ "duration-desc": "Temps d'affichage avant masquage automatique de l'OSD.", "duration-title": "Délai d'auto‑masquage", "enabled-description": "Afficher en temps réel les changements de volume et de luminosité.", - "enabled-label": "Activer l'affichage à l'écran", + "enabled-label": "Activer l'affichage à l'écran (OSD)", "events-desc": "Sélectionnez les événements qui déclenchent l'affichage à l'écran.", "general-desc": "Configurer la visibilité et le comportement de l'OSD.", "location-description": "Emplacement des affichages à l'écran.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Afficher les indices de raccourcis clavier sur les options de session.", "show-keybinds-label": "Afficher les raccourcis clavier" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Garder les widgets de bureau visibles tant que le mode performance Noctalia est activé.", + "noctalia-performance-disable-desktop-widgets-label": "Activer les widgets de bureau en mode performance", + "noctalia-performance-disable-wallpaper-description": "Gardez les fonds d'écran du bureau, de l'aperçu et de l'écran de verrouillage visibles lorsque le mode performance Noctalia est activé.", + "noctalia-performance-disable-wallpaper-label": "Activer le rendu du fond d'écran en mode performance", + "title": "Système" + }, "system-monitor": { "critical-color-label": "Couleur critique", "custom-highlight-colors-title-label": "Couleurs de surbrillance personnalisées", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Intervalle d'interrogation", "polling-section-description": "Configurer la fréquence de mise à jour de chaque métrique système.", "polling-section-label": "Intervalles d'interrogation", - "threshold-critical": "Seuil critique", - "threshold-warning": "Seuil d'alerte", + "threshold-critical": "Critique", + "threshold-warning": "Avertissement", "thresholds-section-description": "Ajustez les seuils d’avertissement/critiques et les intervalles d’interrogation pour chaque métrique système.", "use-custom-highlight-colors-description": "Lorsque cette option est désactivée, les couleurs de surbrillance par défaut du thème sont utilisées.", "use-custom-highlight-colors-label": "Utiliser des couleurs de surlignage personnalisées", @@ -1588,7 +1659,9 @@ "animation-speed-description": "Ajuster la vitesse globale de l'animation.", "animation-speed-label": "Vitesse d'animation", "animation-speed-reset": "Réinitialiser la vitesse de l'animation", - "appearance-desc": "Personnaliser les éléments visuels tels que les info-bulles, les bordures et les ombres.", + "appearance-desc": "Personnaliser les éléments visuels tels que les infobulles, les bordures et les ombres.", + "blur-behind-description": "Floute la zone derrière les barres et les panneaux en utilisant le protocole de flou du compositeur.", + "blur-behind-label": "Flou d'arrière-plan", "box-border-description": "Affiche un contour autour des zones de contenu.", "box-border-label": "Contour du conteneur", "box-border-radius-description": "Ajuste de l'arrondi des coins des principales sections de la mise en page, telles que les barres latérales, les cartes et les panneaux de contenu.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Garder les panneaux et la barre en haut", "scaling-description": "Modifie la taille de l'interface utilisateur générale, à l'exception de la barre.", "scaling-label": "Mise à l'échelle de l'interface", + "scrollbar-always-visible-description": "Garder les barres de défilement visibles chaque fois que le contenu est défilable, au lieu de les afficher uniquement au survol.", + "scrollbar-always-visible-label": "Toujours afficher les barres de défilement", "settings-panel-header": "Panneau des Paramètres", "settings-panel-mode-description": "Choisir la disposition des paramètres (peut nécessiter une réouverture).", "settings-panel-mode-label": "Mode du panneau des paramètres", @@ -1618,8 +1693,10 @@ "shadows-direction-label": "Direction de l’ombre", "shadows-label": "Ombres portées", "title": "Interface utilisateur", - "tooltips-description": "Activer ou désactiver les info-bulles dans toute l'interface.", - "tooltips-label": "Afficher les infobulles" + "tooltips-description": "Activer ou désactiver les infobulles dans toute l'interface.", + "tooltips-label": "Afficher les infobulles", + "translucent-widgets-description": "Rendre les boutons, onglets et autres widgets à l'intérieur des panneaux semi-transparents.", + "translucent-widgets-label": "Widgets translucides" }, "wallpaper": { "automation-change-mode-alphabetical": "Alphabétique", @@ -1725,7 +1802,7 @@ }, "skip-setup": "Ignorer la configuration", "telemetry-wizard-done": "Bien reçu !", - "telemetry-wizard-note": "Vous avez le contrôle — activez ou désactivez cette option à tout moment dans les paramètres", + "telemetry-wizard-note": "Vous avez le contrôle — activez ou désactivez cette option à tout moment dans les paramètres", "telemetry-wizard-subtitle": "Nous avons ajouté des analyses anonymes pour aider à améliorer Noctalia", "telemetry-wizard-title": "Mise à jour de la confidentialité", "wallpaper": { @@ -1742,6 +1819,7 @@ "select-prompt": "Sélectionnez un fond ci-dessous", "subheader": "Définissez l'ambiance avec un joli fond." }, + "welcome": "Bienvenue", "welcome-note": "Quelques réglages de base pour démarrer — toutes les options sont dans paramètres", "welcome-subtitle": "Personnalisons votre bureau pour qu'il soit unique", "welcome-title": "Bienvenue dans Noctalia !" @@ -1761,6 +1839,7 @@ "widget-settings-title": "Paramètres de {widget}" }, "system-monitor": { + "core-usage": "Utilisation du cœur {id}", "cpu-temp": "Température du processeur", "cpu-usage": "Utilisation du processeur", "disk": "Disque", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Adresse copiée dans le presse-papiers", + "auto-connect-disabled": "Connexion automatique désactivée", + "auto-connect-enabled": "Connexion automatique activée", + "auto-connecting": "Connexion à {count} appareil(s)...", "confirm-code": "Confirmer le code {value} sur l'autre appareil.", "connect-failed": "Échec de la connexion à l’appareil", "disconnect-failed": "Échec de la déconnexion de l’appareil", @@ -1800,6 +1882,10 @@ "unavailable": "Historique du presse-papiers indisponible", "unavailable-desc": "L'application 'cliphist' n'est pas installée. Veuillez l'installer pour utiliser les fonctionnalités d'historique du presse-papiers" }, + "custom-command-failed": { + "description": "La commande a échoué : {command}\\nCode de sortie : {code}", + "title": "La commande personnalisée a échoué" + }, "do-not-disturb": { "disabled": "'Ne pas déranger' désactivé", "disabled-desc": "Affichage de toutes les notifications", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Ajouter un widget", + "auto-connect": "Activer/désactiver la connexion automatique pour cet appareil", + "bluetooth-auto-connect-off": "Connexion automatique désactivée", + "bluetooth-auto-connect-on": "Connexion automatique activée", "bluetooth-devices": "Appareils Bluetooth", "brightness-at": "Luminosité : {brightness}%", "click-to-start-recording": "Enregistreur d'écran (démarrer l'enregistrement)", @@ -1951,6 +2040,11 @@ "resolution-label": "Résolution", "resolution-mode-label": "Mode", "solid-color-tooltip": "Fond uni", + "sort-date-asc": "Trier du plus ancien au plus récent", + "sort-date-desc": "Trier du plus récent au plus ancien", + "sort-name-asc": "Trier par nom (A-Z)", + "sort-name-desc": "Trier par nom (Z-A)", + "sort-random": "Trier aléatoirement", "sorting-date-added": "Date d'ajout", "sorting-favorites": "Favoris", "sorting-label": "Trier par", diff --git a/Assets/Translations/hu.json b/Assets/Translations/hu.json index 36af3eac5..aa24195fc 100644 --- a/Assets/Translations/hu.json +++ b/Assets/Translations/hu.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "A vizualizátor színe.", "color-name-label": "Kitöltési szín", + "height-description": "Egyéni komponensszélesség.", "hide-when-idle-description": "Ha engedélyezve van, a vizualizátor el van rejtve, kivéve, ha egy lejátszó aktívan játszik.", "hide-when-idle-label": "Elrejtés, ha nincs média lejátszva", "width-description": "Egyéni komponens szélessége." @@ -61,7 +62,7 @@ "hide-if-not-detected-label": "Elrejtés, ha nincs észlelve", "low-battery-threshold-label": "Alacsony akkumulátor töltöttségi figyelmeztetési küszöb", "show-noctalia-performance-description": "A Noctalia teljesítménymód kapcsoló megjelenítése az akkumulátor panelen belül.
Letiltja az árnyékokat és animációkat a Noctaliaban az erőforrásfelhasználás csökkentése érdekében.", - "show-noctalia-performance-label": "Noctalia teljesítménymód kapcsoló", + "show-noctalia-performance-label": "Noctalia Teljesítmény kapcsoló megjelenítése", "show-power-profile-description": "Az energiaprofil kiválasztásának megjelenítése az akkumulátor panelen belül.", "show-power-profile-label": "Energiaprofil vezérlők" }, @@ -70,7 +71,7 @@ "apply-all-label": "Görgetési változások alkalmazása minden monitorra" }, "clock": { - "clock-display-description": "Testreszabhatja az óra kijelzőjét a lenti listában szereplő tokenek hozzáadásával. A 12 órás formátum használatához tartalmaznia kell az 'AP' tokent.", + "clock-display-description": "Testreszabhatod az óra kijelzőjét a lenti listában szereplő tokenek hozzáadásával. A 12 órás formátum használatához tartalmaznia kell az 'AP' tokent.", "clock-display-label": "Óra kijelző", "custom-font-description": "Egyéni betűtípus az óra kijelzőjéhez.", "custom-font-label": "Egyéni betűtípus", @@ -102,7 +103,7 @@ "collapse-condition-description": "Ha a kimeneti szöveg megegyezik ezzel az értékkel, a gomb összecsukódik.", "collapse-condition-label": "Összecsukási feltétel", "color-selection-description": "Témák színeinek alkalmazása az ikonra és a szövegre.", - "default-tooltip": "Egyéni gomb, konfiguráld a beállításokban", + "default-tooltip": "Egyéni gomb, konfigurálás a beállításokban", "display-command-output-description": "Egy parancs, amelyet rendszeres időközönként futtatni szeretnél. A kimenetének első sora szövegként jelenik meg.", "display-command-output-label": "Parancs kimenetének megjelenítése", "display-command-output-stream-description": "Egy parancs, amelyet folyamatosan futtatni szeretnél.", @@ -199,7 +200,7 @@ "hide-widget-when-zero-unread-label": "Ikon elrejtése, amikor nincs olvasatlan értesítés", "show-unread-badge-description": "Jelenítsen meg egy jelvényt, amely mutatja az olvasatlan értesítések számát.", "show-unread-badge-label": "Olvasatlan jelvény megjelenítése", - "unread-badge-color-description": "Válaszd ki az olvasatlan értesítési jelvény színét.", + "unread-badge-color-description": "Az olvasatlan értesítési jelvény színe.", "unread-badge-color-label": "Olvasatlan jelvény színe" }, "section-editor": { @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "A statisztikákat mini sávdiagramokként jelenítse meg a szöveges értékek helyett. Megakadályozza az elrendezés eltolódását.", "compact-mode-label": "Kompakt mód", + "cpu-cores-description": "A CPU magok használatának külön-külön megjelenítése.", + "cpu-cores-label": "CPU magok", "cpu-frequency-description": "Az aktuális CPU-órajel megjelenítése GHz-ben.", "cpu-frequency-label": "CPU-frekvencia megjelenítése", "cpu-temperature-description": "CPU hőmérséklet-adatok megjelenítése, ha elérhetőek.", @@ -291,6 +294,8 @@ "focused-color-label": "Fókuszált munkaterület színe", "follow-focused-screen-description": "Munkaterületek megjelenítése az aktuálisan fókuszált képernyőről, nem arról a képernyőről, ahol a sáv található.", "follow-focused-screen-label": "Fókuszált képernyő követése", + "font-weight-description": "A szöveg vizuális súlya a munkaterületen belül.", + "font-weight-label": "Betűvastagság", "grouped-border-opacity-description": "A munkaterület-konténerek szegélyeinek átlátszósági szintjének beállítása.", "grouped-border-opacity-label": "Szegély átlátszósága", "hide-unoccupied-description": "Ne jelenítse meg az ablak nélküli munkaterületeket.", @@ -300,7 +305,7 @@ "occupied-color-description": "A foglalt munkaterület háttérszínének beállítása.", "occupied-color-label": "Foglalt munkaterület színe", "pill-size-description": "A munkaterület jelzők mérete (50%-100%).", - "pill-size-label": "Pill méret", + "pill-size-label": "Kapszula méret", "reverse-scrolling-description": "Görgetéskor fordított irányba váltson a munkaterületek között.", "reverse-scrolling-label": "Görgetés megfordítása", "show-applications-description": "Alkalmazásikonok megjelenítése minden munkaterületen belül.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Összes akkumulátor (kombinálva)", "battery-health": "Akkumulátor állapota", "battery-level": "Akkumulátor töltöttségi szintje", "capacity-level": "Kapacitás: {level}", @@ -374,6 +380,7 @@ "add": "Hozzáadás", "appearance": "Megjelenés", "apply": "Alkalmazás", + "auto-connect": "Automatikus csatlakozás", "automation": "Automatizálás", "available": "Elérhető", "back": "Vissza", @@ -381,7 +388,7 @@ "behavior": "Viselkedés", "bluetooth": "Bluetooth", "brightness": "Fényerő", - "browse": "Böngésszen", + "browse": "Böngészés", "calendar": "Naptár", "calendar-panel": "Naptár panel", "cancel": "Mégse", @@ -401,6 +408,7 @@ "contributors": "Közreműködők", "copied-to-clipboard": "Vágólapra másolva", "countdown": "Visszaszámlálás", + "customize": "Testreszabás", "date": "Dátum", "default": "Alapértelmezett", "delete": "Törlés", @@ -414,15 +422,20 @@ "documentation": "Dokumentáció", "download": "Letöltés", "duration": "Időtartam", - "dysfunctional": "Diszfunkcionális", + "dysfunctional": "Hibás", "edit": "Szerkesztés", "enabled": "Engedélyezve", "error": "Hiba", "ethernet": "Ethernet", "events": "Események", "execute": "Végrehajtás", - "faithful": "Hű", + "faithful": "Hűséges", "focus": "Fókusz", + "font-weight-bold": "Félkövér", + "font-weight-light": "Vékony", + "font-weight-medium": "Közepes", + "font-weight-regular": "Normál", + "font-weight-semibold": "Félkövér", "frequency": "Frekvencia", "gateway": "Átjáró", "general": "Általános", @@ -445,6 +458,7 @@ "lock": "Zárolás", "logout": "Kijelentkezés", "look": "Megjelenés", + "margins": "Margók", "media": "Média", "media-player": "Médialejátszó", "memory": "Memória", @@ -467,6 +481,7 @@ "panels": "Panelek", "password": "Jelszó", "pause": "Szünet", + "performance": "Teljesítmény", "pin": "Rögzítés", "play": "Lejátszás", "polling": "Lekérdezés", @@ -511,7 +526,7 @@ "test": "Teszt", "thresholds": "Küszöbértékek", "title": "Cím", - "toast": "Toast", + "toast": "Értesítő", "trusted": "Megbízható", "uninstall": "Eltávolítás", "unknown": "Ismeretlen", @@ -519,10 +534,11 @@ "unpin": "Rögzítés megszüntetése", "update": "Frissítés", "upload": "Feltöltés", + "userspace-reboot": "Felhasználói tér újraindítása", "version": "Verzió", "vibrant": "Élénk", "visualizer": "Vizualizáló", - "volume": "Térfogat", + "volume": "Hangerő", "volumes": "Hangerő", "wallpaper": "Háttérkép", "weather": "Időjárás", @@ -537,7 +553,7 @@ "control-center": { "power-profile": { "tooltip-action": "Energiaprofil", - "tooltip-disabled": "Telepítse a power-profiles-daemon csomagot az energiaprofilok használatához" + "tooltip-disabled": "Telepítsd a power-profiles-daemon csomagot az energiaprofilok használatához" }, "wifi": { "label-disconnected": "Wi-Fi leválasztva", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Legyél támogató", "changelog": "Változások megtekintése", + "changelog-on-startup": "Változási napló megjelenítése frissítéskor", + "changelog-on-startup-desc": "Automatikusan mutassa a változási naplót, amikor a Noctalia frissül.", "contributors-description": "Köszönet a(z) {count} fantasztikus közreműködőnknek!", "contributors-description-plural": "Köszönet a(z) {count} fantasztikus közreműködőnknek!", "copy-info": "Információk másolása", "debug-disabled": "Hibakeresési mód letiltva", "debug-enabled": "Hibakeresési mód engedélyezve", "info-copied": "Információ a vágólapra másolva", + "noctalia-available": "Elérhető:", "noctalia-desc": "Egy elegáns és minimalista asztali felület, Waylandra tervezve, Quickshell-en alapulva.", "noctalia-git-commit": "Git commit:", "noctalia-installed-version": "Telepített verzió:", "noctalia-latest-version": "Legújabb verzió:", + "noctalia-qs-version": "Noctalia QS verzió:", "noctalia-title": "Noctalia shell", "privacy-policy": "Adatvédelmi irányelvek", "support": "Támogasson minket", @@ -720,11 +740,12 @@ "supporters-desc": "Hatalmas köszönet csodálatos támogatóinknak!", "supporters-desc-plural": "Hatalmas köszönet a {count} csodálatos támogatóinknak!", "supporters-loading": "Támogatók betöltése...", + "system-board": "Alaplap:", "system-cpu": "CPU:", "system-disk": "Lemez:", "system-gpu": "GPU:", "system-host": "Házigazda:", - "system-install-hint": "Telepítse a fastfetch programot a rendszerinformációk megtekintéséhez", + "system-install-hint": "Telepítsd a fastfetch programot a rendszerinformációk megtekintéséhez", "system-kernel": "Kernel:", "system-loading": "Rendszerinformációk betöltése...", "system-memory": "Memória:", @@ -737,7 +758,7 @@ "system-uptime": "Működési idő:", "system-wm": "WM:", "telemetry-data-copied": "Telemetriai adatok a vágólapra másolva", - "telemetry-desc": "Segítsen a Noctalia fejlesztésében anonim rendszerinformációk (képernyőfelbontás, kompozitor, disztribúció) megosztásával. Egyszer kerül elküldésre indításkor, nincs nyomon követés, az adatok 30 nap után automatikusan törlődnek.", + "telemetry-desc": "Segíts a Noctalia fejlesztésében anonim rendszerinformációk (képernyőfelbontás, kompozitor, disztribúció) megosztásával. Egyszer kerül elküldésre indításkor, nincs nyomon követés, az adatok 30 nap után automatikusan törlődnek.", "telemetry-enabled": "Névtelen rendszerinformációk küldése", "telemetry-show-data": "Adatok megtekintése", "telemetry-title": "Adatvédelem", @@ -753,7 +774,7 @@ "devices-output-device-description": "A kívánt hangkimeneti eszköz.", "devices-output-device-label": "Kimeneti eszköz", "devices-title": "Hangeszközök", - "external-mixer-description": "Add meg a parancsot vagy alkalmazás útvonalát, amit el kell indítani a külső hangkeverő funkció aktiválásakor.", + "external-mixer-description": "A parancs vagy alkalmazás útvonala, amit el kell indítani a külső hangkeverő funkció aktiválásakor.", "external-mixer-label": "Külső hangkeverő parancs", "external-mixer-placeholder": "pwvucontrol || pavucontrol", "media-desc": "A preferált és figyelmen kívül hagyott médiaalkalmazások.", @@ -776,7 +797,7 @@ "visualizer-type-description": "Vizualizációs típus a média lejátszásához.", "visualizer-type-label": "Vizualizációs típus", "volumes-desc": "Beállítja a hangerőszabályzókat és a hangszinteket.", - "volumes-feedback-sound-file-description": "Az a hangfájl elérési útja, amely a hangerő változtatásakor játszódik le.", + "volumes-feedback-sound-file-description": "Annak a hangfájlnak az elérési útja, amely a hangerő változtatásakor játszódik le.", "volumes-feedback-sound-file-label": "Hangerő visszajelző hangfájl", "volumes-feedback-sound-file-select-title": "Hangerő visszajelző hangfájl kiválasztása", "volumes-input-volume-description": "Mikrofon bemeneti hangerőszintje.", @@ -810,7 +831,9 @@ "appearance-density-label": "Sáv sűrűsége", "appearance-desc": "A sáv megjelenésének és pozíciójának testreszabása.", "appearance-display-mode-description": "Mikor látható a sáv.", - "appearance-floating-description": "Megjeleníti a sávot lebegő „pirulaként”.", + "appearance-enable-exclusion-zone-inset-description": "Csökkentse a kizárási zónát 1 fizikai pixellel, hogy a Bar széle alá tökéletesen illeszkedjenek a szélén lévő ablakok.", + "appearance-enable-exclusion-zone-inset-label": "Behúzott kizárási zóna", + "appearance-floating-description": "Megjeleníti a sávot lebegő „kapszulaként”.", "appearance-floating-label": "Lebegő sáv", "appearance-font-scale-description": "A betűméret skálájának beállítása a sávon megjelenő szöveghez.", "appearance-font-scale-label": "Betűméret-skála", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "A sáv elrejtése és a panelek bezárása, amikor a kompozitor áttekintése aktív.", "appearance-hide-on-overview-label": "Sáv elrejtése az áttekintésben", "appearance-margins-description": "A lebegő sáv körüli margók.", - "appearance-margins-horizontal": "Vízszintes", - "appearance-margins-label": "Margók", - "appearance-margins-vertical": "Függőleges", + "appearance-margins-horizontal": "Vízszintes margó", + "appearance-margins-vertical": "Függőleges margó", "appearance-outer-corners-description": "Megjelenít kifelé ívelt sarkokat a sávon.", "appearance-outer-corners-label": "Külső sarkok", "appearance-position-description": "A sáv a képernyőn való elhelyezése.", @@ -840,13 +862,33 @@ "appearance-use-separate-opacity-label": "Külön oszlop átlátszóság használata", "appearance-widget-spacing-description": "Az egyes widgetek közötti távolság a sávon.", "appearance-widget-spacing-label": "Widget távolság", + "behavior-middle-click-command-description": "Középső kattintásra végrehajtandó parancs.", + "behavior-middle-click-command-label": "Középső kattintás parancsa", + "behavior-middle-click-command-placeholder": "niri msg action áttekintés váltása", + "behavior-middle-click-description": "Mit csináljon a középső kattintás a sáv üres területein.", + "behavior-middle-click-follow-mouse-description": "Megnyitja a kiválasztott középső kattintás panelt a kurzor pozíciójánál.", + "behavior-middle-click-follow-mouse-label": "Középső kattintás követi az egeret", + "behavior-middle-click-label": "Sáv középső kattintás művelete", + "behavior-right-click-command-description": "Jobb kattintásra végrehajtandó parancs.", + "behavior-right-click-command-label": "Jobb Kattintási Parancs", + "behavior-right-click-command-placeholder": "notify-send \"Jobb kattintás\"", + "behavior-right-click-description": "Mit csináljon a jobb kattintás a sáv üres területein.", + "behavior-right-click-follow-mouse-description": "Megnyitja a kiválasztott jobb egérgombos panelt a kurzor pozíciójánál.", + "behavior-right-click-follow-mouse-label": "Jobb kattintás követi az egeret", + "behavior-right-click-label": "Jobb kattintási művelet a sávon", + "behavior-wheel-wrap-description": "Ha engedélyezve van, a görgetés az utolsó elemtől az elsőig folytatódik.", + "behavior-wheel-wrap-label": "Körbefutás", + "behavior-workspace-scroll-description": "Mit csináljon az egérgörgő a sáv üres területein.", + "behavior-workspace-scroll-label": "Sáv egérgörgő művelet", + "behavior-workspace-scroll-option-content": "Tartalom", + "behavior-workspace-scroll-option-workspace": "Munkaterület", "monitor-configure-widgets": "Widgetek konfigurálása", "monitor-override-settings": "Globális beállítások felülírása", "monitor-override-settings-description": "Egyéni beállítások használata ehhez a monitorhoz.", "monitor-reset-all": "Összes visszaállítása", "monitor-widgets-title": "Widget konfiguráció - {monitor}", "monitors-desc": "Sáv megjelenítése meghatározott monitorokon. Alapértelmezetten minden monitoron, ha nincs kiválasztva.", - "monitors-desc-new": "Mely monitorok jelenítsék meg a sávot, és beállítások testre szabása monitoronként.", + "monitors-desc-new": "Mely monitorok jelenítsék meg a sávot, és a beállítások testre szabása monitoronként.", "monitors-title": "Monitorok megjelenítése", "title": "Sáv", "tray-blacklist-description": "Tálca kizárási szabályok, támogatja a helyettesítő karaktereket (*).", @@ -859,7 +901,7 @@ "widgets-title": "Widgetek elhelyezése" }, "color-scheme": { - "color-source-use-wallpaper-colors-description": "Színsémák generálása a háttérképedből. Automatikusan kinyeri a színeket, hogy egy egységes témát hozzon létre.", + "color-source-use-wallpaper-colors-description": "Színsémák generálása a háttérképből. Automatikusan kinyeri a színeket, hogy egy egységes témát hozzon létre.", "color-source-use-wallpaper-colors-label": "Háttérkép színeinek használata", "dark-mode-mode-description": "Engedélyezi az automatikus váltást a világos és sötét mód között.", "dark-mode-mode-label": "Sötét mód ütemezése", @@ -916,10 +958,12 @@ }, "connections": { "authentication-required": "Hitelesítés szükséges", + "bluetooth-auto-connect-description": "Automatikus csatlakozás megbízható párosított eszközökhöz, ha a Bluetooth engedélyezve van.", + "bluetooth-auto-connect-label": "Párosított eszközök automatikus csatlakoztatása", "bluetooth-devices-unnamed": "A névtelen eszközök nem jelennek meg.", "bluetooth-discoverable": "Ez az eszköz {hostName} néven fedezhető fel, amíg ez a beállítási fül nyitva van.", "bluetooth-rssi-polling-description": "Rendszeresen mintavételezi a csatlakoztatott eszközök RSSI-jét bluetoothctl-en keresztül. Lehet, hogy nem minden eszközhöz érhető el; engedélyezve minimális erőforrásokat használ.", - "bluetooth-rssi-polling-interval-description": "Konfigurálja, milyen gyakran frissüljön a jelerősség a csatlakoztatott eszközök számára.", + "bluetooth-rssi-polling-interval-description": "Milyen gyakran frissüljön a jelerősség a csatlakoztatott eszközök számára.", "bluetooth-rssi-polling-interval-label": "Lekérdezési intervallum", "bluetooth-rssi-polling-label": "Bluetooth jelszint lekérdezés", "disable-discoverability-description": "Eszköz elrejtése a közeli Bluetooth eszközök elől.", @@ -930,8 +974,10 @@ "title": "Kapcsolatok" }, "control-center": { - "cards-desc": "Személyre szabhatja, hogy mely vezérlők jelenjenek meg a vezérlőközpontban és milyen sorrendben.", + "cards-desc": "Mely vezérlők jelenjenek meg a vezérlőközpontban és milyen sorrendben.", "desc": "A vezérlőközpont panel pozicionálásának és viselkedésének konfigurálása.", + "open-at-mouse-description": "Ha a sáv üres területére kattintasz jobb gombbal, megnyílik a kurzornál a vezérlőközpont. Ha ez le van tiltva, akkor a fenti pozícióbeállítás kerül alkalmazásra.", + "open-at-mouse-label": "Megnyitás a kurzornál jobb kattintásra", "position-description": "Hol jelenjen meg a vezérlőközpont panel megnyitáskor.", "shortcuts-custom-button-command-description": "A gombra kattintáskor végrehajtandó parancs.", "shortcuts-custom-button-enable-on-state-logic-description": "Második ikon és „aktív” állapot engedélyezése ellenőrző parancs alapján.", @@ -983,15 +1029,16 @@ "edit-mode-description": "Engedélyezi a szerkesztési módot az asztali widgetek mozgatásához és áthelyezéséhez. Ha engedélyezve van, a widgetek húzható körvonalat mutatnak, és áthelyezhetők.", "edit-mode-exit-button": "Kilépés a szerkesztési módból", "edit-mode-grid-snap-label": "Rácshoz igazítás", + "edit-mode-grid-snap-scale-label": "Rácsra illesztés skálája", "edit-mode-label": "Szerkesztési mód", "enabled-description": "Asztali widgetek engedlyezése vagy letiltása.", "enabled-label": "Asztali widgetek engedélyezése", - "general-desc": "Konfigurálja az asztalon megjelenő widgeteket.", + "general-desc": "Az asztalon megjelenő widgetek konfigurálása.", "general-title": "Asztali widgetek", "media-player-enabled-description": "Médiaplayer widget megjelenítése az asztalon.", "media-player-enabled-label": "Médiaplayer widget engedélyezése", "media-player-rounded-corners-description": "Engedélyezi a lekerekített sarkokat a widget szélein.", - "media-player-show-album-art-description": "Mutasd az album borítóját és a számok adatait (cím és előadó).", + "media-player-show-album-art-description": "Az album borítójának és a számok adatainak (cím és előadó) megjelenítése.", "media-player-show-album-art-label": "Album borító és cím megjelenítése", "media-player-show-background-description": "Jelenítse meg a média lejátszó widget háttérkonténerét.", "media-player-show-buttons-description": "Médiavezérlő gombok (lejátszás/szünet, előző, következő) megjelenítése a média lejátszó widgeten.", @@ -1006,7 +1053,7 @@ "system-stat-layout-side": "Oldal", "system-stat-rounded-corners-description": "Lekerekített sarkok használata a widget hátteréhez.", "system-stat-rounded-corners-label": "Lekerekített Sarkok", - "system-stat-show-background-description": "Mutassa a háttérkonténert a rendszerstatisztika Widgethez.", + "system-stat-show-background-description": "Jelenítse meg a háttérkonténert a rendszerstatisztika Widgetben.", "system-stat-show-background-label": "Háttér megjelenítése", "system-stat-stat-type-description": "Mely rendszerstatisztikák jelenjenek meg.", "system-stat-stat-type-label": "Statisztika Típusa", @@ -1031,14 +1078,14 @@ "monitors-global-brightness-description": "Az összes monitor fényerejének egyidejű beállítása.", "monitors-global-brightness-label": "Összes monitor", "monitors-title": "Monitorkénti beállítások", - "night-light-auto-schedule-description": "A napnyugta és napkelte időpontja alapján {location} helyen — ajánlott.", + "night-light-auto-schedule-description": "A napnyugta és napkelte időpontja alapján itt: {location} — ajánlott.", "night-light-auto-schedule-label": "Automatikus ütemezés", "night-light-desc": "Csökkenti a kék fény kibocsátását, hogy jobban aludjon és csökkentse a szem megerőltetését.", "night-light-enable-description": "Meleg színszűrő alkalmazása a kék fény kibocsátás csökkentése érdekében.", "night-light-enable-label": "Éjszakai fény engedélyezése", "night-light-force-activation-description": "Figyelmen kívül hagyja az ütemezést, és azonnal alkalmazza az éjszakai szűrőt.", "night-light-force-activation-label": "Kényszerített aktiválás", - "night-light-manual-schedule-description": "Állítson be egyéni időpontokat a napkelte és napnyugta számára.", + "night-light-manual-schedule-description": "Egyéni időpontok a napkeltére és napnyugtára.", "night-light-manual-schedule-label": "Kézi ütemezés", "night-light-manual-schedule-select-start": "Kezdési idő kiválasztása", "night-light-manual-schedule-select-stop": "Leállítási idő kiválasztása", @@ -1064,11 +1111,11 @@ "appearance-display-auto-hide": "Automatikus elrejtés", "appearance-display-description": "A dokk viselkedése.", "appearance-display-exclusive": "Exkluzív", + "appearance-dock-indicator-description": "Egy kis jelző megjelenítése, amikor a dokk rejtve van.", + "appearance-dock-indicator-label": "Dokk jelző", "appearance-floating-distance-description": "A dokk és a képernyő széle közötti távolság.", "appearance-floating-distance-label": "Dokk lebegési távolság", - "appearance-frame-indicator-description": "Mutass egy kis jelzőt a kereten, amikor a dokk rejtve van.", - "appearance-frame-indicator-label": "Keretezett dokk jelző", - "appearance-group-apps-description": "Csoportosítsa ugyanazon app több ablakát egy dokk-bejegyzésbe.", + "appearance-group-apps-description": "Ugyanazon alkalmazás több ablakának csoportosítása egy dokk bejegyzésbe.", "appearance-group-apps-label": "Azonos alkalmazások csoportosítása", "appearance-group-click-action-cycle": "Ablakok váltása", "appearance-group-click-action-description": "Mit csinál a bal kattintás a csoportosított alkalmazásoknál.", @@ -1088,7 +1135,13 @@ "appearance-icon-size-label": "Dokk mérete", "appearance-inactive-indicators-description": "Jelenjenek meg jelzőpöttyök minden alkalmazásnál, ne csak az aktuálisan aktívnál.", "appearance-inactive-indicators-label": "Futásjelzők", - "appearance-launcher-position-description": "Válaszd ki, hol jelenjen meg az indító ikonja a dokkban.", + "appearance-indicator-color-description": "A rejtett dokk jelzőjének színe.", + "appearance-indicator-color-label": "Jelző színe", + "appearance-indicator-opacity-description": "A rejtett dokk jelzőjének átlátszósága.", + "appearance-indicator-opacity-label": "Jelző átlátszósága", + "appearance-indicator-thickness-description": "Vastagabb jelző használata a rejtett dokkhoz (3 px helyett 6 px).", + "appearance-indicator-thickness-label": "Vastagabb jelző", + "appearance-launcher-position-description": "Hol jelenjen meg az indító ikonja a dokkban.", "appearance-launcher-position-end": "Vége", "appearance-launcher-position-label": "Indító pozíciója", "appearance-launcher-position-start": "Indítás", @@ -1100,7 +1153,7 @@ "appearance-show-launcher-icon-label": "Alkalmazásindító megjelenítése", "appearance-sit-on-frame-description": "Igazítsa a dokkot a keret szegélyén belülre, ahelyett, hogy a tetején ülne.", "appearance-sit-on-frame-label": "A dokk a kereten ül", - "appearance-type-description": "Lebegő, pirula alakú sáv vagy statikus, a szélhez rögzített sáv.", + "appearance-type-description": "Lebegő, kapszula alakú sáv vagy statikus, a szélhez rögzített sáv.", "appearance-type-floating": "Lebegő", "appearance-type-label": "Dokk stílus", "appearance-type-static": "Statikus", @@ -1117,7 +1170,7 @@ "fonts-default-description": "A felületen használt fő betűtípus.", "fonts-default-label": "Alapértelmezett betűtípus", "fonts-default-placeholder": "Alapértelmezett betűtípus kiválasztása...", - "fonts-default-scale-description": "Növelje vagy csökkentse a standard szöveg méretét.", + "fonts-default-scale-description": "Szabványos szöveg méretének növelése vagy csökkentése.", "fonts-default-scale-label": "Alapértelmezett betűméret", "fonts-default-search-placeholder": "Betűtípus keresése...", "fonts-desc": "Az interfészben használt betűtípusok.", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Automatikus", "language-select-description": "Az alkalmazás felületén használt nyelv.", "language-select-label": "Alkalmazás nyelve", - "launch-setup-wizard": "Telepítő varázsló indítása", + "launch-setup-wizard": "Beállítási varázsló", "profile-desc": "Szerkessze felhasználói adatait és profilképét.", "profile-picture-description": "A profilkép, amely megjelenik a felületen.", "profile-picture-label": "Profilkép", @@ -1169,7 +1222,7 @@ }, "hooks": { "info-command-info-description": "• A parancsok shellen keresztül futnak (sh -lc)
• A parancsok a háttérben futnak (leválasztva)
• A tesztgombok az aktuális értékekkel futnak le", - "info-parameters-description": "• Háttérkép horog: $1 = háttérkép útvonala, $2 = képernyő neve
• Téma váltó horog: $1 = igaz/hamis (Sötét mód állapota)
• Képernyő zárolás/feloldás horgok: Nincsenek paraméterek
• Teljesítmény mód horgok: Nincsenek paraméterek
• Munkamenet horog: $1 = művelet (leállítás/újraindítás)", + "info-parameters-description": "• Háttérkép hook: $1 = háttérkép útvonala, $2 = képernyő neve
• Téma váltó hook: $1 = true/false (Dark Mode állapot)
• Képernyő zár/feloldó hookok: $1 = lock/unlock (képernyőzár állapota)
• Teljesítmény mód hookok: Nincs paraméter
• Munkamenet hook: $1 = action (shutdown/reboot)", "info-parameters-label": "Elérhető paraméterek", "noctalia-started-description": "Parancs, ami akkor fut le, amikor a Noctalia betöltése befejeződött.", "noctalia-started-label": "Noctalia elindult", @@ -1206,24 +1259,33 @@ "custom-description": "Futtass egy shell parancsot egy inaktivitási időszak után.", "custom-entry-command": "Parancs", "custom-entry-delete": "Törlés", + "custom-entry-edit": "Egyéni parancs szerkesztése", + "custom-entry-name": "Név", + "custom-entry-name-placeholder": "pl. Lámpák lekapcsolása", + "custom-entry-new": "Új egyéni parancs", "custom-entry-timeout": "Inaktivitási idő", - "custom-label": "Egyedi Tétlenségi Parancsok", + "custom-entry-timeout-format": "{count} másodperc", + "custom-entry-timeout-format-plural": "{count} másodperc", + "custom-entry-unnamed": "Névtelen parancs", + "custom-label": "Egyedi tétlenségi parancsok", "enable-description": "Automatikusan kikapcsolja a képernyőt, zárolja vagy felfüggeszti egy inaktivitási időszak után.", "enable-label": "Inaktivitás kezelés engedélyezése", "fade-duration-description": "Másodpercek a feketére halványuló animációhoz, mielőtt minden művelet elindul. Bármilyen egérmozgás megszakítja a halványítást.", "fade-duration-label": "Áttűnés időtartama", "lock-description": "Inaktivitási másodpercek a zárolási képernyő aktiválása előtt.", "lock-label": "Képernyőzár", + "resume-command-label": "Parancs folytatása", "screen-off-description": "Inaktivitás másodpercei, mielőtt a monitorok kikapcsolnak.", "screen-off-label": "Képernyő kikapcsolása", - "status-description": "A Compositor által jelentett inaktivitási idő.", - "status-label": "Tétlen Idő", + "status-description": "A kompozitor által jelentett inaktivitási idő.", + "status-label": "Tétlen idő", "suspend-description": "Másodpercnyi inaktivitás, mielőtt a rendszer felfüggeszti magát.", "tab-behavior": "Viselkedés", "tab-custom": "Egyéni", - "timeouts-description": "0 egy szakasz letiltásához. Az időtúllépések szünetelnek, amíg az Ébren tartás aktív.", + "timeouts-description": "0 a szakasz letiltásához. Az időtúllépések szünetelnek, amíg az 'Ébren tartás' aktív.", "timeouts-label": "Időtúllépések", - "unavailable": "Natív tétlenségfigyelés nem érhető el ezen a Compositoron." + "title": "Tétlenség", + "unavailable": "Natív tétlenségfigyelés nem érhető el ezen a kompozitoron." }, "indicator": { "default-value": "Alapértelmezett: {value}", @@ -1238,7 +1300,7 @@ "settings-annotation-tool-placeholder": "pl. 'gradia', 'satty -f -'", "settings-auto-paste-description": "Automatikusan beilleszti a kiválasztott vágólap elemet. Wtype szükséges hozzá.", "settings-auto-paste-label": "Automatikus beillesztés", - "settings-clip-preview-description": "Mutassa a vágólap tartalmának előnézetét a >clip parancs használatakor.", + "settings-clip-preview-description": "A vágólap tartalmának előnézetének megjelenítése a >clip parancs használatakor.", "settings-clip-preview-label": "Vágólap előnézet engedélyezése", "settings-clip-wrap-text-description": "A szöveg tördelése a vágólap listában a csonkítás helyett.", "settings-clip-wrap-text-label": "Clipboard szöveg becsomagolása", @@ -1255,7 +1317,7 @@ "settings-density-description": "Az alkalmazásikonok méretét és a Launcher sűrűsége.", "settings-density-label": "Sűrűség", "settings-desc": "Az indító viselkedésének és megjelenésének testreszabása.", - "settings-enable-session-search-description": "Munkamenet-műveletek (zárolás, leállítás, újraindítás stb.) megjelenítése a Launcherben való kereséskor.", + "settings-enable-session-search-description": "Munkamenet-műveletek (zárolás, leállítás, újraindítás stb.) megjelenítése az indítóban való kereséskor.", "settings-enable-session-search-label": "Munkamenet-keresés engedélyezése", "settings-enable-settings-search-description": "Beállítások megjelenítése a találatok között az indítón belül.", "settings-enable-settings-search-label": "Beállítások keresésének engedélyezése", @@ -1264,14 +1326,14 @@ "settings-grid-view-description": "Elemek megjelenítése rács elrendezésben lista helyett.", "settings-icon-mode-description": "Natív rendszerikonok használata a Tabler ikonok helyett.", "settings-icon-mode-label": "Natív ikonok használata", - "settings-ignore-mouse-input-description": "A launcherben tiltsd le az egérműveleteket és a görgőt.", + "settings-ignore-mouse-input-description": "Az egérműveletek és a görgő letiltása az indítóban.", "settings-ignore-mouse-input-label": "Egérbemenet figyelmen kívül hagyása", "settings-overlay-layer-description": "Jelenítse meg az indítót az átfedő rétegen, a teljes képernyős ablakok felett. Ha engedélyezve van, az indító nem fog a sávhoz csatlakozni.", "settings-overlay-layer-label": "Megjelenítés teljes képernyő felett", "settings-position-description": "Hol jelenjen meg az indító panel.", "settings-show-categories-description": "Kategóriafülek megjelenítése az alkalmazások szűréséhez.", "settings-show-categories-label": "Kategóriák megjelenítése", - "settings-show-icon-background-description": "Ikonok mögött lekerekített téglalap hátteret mutass.", + "settings-show-icon-background-description": "Ikonok mögötti lekerekített téglalap háttér megjelenítése.", "settings-show-icon-background-label": "Ikon háttér megjelenítése", "settings-sort-by-usage-description": "Ha engedélyezve van, a gyakran indított alkalmazások jelennek meg először a listán.", "settings-sort-by-usage-label": "Rendezés gyakoriság szerint", @@ -1321,11 +1383,11 @@ "weather-show-in-calendar-label": "Időjárás megjelenítése a naptárban" }, "lock-screen": { - "allow-password-with-fprintd-description": "Ha az fprintd (ujjlenyomat-hitelesítés) aktív, ez az opció lehetővé teszi, hogy továbbra is jelszóval jelentkezzen be ujjlenyomat helyett.", + "allow-password-with-fprintd-description": "Ha az fprintd (ujjlenyomat-hitelesítés) aktív, ez az opció lehetővé teszi, hogy továbbra is jelszóval jelentkezz be ujjlenyomat helyett.", "allow-password-with-fprintd-label": "Jelszavas bejelentkezés engedélyezése fprintd-vel", "auto-start-auth-description": "Automatikusan elindítja az ujjlenyomat-hitelesítést anélkül, hogy billentyűt kellene megnyomni vagy gombra kellene kattintani.", "auto-start-auth-label": "Automatikus hitelesítés indításkor", - "clock-format-description": "Testreszabhatja az óra formátumát dátum/idő szintaxis tokenek használatával.", + "clock-format-description": "Testreszabhatod az óra formátumát dátum/idő szintaxis tokenek használatával.", "clock-format-label": "Óraformátum", "clock-style-analog": "Analóg", "clock-style-custom": "Egyéni", @@ -1334,6 +1396,8 @@ "clock-style-label": "Óra stílus", "compact-lockscreen-description": "Csak a bejelentkezési beviteli mező és a rendszervezérlők megjelenítése, elrejtve az időjárás és média widgeteket.", "compact-lockscreen-label": "Kompakt zárolási képernyő", + "enable-lockscreen-media-controls-description": "Interaktív médialejátszási vezérlők megjelenítése a zárolási képernyőn.", + "enable-lockscreen-media-controls-label": "Zárolási Képernyő Médiavezérlői", "lock-on-suspend-description": "A képernyő automatikus zárolása a rendszer felfüggesztésekor.", "lock-on-suspend-label": "Zárolás felfüggesztéskor", "lock-screen-animations-description": "A zárolási képernyő animációinak engedélyezése vagy letiltása.", @@ -1415,7 +1479,7 @@ "sounds-separate-description": "Különböző hangfájlok használata az alacsony, normál és kritikus prioritású értesítésekhez.", "sounds-separate-label": "Különböző hangok használata prioritás szerint", "sounds-title": "Hangbeállítások", - "sounds-unavailable-description": "Telepítse a Qt6 Multimédiát az értesítési hangok engedélyezéséhez.", + "sounds-unavailable-description": "Telepítsd a Qt6 Multimédia csomagot az értesítési hangok engedélyezéséhez.", "sounds-unavailable-label": "Értesítési hangok nem érhetők el", "sounds-volume-description": "Az értesítési hangok hangerejének beállítása.", "sounds-volume-label": "Hangerő", @@ -1424,7 +1488,7 @@ "toast-desc": "Felugró üzenet megjelenésének és viselkedésének beállítása.", "toast-keyboard-description": "Felugró üzenet megjelenítése a billentyűzetkiosztás változásakor.", "toast-keyboard-label": "Billentyűzetkiosztás", - "toast-media-description": "Toast üzenet megjelenítése a média lejátszási állapotának változásakor.", + "toast-media-description": "Felugró üzenet megjelenítése a média lejátszási állapotának változásakor.", "toast-media-label": "Média" }, "osd": { @@ -1460,7 +1524,7 @@ "types-volume-label": "Kimeneti hangerő" }, "plugins": { - "auto-update": "Beépülők automatikus frissítése", + "auto-update": "Bővítmények automatikus frissítése", "auto-update-description": "Automatikusan frissítse az összes plugint a shell indításakor.", "available-description": "Bővítmények böngészése és telepítése a beállított forrásokból.", "available-label": "Elérhető bővítmények", @@ -1470,7 +1534,7 @@ "checking-for-updates": "Frissítések keresése...", "collision-already-installed": "Ez a bővítmény már telepítve van", "collision-custom-version-exists": "Egyéni verzió a következő helyről: \"{source}\" már telepítve van", - "collision-official-version-exists": "A plugin hivatalos verziója már telepítve van", + "collision-official-version-exists": "A bővítmény hivatalos verziója már telepítve van", "development-disable": "Fejlesztői mód letiltása ennél a bővítménynél", "development-enable": "Fejlesztői mód engedélyezése ehhez a beépülőhöz.", "filter-downloaded": "Letöltött", @@ -1485,7 +1549,7 @@ "install-success": "Sikeresen telepítve: {plugin}", "installed-description": "Az összes helyileg telepített bővítmény kezelése és beállítása.", "installed-label": "Telepített bővítmények", - "installed-no-plugins-description": "Telepítsen bővítményeket az \"Elérhető\" szakaszból.", + "installed-no-plugins-description": "Bővítmények telepítése az \"Elérhető\" szakaszban lehetséges.", "installed-no-plugins-label": "Nincs telepített bővítmény", "installing": "{plugin} telepítése...", "open-plugin-page": "Bővítmény oldal megnyitása", @@ -1501,7 +1565,7 @@ "sources-add-dialog-description": "GitHub tároló hozzáadása bővítményforrásként.", "sources-add-dialog-error": "Sikertelen bővítményforrás hozzáadás", "sources-add-dialog-name": "Tároló neve", - "sources-add-dialog-name-placeholder": "Saját egyéni bővítményeim", + "sources-add-dialog-name-placeholder": "Egyéni bővítményeim", "sources-add-dialog-success": "Bővítményforrás sikeresen hozzáadva", "sources-add-dialog-title": "Bővítményforrás hozzáadása", "sources-add-dialog-url": "Tároló URL-címe", @@ -1510,7 +1574,7 @@ "sources-remove-tooltip": "Bővítményforrás eltávolítása", "title": "Bővítmények", "translations-reloaded": "Fordítások újratöltve: {name}", - "uninstall-dialog-description": "Biztosan el szeretné távolítani a(z) {plugin} bővítményt? Ez eltávolítja az összes bővítményadatot.", + "uninstall-dialog-description": "Biztosan el szeretnéd távolítani a(z) {plugin} bővítményt? Ez eltávolítja az összes bővítményadatot.", "uninstall-dialog-title": "Bővítmény eltávolítása", "uninstall-error": "Sikertelen eltávolítás: {error}", "uninstall-success": "{plugin} sikeresen eltávolítva", @@ -1533,7 +1597,7 @@ "countdown-duration-label": "Visszaszámlálás időtartama", "enable-countdown-description": "Visszaszámlálás megjelenítése az energiagazdálkodási műveletek végrehajtása előtt.", "enable-countdown-label": "Visszaszámláló engedélyezése", - "entries-desc": "Szabja testre, mely energiagazdálkodási műveletek jelenjenek meg a munkamenet menüben, és milyen sorrendben.", + "entries-desc": "Mely energiagazdálkodási műveletek jelenjenek meg a munkamenet menüben, és milyen sorrendben.", "entries-title": "Energiagazdálkodási műveletek", "entry-settings-command-description": "Egyéni parancs a művelet végrehajtásához. Hagyd üresen az alapértelmezett rendszerparancs használatához.", "entry-settings-command-placeholder": "pl. systemctl poweroff", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Gyorsbillentyű tippek megjelenítése a munkamenet-opcióknál.", "show-keybinds-label": "Gyorsbillentyűk megjelenítése" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Az asztali widgetek láthatóak maradnak, amíg a Noctalia teljesítmény mód engedélyezve van.", + "noctalia-performance-disable-desktop-widgets-label": "Engedélyezi az asztali widgeteket teljesítmény módban", + "noctalia-performance-disable-wallpaper-description": "Az asztali, áttekintő és zárolási képernyő háttérképek láthatóak maradnak, amíg a Noctalia teljesítmény mód engedélyezve van.", + "noctalia-performance-disable-wallpaper-label": "Engedélyezi a háttérkép renderelését teljesítmény módban", + "title": "Rendszer" + }, "system-monitor": { "critical-color-label": "Kritikus szín", "custom-highlight-colors-title-label": "Egyéni kiemelőszínek", @@ -1564,7 +1635,7 @@ "disk-section-label": "Lemezhasználat", "enable-dgpu-monitoring-description": "Figyelem: Ez felébreszti a dedikált GPU-t (NVIDIA/AMD), ami jelentősen befolyásolhatja az akkumulátor élettartamát a hibrid grafikával rendelkező laptopokon.", "enable-dgpu-monitoring-label": "Dedikált GPU figyelés engedélyezése", - "external-monitor-description": "Add meg a parancsot vagy alkalmazás elérési útját a külső rendszermonitor alkalmazás indításához.", + "external-monitor-description": "Parancs vagy alkalmazás elérési útja a külső rendszermonitor alkalmazás indításához.", "external-monitor-label": "Külső rendszermonitor parancs", "external-monitor-placeholder": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor", "general-desc": "A rendszerfigyelő viselkedésének és megjelenésének beállítása.", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Lekérdezési intervallum", "polling-section-description": "Milyen gyakran frissüljenek az egyes rendszer metrikák.", "polling-section-label": "Lekérdezési időközök", - "threshold-critical": "Kritikus küszöb", - "threshold-warning": "Figyelmeztetési küszöb", + "threshold-critical": "Kritikus", + "threshold-warning": "Figyelmeztetés", "thresholds-section-description": "A figyelmeztetési/kritikus küszöbértékek és a lekérdezési intervallumok minden rendszermutatóhoz.", "use-custom-highlight-colors-description": "Ha le van tiltva, a téma alapértelmezett kiemelőszínei kerülnek használatra.", "use-custom-highlight-colors-label": "Egyéni kiemelőszínek használata", @@ -1588,7 +1659,9 @@ "animation-speed-description": "A globális animációs sebesség.", "animation-speed-label": "Animációs sebesség", "animation-speed-reset": "Animációs sebesség visszaállítása", - "appearance-desc": "Testreszabhatja a vizuális elemeket, például az eszköztippeket, a szegélyeket és az árnyékokat.", + "appearance-desc": "Testreszabhatod a vizuális elemeket, például az eszköztippeket, a szegélyeket és az árnyékokat.", + "blur-behind-description": "Elhomályosítja a sávok és panelek mögötti területet a kompozitor elmosási protokolljának használatával.", + "blur-behind-label": "Háttér elmosása", "box-border-description": "Tartalomterületek köré vázlatot jelenít meg.", "box-border-label": "Konténer körvonal", "box-border-radius-description": "A fő elrendezési szakaszok (például oldalsávok, kártyák és tartalompanelek) saroklekerekítése.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Panelek és sáv felül tartása", "scaling-description": "Megváltoztatja az általános felhasználói felület méretét, kivéve a sávot.", "scaling-label": "Felület méretezés", + "scrollbar-always-visible-description": "Görgetősávok láthatóan tartása, amikor a tartalom görgethető, ahelyett, hogy csak rámutatáskor jelennének meg.", + "scrollbar-always-visible-label": "Mindig mutassa a görgetősávokat", "settings-panel-header": "Beállítások panel", "settings-panel-mode-description": "A beállítások elrendezése (újraindítást igényelhet).", "settings-panel-mode-label": "Beállítási panel mód", @@ -1619,7 +1694,9 @@ "shadows-label": "Árnyékok", "title": "Felhasználói felület", "tooltips-description": "Engedélyezi vagy letiltja az eszköztippeket a felületen.", - "tooltips-label": "Eszköztippek megjelenítése" + "tooltips-label": "Eszköztippek megjelenítése", + "translucent-widgets-description": "Tedd a gombokat, füleket és egyéb widgeteket a paneleken belül félig átlátszóvá.", + "translucent-widgets-label": "Áttetsző widgetek" }, "wallpaper": { "automation-change-mode-alphabetical": "Ábécérend", @@ -1645,7 +1722,7 @@ "look-feel-transition-type-description": "Animáció típusa háttérképek közötti váltáskor.", "look-feel-transition-type-label": "Átmenet típusa", "settings-desc": "Szabályozza, hogyan legyenek kezelve és megjelenítve a háttérképek.", - "settings-enable-management-description": "Háttérképek kezelése a Noctalia segítségével. Vegye ki a pipát, ha inkább más alkalmazást használna.", + "settings-enable-management-description": "Háttérképek kezelése a Noctalia segítségével. Vedd ki a pipát, ha inkább más alkalmazást használnál.", "settings-enable-management-label": "Háttérkép-kezelés engedélyezése", "settings-enable-overview-description": "Elmosódott és elsötétített háttérképet alkalmaz az áttekintő képernyőn.", "settings-enable-overview-label": "Áttekintő háttérkép engedélyezése", @@ -1679,8 +1756,8 @@ "placeholders": { "add-new-keybind": "Új gyorsbillentyű hozzáadása", "command-example": "echo \"Hello World\"", - "enter-command": "Add meg a végrehajtandó parancsot (alkalmazás vagy egyéni parancsfájl)", - "enter-ipc-identifier": "Add meg az IPC parancsok egyedi azonosítóját", + "enter-command": "Végrehajtandó parancs (alkalmazás vagy egyéni parancsfájl)", + "enter-ipc-identifier": "Az IPC parancsok egyedi azonosítója", "enter-path": "Útvonal megadása...", "enter-text-to-collapse": "pl. 'semmi sem játszik'. /regex/ a mintákhoz.", "enter-tooltip": "Add meg a buboréksúgót", @@ -1740,8 +1817,9 @@ "none-in-dir": "Nincs háttérkép a könyvtárban", "preview-error": "A kép betöltése sikertelen", "select-prompt": "Válassz egy háttérképet alább", - "subheader": "Teremtsen hangulatot egy gyönyörű háttérrel." + "subheader": "Teremts hangulatot egy gyönyörű háttérrel." }, + "welcome": "Üdvözlünk", "welcome-note": "Csak néhány alapvető dolog az induláshoz — a teljes beállítási lehetőség a beállításokban található", "welcome-subtitle": "Tegyük egyedivé az asztalod", "welcome-title": "Üdvözöl a Noctalia!" @@ -1757,10 +1835,11 @@ "unknown-app": "Ismeretlen alkalmazás", "uptime": "Működési idő: {uptime}", "user-requested": "Felhasználó kérte", - "welcome-back": "Üdvözöljük újra,", + "welcome-back": "Üdvözlünk újra,", "widget-settings-title": "{widget} beállítások" }, "system-monitor": { + "core-usage": "Mag {id} használat", "cpu-temp": "CPU-hőmérséklet", "cpu-usage": "CPU-használat", "disk": "Lemez", @@ -1782,7 +1861,10 @@ }, "bluetooth": { "address-copied": "Cím a vágólapra másolva", - "confirm-code": "Erősítse meg a {value} kódot a másik eszközön.", + "auto-connect-disabled": "Automatikus csatlakozás letiltva", + "auto-connect-enabled": "Automatikus csatlakozás engedélyezve", + "auto-connecting": "Csatlakozás {count} eszközhöz...", + "confirm-code": "Erősítsd meg a {value} kódot a másik eszközön.", "connect-failed": "Nem sikerült csatlakozni az eszközhöz", "disconnect-failed": "Nem sikerült leválasztani az eszközről", "discoverable-change-failed": "Nem sikerült megváltoztatni a felfedezhetőság állapotát", @@ -1793,12 +1875,16 @@ "pair-failed": "Nem sikerült párosítani az eszközt", "passkey-required": "Az eszköz által megkövetelt jelszó.", "pincode-required": "Az eszköz által megkövetelt PIN.", - "state-change-failed": "Nem sikerült megváltoztatni a Bluetooth állapotot" + "state-change-failed": "Nem sikerült megváltoztatni a Bluetooth állapotát" }, "clipboard": { "long-text": "Hosszú szöveg", "unavailable": "A vágólapelőzmények nem elérhetők", - "unavailable-desc": "A 'cliphist' alkalmazás nincs telepítve. Telepítse a vágólapelőzmények funkcióinak használatához" + "unavailable-desc": "A 'cliphist' alkalmazás nincs telepítve. Telepítsd a vágólapelőzmények funkcióinak használatához" + }, + "custom-command-failed": { + "description": "A parancs sikertelen: {command}\\nKilépési kód: {code}", + "title": "Egyéni parancs sikertelen" }, "do-not-disturb": { "disabled": "Ne zavarjanak letiltása", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Widget hozzáadása", + "auto-connect": "Automatikus csatlakozás váltása ehhez az eszközhöz", + "bluetooth-auto-connect-off": "Automatikus csatlakozás kikapcsolva", + "bluetooth-auto-connect-on": "Automatikus csatlakozás engedélyezve", "bluetooth-devices": "Bluetooth eszközök", "brightness-at": "Fényerő: {brightness}%", "click-to-start-recording": "Képernyőfelvevő (felvétel indítása)", @@ -1951,6 +2040,11 @@ "resolution-label": "Felbontás", "resolution-mode-label": "Mód", "solid-color-tooltip": "Egyszínű háttér", + "sort-date-asc": "Rendezés: a legrégebbiek elöl", + "sort-date-desc": "Rendezés: a legújabbak elöl", + "sort-name-asc": "Rendezés név szerint (A-Z)", + "sort-name-desc": "Rendezés név szerint (Z-A)", + "sort-random": "Rendezés véletlenszerűen", "sorting-date-added": "Hozzáadás dátuma", "sorting-favorites": "Kedvencek", "sorting-label": "Rendezés", @@ -2044,7 +2138,7 @@ }, "wifi": { "panel": { - "action-required": "Szukseges akció", + "action-required": "Szükseges akció", "available-interfaces": "Elérhető interfészek", "available-networks": "Elérhető hálózatok", "disabled": "A Wi‑Fi le van tiltva", diff --git a/Assets/Translations/it.json b/Assets/Translations/it.json index 6892ce587..40fdd6882 100644 --- a/Assets/Translations/it.json +++ b/Assets/Translations/it.json @@ -5,12 +5,12 @@ "connect-vpn": "Connetti a {name}", "cycle-visualizer": "Cambia visualizzatore", "disable-bluetooth": "Disattiva Bluetooth", - "disable-dnd": "Disattiva Non disturbare", + "disable-dnd": "Disattiva 'Non disturbare'", "disable-wifi": "Disattiva Wi-Fi", "disconnect-vpn": "Disconnetti {name}", "dock-settings": "Impostazioni dock", "enable-bluetooth": "Attiva Bluetooth", - "enable-dnd": "Attiva Non disturbare", + "enable-dnd": "Attiva 'Non disturbare'", "enable-wifi": "Attiva Wi-Fi", "launcher-settings": "Impostazioni launcher", "lower-to-bottom": "Porta in basso", @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Seleziona il colore del visualizzatore.", "color-name-label": "Colore riempimento", + "height-description": "Larghezza componente personalizzata.", "hide-when-idle-description": "Se attivo, il visualizzatore viene nascosto quando nessun player è in riproduzione.", "hide-when-idle-label": "Nascondi quando non è in riproduzione nulla", "width-description": "Larghezza personalizzata del componente." @@ -60,8 +61,8 @@ "hide-if-not-detected-description": "Nasconde il widget quando non viene rilevata alcuna batteria nel sistema.", "hide-if-not-detected-label": "Nascondi se non rilevata", "low-battery-threshold-label": "Soglia avviso batteria scarica", - "show-noctalia-performance-description": "Mostra l’interruttore Modalità Prestazioni Noctalia nel pannello batteria.
Disattiva ombre e animazioni in Noctalia per ridurre l’uso di risorse.", - "show-noctalia-performance-label": "Mostra interruttore Prestazioni Noctalia", + "show-noctalia-performance-description": "Mostra l’interruttore della modalità prestazioni Noctalia nel pannello batteria.
Disattiva ombre e animazioni in Noctalia per ridurre l’uso di risorse.", + "show-noctalia-performance-label": "Mostra interruttore Noctalia Prestazioni", "show-power-profile-description": "Mostra la selezione del profilo energetico nel pannello batteria.", "show-power-profile-label": "Mostra controlli profilo energetico" }, @@ -70,7 +71,7 @@ "apply-all-label": "Applica modifiche di scorrimento a tutti i monitor" }, "clock": { - "clock-display-description": "Personalizza la visualizzazione dell’orologio aggiungendo token dall’elenco qui sotto. Per usare il formato 12 ore devi includere il token \"AP\".", + "clock-display-description": "Personalizza la visualizzazione dell’orologio aggiungendo token dall’elenco qui sotto. Per usare il formato 12 ore devi includere il token 'AP'.", "clock-display-label": "Visualizzazione orologio", "custom-font-description": "Seleziona un font personalizzato per la visualizzazione dell’orologio.", "custom-font-label": "Font personalizzato", @@ -117,7 +118,7 @@ "hide-mode-label": "Modalità nascondi", "hide-mode-max-transparent": "Massimo espanso ma trasparente", "icon-description": "Seleziona un’icona dalla libreria.", - "ipc-identifier-description": "Identificatore univoco per i comandi IPC. Usa questo identificatore con \"qs -c noctalia-shell ipc call cb [action] [identifier]\" per controllare questo pulsante via IPC.", + "ipc-identifier-description": "Identificatore univoco per i comandi IPC. Usa questo identificatore con 'qs -c noctalia-shell ipc call cb [action] [identifier]' per controllare questo pulsante via IPC.", "ipc-identifier-label": "Identificatore IPC", "left-click-description": "Comando da eseguire al clic sinistro sul pulsante.", "left-click-label": "Clic sinistro", @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Mostra statistiche come mini grafici a barre invece di valori testuali. Evita spostamenti del layout.", "compact-mode-label": "Modalità compatta", + "cpu-cores-description": "Mostra l'utilizzo dei core della CPU individualmente.", + "cpu-cores-label": "Core CPU", "cpu-frequency-description": "Mostra la frequenza corrente della CPU in GHz.", "cpu-frequency-label": "Mostra frequenza CPU", "cpu-temperature-description": "Mostra le letture della temperatura CPU se disponibili.", @@ -274,7 +277,7 @@ "colorize-icons-label": "Colora icone", "drawer-enabled-description": "Se attivo, gli elementi tray non fissati vengono mostrati in un pannello cassetto.
Se disattivo, tutti gli elementi tray vengono mostrati in linea.", "drawer-enabled-label": "Abilita cassetto", - "hide-passive-description": "Se attivo, gli elementi tray con stato \"Passive\" verranno nascosti.", + "hide-passive-description": "Se attivo, gli elementi tray con stato 'Passive' verranno nascosti.", "hide-passive-label": "Nascondi elementi passivi" }, "volume": { @@ -291,6 +294,8 @@ "focused-color-label": "Colore workspace focalizzata", "follow-focused-screen-description": "Mostra workspace dello schermo attualmente focalizzato invece dello schermo dove si trova la barra.", "follow-focused-screen-label": "Segui schermo focalizzato", + "font-weight-description": "Imposta il peso visivo per il testo all'interno dello spazio di lavoro.", + "font-weight-label": "Peso del carattere", "grouped-border-opacity-description": "Imposta il livello di opacità dei bordi contenitore workspace.", "grouped-border-opacity-label": "Opacità bordi", "hide-unoccupied-description": "Non mostrare workspace senza finestre.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Tutte le batterie (combinate)", "battery-health": "Stato batteria", "battery-level": "Livello batteria", "capacity-level": "Capacità: {level}", @@ -374,6 +380,7 @@ "add": "Aggiungi", "appearance": "Aspetto", "apply": "Applica", + "auto-connect": "Connessione automatica", "automation": "Automazione", "available": "Disponibile", "back": "Indietro", @@ -401,6 +408,7 @@ "contributors": "Contributori", "copied-to-clipboard": "Copiato negli appunti", "countdown": "Conto alla rovescia", + "customize": "Personalizza", "date": "Data", "default": "Predefinito", "delete": "Elimina", @@ -423,6 +431,11 @@ "execute": "Esegui", "faithful": "Fedele", "focus": "Messa a fuoco", + "font-weight-bold": "Grassetto", + "font-weight-light": "Sottile", + "font-weight-medium": "Medio", + "font-weight-regular": "Normale", + "font-weight-semibold": "Semibold", "frequency": "Frequenza", "gateway": "Gateway", "general": "Generale", @@ -445,6 +458,7 @@ "lock": "Blocca", "logout": "Disconnetti sessione", "look": "Aspetto", + "margins": "Margini", "media": "Media", "media-player": "Lettore multimediale", "memory": "Memoria", @@ -467,6 +481,7 @@ "panels": "Pannelli", "password": "Password", "pause": "Pausa", + "performance": "Prestazioni", "pin": "Fissa", "play": "Riproduci", "polling": "Interrogazione", @@ -519,6 +534,7 @@ "unpin": "Sblocca", "update": "Aggiorna", "upload": "Carica", + "userspace-reboot": "Riavvia spazio utente", "version": "Versione", "vibrant": "Vivace", "visualizer": "Visualizzatore", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Diventa sostenitore", "changelog": "Visualizza changelog", + "changelog-on-startup": "Mostra il changelog all'aggiornamento", + "changelog-on-startup-desc": "Mostra automaticamente il changelog quando Noctalia viene aggiornato.", "contributors-description": "Un ringraziamento al nostro {count} contributore fantastico!", "contributors-description-plural": "Un ringraziamento ai nostri {count} contributori fantastici!", "copy-info": "Copia info", "debug-disabled": "Modalità debug disattivata", "debug-enabled": "Modalità debug attivata", "info-copied": "Info copiate negli appunti", + "noctalia-available": "Disponibile:", "noctalia-desc": "Una shell desktop elegante e minimale, progettata con cura per Wayland, costruita con Quickshell.", "noctalia-git-commit": "Commit Git:", "noctalia-installed-version": "Versione installata:", "noctalia-latest-version": "Ultima versione:", + "noctalia-qs-version": "Noctalia QS versione:", "noctalia-title": "Shell Noctalia", "privacy-policy": "Informativa privacy", "support": "Supportaci", @@ -720,6 +740,7 @@ "supporters-desc": "Un enorme grazie al nostro fantastico sostenitore!", "supporters-desc-plural": "Un enorme grazie ai nostri {count} fantastici sostenitori!", "supporters-loading": "Caricamento sostenitori...", + "system-board": "Scheda madre:", "system-cpu": "CPU:", "system-disk": "Disco:", "system-gpu": "GPU:", @@ -810,7 +831,9 @@ "appearance-density-label": "Densità barra", "appearance-desc": "Personalizza aspetto e posizione della barra.", "appearance-display-mode-description": "Scegli quando la barra è visibile.", - "appearance-floating-description": "Mostra la barra come \"pill\" flottante.", + "appearance-enable-exclusion-zone-inset-description": "Riduci la zona di esclusione di 1 pixel fisico in modo che le finestre a filo si estendano perfettamente sotto il bordo della Bar.", + "appearance-enable-exclusion-zone-inset-label": "Zona di esclusione interna", + "appearance-floating-description": "Mostra la barra come 'pill' flottante.", "appearance-floating-label": "Barra flottante", "appearance-font-scale-description": "Regola la scala dimensione font per il testo mostrato nella barra.", "appearance-font-scale-label": "Scala font", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Nascondi barra e chiudi pannelli quando l’overview compositor è attiva.", "appearance-hide-on-overview-label": "Nascondi barra in overview", "appearance-margins-description": "Regola i margini attorno alla barra flottante.", - "appearance-margins-horizontal": "Orizzontale", - "appearance-margins-label": "Margini", - "appearance-margins-vertical": "Verticale", + "appearance-margins-horizontal": "Margine orizzontale", + "appearance-margins-vertical": "Margine verticale", "appearance-outer-corners-description": "Mostra angoli curvati verso l’esterno sulla barra.", "appearance-outer-corners-label": "Angoli esterni", "appearance-position-description": "Scegli dove posizionare la barra sullo schermo.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Usa opacità barra separata", "appearance-widget-spacing-description": "Regola la spaziatura tra i widget nella barra.", "appearance-widget-spacing-label": "Spaziatura widget", + "behavior-middle-click-command-description": "Comando da eseguire al clic centrale.", + "behavior-middle-click-command-label": "Comando Clic Centrale", + "behavior-middle-click-command-placeholder": "niri msg action commuta panoramica", + "behavior-middle-click-description": "Scegli cosa fa il clic centrale sulle aree vuote della Barra.", + "behavior-middle-click-follow-mouse-description": "Apri il pannello del clic centrale selezionato nella posizione del cursore.", + "behavior-middle-click-follow-mouse-label": "Clic centrale segue il mouse", + "behavior-middle-click-label": "Azione del clic centrale sulla Barra", + "behavior-right-click-command-description": "Comando da eseguire al clic destro.", + "behavior-right-click-command-label": "Comando Clic Destro", + "behavior-right-click-command-placeholder": "notify-send \"Clic destro\"", + "behavior-right-click-description": "Scegli cosa fa il clic destro sulle aree vuote della barra.", + "behavior-right-click-follow-mouse-description": "Apri il pannello del clic destro selezionato nella posizione del cursore.", + "behavior-right-click-follow-mouse-label": "Clic destro segue il mouse", + "behavior-right-click-label": "Azione al clic destro sulla Barra", + "behavior-wheel-wrap-description": "Quando abilitato, lo scorrimento continua dall'ultimo elemento al primo.", + "behavior-wheel-wrap-label": "Circolare", + "behavior-workspace-scroll-description": "Scegli cosa fa la rotellina del mouse sulle aree vuote della barra.", + "behavior-workspace-scroll-label": "Azione rotella mouse della Barra", + "behavior-workspace-scroll-option-content": "Contenuto", + "behavior-workspace-scroll-option-workspace": "Spazio di lavoro", "monitor-configure-widgets": "Configura widget", "monitor-override-settings": "Sovrascrivi impostazioni globali", "monitor-override-settings-description": "Usa impostazioni personalizzate per questo monitor.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Autenticazione richiesta", + "bluetooth-auto-connect-description": "Connettiti automaticamente ai dispositivi accoppiati fidati quando il Bluetooth è abilitato.", + "bluetooth-auto-connect-label": "Connetti automaticamente i dispositivi accoppiati", "bluetooth-devices-unnamed": "I dispositivi senza nome non sono mostrati.", "bluetooth-discoverable": "Questo dispositivo è visibile come {hostName} mentre questa scheda impostazioni è aperta.", "bluetooth-rssi-polling-description": "Campiona periodicamente l’RSSI dei dispositivi connessi tramite bluetoothctl. Potrebbe non essere disponibile per tutti i dispositivi; usa risorse minime quando attivo.", @@ -932,16 +976,18 @@ "control-center": { "cards-desc": "Personalizza quali controlli appaiono nel centro di controllo e in quale ordine.", "desc": "Configura posizione e comportamento del pannello centro di controllo.", + "open-at-mouse-description": "Quando si fa clic destro su un'area vuota della Bar, apri il Centro di Controllo vicino al cursore. Se disabilitato, viene utilizzata l'impostazione di posizione sopra indicata.", + "open-at-mouse-label": "Apri al cursore con clic destro", "position-description": "Scegli dove appare il pannello centro di controllo quando viene aperto.", "shortcuts-custom-button-command-description": "Comando da eseguire quando il pulsante viene cliccato.", - "shortcuts-custom-button-enable-on-state-logic-description": "Abilita una seconda icona e uno stato \"attivo\" basato su un comando di controllo.", + "shortcuts-custom-button-enable-on-state-logic-description": "Abilita una seconda icona e uno stato 'attivo' basato su un comando di controllo.", "shortcuts-custom-button-enable-on-state-logic-label": "Abilita logica stato attivo", "shortcuts-custom-button-icon-description": "Seleziona un’icona dalla libreria.", "shortcuts-custom-button-on-clicked-label": "Comando clic sinistro", "shortcuts-custom-button-on-middle-clicked-description": "Comando da eseguire al clic centrale del pulsante.", - "shortcuts-custom-button-on-state-command-description": "Comando da eseguire per verificare se il pulsante deve essere nello stato \"attivo\". Restituisce 0 per attivo, valore diverso da 0 per spento.", + "shortcuts-custom-button-on-state-command-description": "Comando da eseguire per verificare se il pulsante deve essere nello stato 'attivo'. Restituisce 0 per attivo, valore diverso da 0 per spento.", "shortcuts-custom-button-on-state-command-label": "Comando controllo stato attivo", - "shortcuts-custom-button-on-state-icon-description": "L’icona del pulsante quando è nello stato \"attivo\".", + "shortcuts-custom-button-on-state-icon-description": "L’icona del pulsante quando è nello stato 'attivo'.", "shortcuts-custom-button-on-state-icon-label": "Icona stato attivo", "shortcuts-custom-button-state-checks-add": "Aggiungi controllo stato", "shortcuts-custom-button-state-checks-command": "Comando da eseguire per questo controllo stato", @@ -983,6 +1029,7 @@ "edit-mode-description": "Abilita modalità modifica per spostare e riposizionare i widget desktop. Quando attiva, i widget mostrano un contorno di trascinamento e possono essere riposizionati.", "edit-mode-exit-button": "Esci da modalità modifica", "edit-mode-grid-snap-label": "Aggancio griglia", + "edit-mode-grid-snap-scale-label": "Scala di aggancio alla griglia", "edit-mode-label": "Modalità modifica", "enabled-description": "Abilita o disabilita completamente i widget desktop.", "enabled-label": "Abilita widget desktop", @@ -1010,7 +1057,7 @@ "system-stat-show-background-label": "Mostra sfondo", "system-stat-stat-type-description": "Scegli quale statistica di sistema visualizzare.", "system-stat-stat-type-label": "Tipo statistica", - "title": "Widget Desktop", + "title": "Widget desktop", "weather-enabled-description": "Mostra un widget meteo sul desktop.", "weather-enabled-label": "Abilita widget meteo", "weather-show-background-description": "Mostra il contenitore sfondo per il widget meteo." @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Nascondi automaticamente", "appearance-display-description": "Scegli come si comporta il dock.", "appearance-display-exclusive": "Esclusivo", + "appearance-dock-indicator-description": "Mostra un piccolo indicatore quando il dock è nascosto.", + "appearance-dock-indicator-label": "Indicatore dock", "appearance-floating-distance-description": "Imposta la distanza tra il dock e il bordo dello schermo.", "appearance-floating-distance-label": "Distanza flottante dock", - "appearance-frame-indicator-description": "Mostra un piccolo indicatore sul frame quando il dock è nascosto.", - "appearance-frame-indicator-label": "Indicatore frame dock", "appearance-group-apps-description": "Raggruppa più finestre della stessa app in una sola voce dock.", "appearance-group-apps-label": "Raggruppa stesse app", "appearance-group-click-action-cycle": "Scorri finestre", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Dimensione dock", "appearance-inactive-indicators-description": "Mostra indicatori per tutte le app, non solo quella attualmente attiva.", "appearance-inactive-indicators-label": "Indicatori in esecuzione", + "appearance-indicator-color-description": "Scegli il colore dell'indicatore del dock nascosto.", + "appearance-indicator-color-label": "Colore dell'indicatore", + "appearance-indicator-opacity-description": "Regola l'opacità dell'indicatore del dock nascosto.", + "appearance-indicator-opacity-label": "Opacità dell'indicatore", + "appearance-indicator-thickness-description": "Utilizza un indicatore del dock nascosto più spesso (6px invece di 3px).", + "appearance-indicator-thickness-label": "Indicatore più spesso", "appearance-launcher-position-description": "Scegli dove appare l’icona launcher nel dock.", "appearance-launcher-position-end": "Fine", "appearance-launcher-position-label": "Posizione launcher", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Automatico", "language-select-description": "Seleziona la lingua usata nell’interfaccia dell’applicazione.", "language-select-label": "Lingua applicazione", - "launch-setup-wizard": "Procedura guidata", + "launch-setup-wizard": "Guidata di configurazione", "profile-desc": "Modifica i dettagli utente e l’avatar.", "profile-picture-description": "La tua immagine profilo mostrata in tutta l’interfaccia.", "profile-picture-label": "Immagine profilo", @@ -1169,15 +1222,15 @@ }, "hooks": { "info-command-info-description": "• I comandi vengono eseguiti via shell (sh -lc)
• I comandi girano in background (staccati)
• I pulsanti test eseguono con i valori correnti", - "info-parameters-description": "• Hook sfondo: $1 = percorso sfondo, $2 = nome schermo
• Hook cambio tema: $1 = true/false (stato Modalità Scura)
• Hook blocco/sblocco schermo: nessun parametro
• Hook modalità prestazioni: nessun parametro
• Hook sessione: $1 = azione (shutdown/reboot)", + "info-parameters-description": "• Hook sfondo: $1 = percorso sfondo, $2 = nome schermo
• Hook cambio tema: $1 = true/false (stato Dark Mode)
• Hook blocco/sblocco schermo: $1 = lock/unlock (stato blocco schermo)
• Hook modalità prestazioni: Nessun parametro
• Hook sessione: $1 = action (shutdown/reboot)", "info-parameters-label": "Parametri disponibili", "noctalia-started-description": "Comando da eseguire quando Noctalia ha finito il caricamento.", "noctalia-started-label": "Noctalia avviata", - "noctalia-started-placeholder": "es. notify-send \"Noctalia caricata\"", - "performance-mode-disabled-description": "Comando da eseguire quando Modalità Prestazioni Noctalia è disabilitata.", + "noctalia-started-placeholder": "es. notify-send 'Noctalia caricata'", + "performance-mode-disabled-description": "Comando da eseguire quando la modalità prestazioni Noctalia è disabilitata.", "performance-mode-disabled-label": "Modalità prestazioni disabilitata", "performance-mode-disabled-placeholder": "es. notify-send \"Prestazioni\" \"Modalità disabilitata\"", - "performance-mode-enabled-description": "Comando da eseguire quando Modalità Prestazioni Noctalia è abilitata.", + "performance-mode-enabled-description": "Comando da eseguire quando la modalità prestazioni Noctalia è abilitata.", "performance-mode-enabled-label": "Modalità prestazioni abilitata", "performance-mode-enabled-placeholder": "es. notify-send \"Prestazioni\" \"Modalità abilitata\"", "screen-lock-description": "Comando da eseguire quando lo schermo viene bloccato.", @@ -1206,7 +1259,14 @@ "custom-description": "Esegui un comando shell dopo un periodo di inattività.", "custom-entry-command": "Comando", "custom-entry-delete": "Elimina", + "custom-entry-edit": "Modifica Comando Personalizzato", + "custom-entry-name": "Nome", + "custom-entry-name-placeholder": "es. Spegni le luci", + "custom-entry-new": "Nuovo Comando Personalizzato", "custom-entry-timeout": "Tempo inattività", + "custom-entry-timeout-format": "{count} secondo", + "custom-entry-timeout-format-plural": "{count} secondi", + "custom-entry-unnamed": "Comando senza nome", "custom-label": "Comandi inattività personalizzati", "enable-description": "Spegni automaticamente schermo, blocca o sospendi dopo un periodo di inattività.", "enable-label": "Abilita gestione inattività", @@ -1214,15 +1274,17 @@ "fade-duration-label": "Durata dissolvenza", "lock-description": "Secondi di inattività prima che si attivi la schermata blocco.", "lock-label": "Schermata blocco", + "resume-command-label": "Riprendi comando", "screen-off-description": "Secondi di inattività prima che i monitor vengano spenti.", "screen-off-label": "Spegni schermo", - "status-description": "Tempo inattività come riportato dal compositor.", + "status-description": "Tempo di inattività come riportato dal compositor.", "status-label": "Tempo inattività", "suspend-description": "Secondi di inattività prima che il sistema venga sospeso.", "tab-behavior": "Comportamento", "tab-custom": "Personalizzato", - "timeouts-description": "Imposta a 0 per disabilitare una fase. I timeout vengono sospesi mentre Mantieni attivo è attivo.", + "timeouts-description": "Imposta a 0 per disabilitare una fase. I timeout vengono sospesi mentre 'Mantieni attivo' è attivo.", "timeouts-label": "Timeout", + "title": "Inattività", "unavailable": "Il monitoraggio inattività nativo non è disponibile su questo compositor." }, "indicator": { @@ -1248,7 +1310,7 @@ "settings-clipboard-watch-image-label": "Comando monitoraggio immagini", "settings-clipboard-watch-text-description": "Stringa comando completa passata a wl-paste per cambi testo. (richiede riavvio)", "settings-clipboard-watch-text-label": "Comando monitoraggio testo", - "settings-custom-launch-prefix-description": "Prefissa i comandi con un launcher personalizzato (es. \"runapp\" per integrazione systemd).", + "settings-custom-launch-prefix-description": "Prefissa i comandi con un launcher personalizzato (es. 'runapp' per integrazione systemd).", "settings-custom-launch-prefix-enabled-description": "Usa un prefisso personalizzato per avviare applicazioni invece del metodo predefinito.", "settings-custom-launch-prefix-enabled-label": "Abilita prefisso avvio personalizzato", "settings-custom-launch-prefix-label": "Prefisso avvio personalizzato", @@ -1334,6 +1396,8 @@ "clock-style-label": "Stile orologio", "compact-lockscreen-description": "Mostra solo input di accesso e controlli di sistema, nascondendo i widget meteo e media.", "compact-lockscreen-label": "Schermata blocco compatta", + "enable-lockscreen-media-controls-description": "Mostra i controlli interattivi di riproduzione multimediale sulla schermata di blocco.", + "enable-lockscreen-media-controls-label": "Controlli Multimediali della Schermata di Blocco", "lock-on-suspend-description": "Blocca automaticamente lo schermo quando il sistema viene sospeso.", "lock-on-suspend-label": "Blocca alla sospensione", "lock-screen-animations-description": "Abilita o disabilita le animazioni della schermata blocco.", @@ -1345,7 +1409,7 @@ "monitors-desc": "Mostra la schermata blocco su monitor specifici. Se non ne scegli nessuno, verranno usati tutti.", "password-chars-description": "Icone carine usate per nascondere la password.", "password-chars-label": "Icone password casuali", - "show-hibernate-description": "Mostra l’opzione \"ibernazione\" nei controlli alimentazione.", + "show-hibernate-description": "Mostra l’opzione 'ibernazione' nei controlli alimentazione.", "show-hibernate-label": "Mostra ibernazione", "show-session-buttons-description": "Consenti accesso alle impostazioni di alimentazione dalla schermata blocco.", "show-session-buttons-label": "Controlli alimentazione", @@ -1438,12 +1502,12 @@ "duration-desc": "Per quanto tempo OSD resta visibile prima di nascondersi automaticamente.", "duration-title": "Timeout nascondimento automatico", "enabled-description": "Mostra in tempo reale le variazioni di volume e luminosità.", - "enabled-label": "Abilita On-Screen Display", + "enabled-label": "Abilita on-screen display", "events-desc": "Seleziona quali eventi attivano OSD.", "general-desc": "Configura visibilità e comportamento di OSD.", "location-description": "Dove appaiono gli indicatori a schermo.", "monitors-desc": "Mostra OSD su monitor specifici. Se non ne scegli nessuno, verranno usati tutti.", - "title": "On-Screen Display", + "title": "On-screen display", "types-brightness-description": "Mostra OSD quando cambia la luminosità schermo.", "types-brightness-label": "Luminosità", "types-custom-text-description": "Mostra OSD per messaggi testuali personalizzati da IPC.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Mostra suggerimenti scorciatoie nelle opzioni sessione.", "show-keybinds-label": "Mostra scorciatoie" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Mantenere visibili i widget del desktop mentre la modalità prestazioni Noctalia è abilitato.", + "noctalia-performance-disable-desktop-widgets-label": "Abilita i widget desktop in modalità prestazioni", + "noctalia-performance-disable-wallpaper-description": "Mantieni visibili gli sfondi del desktop, della panoramica e della schermata di blocco mentre la modalità prestazioni Noctalia è abilitato.", + "noctalia-performance-disable-wallpaper-label": "Abilita il rendering dello sfondo in modalità prestazioni", + "title": "Sistema" + }, "system-monitor": { "critical-color-label": "Colore critico", "custom-highlight-colors-title-label": "Colori evidenziazione personalizzati", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Intervallo polling", "polling-section-description": "Configura la frequenza di aggiornamento di ogni metrica di sistema.", "polling-section-label": "Intervalli polling", - "threshold-critical": "Soglia critica", - "threshold-warning": "Soglia avviso", + "threshold-critical": "Critico", + "threshold-warning": "Avviso", "thresholds-section-description": "Regola soglie avviso/critiche per ogni metrica di sistema.", "use-custom-highlight-colors-description": "Quando disattivato, vengono usati i colori evidenziazione predefiniti del tema.", "use-custom-highlight-colors-label": "Usa colori evidenziazione personalizzati", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Velocità animazione", "animation-speed-reset": "Reimposta velocità animazione", "appearance-desc": "Personalizza elementi visivi come tooltip, bordi e ombre.", + "blur-behind-description": "Sfoca l'area dietro le barre e i pannelli utilizzando il protocollo di sfocatura del compositore.", + "blur-behind-label": "Sfocatura dello sfondo", "box-border-description": "Mostra un contorno attorno alle aree contenuto.", "box-border-label": "Contorno contenitore", "box-border-radius-description": "Regola l’arrotondamento degli angoli delle principali sezioni layout, come sidebar, schede e pannelli contenuto.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Mantieni pannelli e barra in primo piano", "scaling-description": "Cambia la dimensione dell’interfaccia utente generale, esclusa la barra.", "scaling-label": "Scala interfaccia", + "scrollbar-always-visible-description": "Mantieni le barre di scorrimento visibili ogni volta che il contenuto è scorrevole, invece di mostrarle solo al passaggio del mouse.", + "scrollbar-always-visible-label": "Mostra sempre le barre di scorrimento", "settings-panel-header": "Pannello impostazioni", "settings-panel-mode-description": "Scegli layout impostazioni (potrebbe richiedere riapertura).", "settings-panel-mode-label": "Modalità pannello impostazioni", @@ -1619,7 +1694,9 @@ "shadows-label": "Ombre esterne", "title": "Interfaccia utente", "tooltips-description": "Abilita o disabilita i tooltip in tutta l’interfaccia.", - "tooltips-label": "Mostra tooltip" + "tooltips-label": "Mostra tooltip", + "translucent-widgets-description": "Rendi semitrasparenti i pulsanti, le schede e gli altri widget all'interno dei pannelli.", + "translucent-widgets-label": "Widget traslucidi" }, "wallpaper": { "automation-change-mode-alphabetical": "Alfabetico", @@ -1725,7 +1802,7 @@ }, "skip-setup": "Salta configurazione", "telemetry-wizard-done": "Capito!", - "telemetry-wizard-note": "Hai il controllo: puoi attivarla o disattivarla quando vuoi nelle impostazioni", + "telemetry-wizard-note": "Hai il controllo — abilita o disabilita questa opzione in qualsiasi momento nelle impostazioni", "telemetry-wizard-subtitle": "Abbiamo aggiunto analisi anonime per migliorare Noctalia", "telemetry-wizard-title": "Aggiornamento privacy", "wallpaper": { @@ -1742,6 +1819,7 @@ "select-prompt": "Seleziona uno sfondo qui sotto", "subheader": "Imposta l’atmosfera con uno sfondo bellissimo." }, + "welcome": "Benvenuto", "welcome-note": "Solo poche basi per iniziare: tutte le opzioni sono nelle impostazioni", "welcome-subtitle": "Rendiamo il tuo desktop davvero tuo", "welcome-title": "Benvenuto in Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "Impostazioni {widget}" }, "system-monitor": { + "core-usage": "Utilizzo del core {id}", "cpu-temp": "Temperatura CPU", "cpu-usage": "Uso CPU", "disk": "Disco", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Address copied to clipboard", + "auto-connect-disabled": "Connessione automatica disabilitata", + "auto-connect-enabled": "Connessione automatica abilitata", + "auto-connecting": "Connessione a {count} dispositivo/i...", "confirm-code": "Confirm code {value} on the other device.", "connect-failed": "Failed to connect to device", "disconnect-failed": "Failed to disconnect from device", @@ -1800,10 +1882,14 @@ "unavailable": "Clipboard history unavailable", "unavailable-desc": "The 'cliphist' application is not installed. Please install it to use clipboard history features" }, + "custom-command-failed": { + "description": "Comando fallito: {command}\\nCodice di uscita: {code}", + "title": "Comando personalizzato fallito" + }, "do-not-disturb": { - "disabled": "Do Not Disturb disabled", + "disabled": "'Non disturbare' disattivato", "disabled-desc": "Showing all notifications", - "enabled": "Do Not Disturb enabled", + "enabled": "'Non disturbare' attivato", "enabled-desc": "You'll find these notifications in your history" }, "donation-opened": "Donation page opened in your browser", @@ -1822,9 +1908,9 @@ "not-installed": "wlsunset not installed" }, "noctalia-performance": { - "disabled": "Performance mode disabled", - "enabled": "Performance mode enabled", - "label": "Noctalia Performance" + "disabled": "Modalità prestazioni disabilitata", + "enabled": "Modalità prestazioni abilitata", + "label": "Noctalia Prestazioni" }, "power-profile": { "changed": "Power profile changed", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Aggiungi widget", + "auto-connect": "Attiva/disattiva la connessione automatica per questo dispositivo", + "bluetooth-auto-connect-off": "Connessione automatica disattivata", + "bluetooth-auto-connect-on": "Connessione automatica attiva", "bluetooth-devices": "Dispositivi Bluetooth", "brightness-at": "Luminosità: {brightness}%", "click-to-start-recording": "Registratore schermo (avvia registrazione)", @@ -1951,6 +2040,11 @@ "resolution-label": "Risoluzione", "resolution-mode-label": "Modalità", "solid-color-tooltip": "Sfondo a colore uniforme", + "sort-date-asc": "Ordina dal più vecchio al più recente", + "sort-date-desc": "Ordina dal più recente al più vecchio", + "sort-name-asc": "Ordina per nome (A-Z)", + "sort-name-desc": "Ordina per nome (Z-A)", + "sort-random": "Ordina casualmente", "sorting-date-added": "Data aggiunta", "sorting-favorites": "Preferiti", "sorting-label": "Ordina per", diff --git a/Assets/Translations/ja.json b/Assets/Translations/ja.json index 1f7086890..5c860c0e3 100644 --- a/Assets/Translations/ja.json +++ b/Assets/Translations/ja.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "ビジュアライザーの色を選択します。", "color-name-label": "塗りつぶし色", + "height-description": "カスタムコンポーネントの幅。", "hide-when-idle-description": "有効にすると、再生中のプレーヤーがない場合にビジュアライザーを隠します。", "hide-when-idle-label": "メディア停止時は隠す", "width-description": "コンポーネントの幅をカスタム設定します。" @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "テキスト値の代わりに、ミニ棒グラフとして統計を表示します。レイアウトのずれを防ぎます。", "compact-mode-label": "コンパクトモード", + "cpu-cores-description": "CPUコアの使用状況を個別に表示します。", + "cpu-cores-label": "CPUコア", "cpu-frequency-description": "現在のCPUクロック速度をGHzで表示します。", "cpu-frequency-label": "CPU周波数を表示", "cpu-temperature-description": "利用可能な場合、CPU 温度を表示します。", @@ -291,6 +294,8 @@ "focused-color-label": "フォーカスされたワークスペースの色", "follow-focused-screen-description": "バーが配置されている画面ではなく、現在フォーカスされている画面のワークスペースを表示します。", "follow-focused-screen-label": "フォーカス中の画面に追従", + "font-weight-description": "ワークスペース内のテキストの視覚的ウェイトを設定します。", + "font-weight-label": "フォントの太さ", "grouped-border-opacity-description": "ワークスペースコンテナのボーダーの不透明度を設定します。", "grouped-border-opacity-label": "境界線の不透明度", "hide-unoccupied-description": "ウィンドウがないワークスペースを表示しません。", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "すべてのバッテリー(合計)", "battery-health": "バッテリーの状態", "battery-level": "バッテリー残量", "capacity-level": "容量:{level}", @@ -374,6 +380,7 @@ "add": "追加", "appearance": "外観", "apply": "適用", + "auto-connect": "自動接続", "automation": "自動化", "available": "利用可能", "back": "戻る", @@ -401,6 +408,7 @@ "contributors": "貢献者", "copied-to-clipboard": "クリップボードにコピーしました", "countdown": "カウントダウン", + "customize": "カスタマイズ", "date": "日付", "default": "デフォルト", "delete": "削除", @@ -423,6 +431,11 @@ "execute": "実行", "faithful": "忠実", "focus": "集中", + "font-weight-bold": "太字", + "font-weight-light": "ライト", + "font-weight-medium": "中", + "font-weight-regular": "標準", + "font-weight-semibold": "セミボールド", "frequency": "頻度", "gateway": "ゲートウェイ", "general": "一般", @@ -445,6 +458,7 @@ "lock": "ロック", "logout": "ログアウト", "look": "外観", + "margins": "余白", "media": "メディア", "media-player": "メディアプレーヤー", "memory": "メモリ", @@ -467,6 +481,7 @@ "panels": "パネル", "password": "パスワード", "pause": "一時停止", + "performance": "パフォーマンス", "pin": "ピン留め", "play": "再生", "polling": "ポーリング", @@ -519,6 +534,7 @@ "unpin": "ピン留め解除", "update": "アップデート", "upload": "アップロード", + "userspace-reboot": "ユーザースペースを再起動", "version": "バージョン", "vibrant": "鮮やか", "visualizer": "ビジュアライザー", @@ -703,16 +719,20 @@ "about": { "become-supporter": "サポーターになる", "changelog": "変更履歴を見る", + "changelog-on-startup": "アップデート時に変更履歴を表示", + "changelog-on-startup-desc": "Noctaliaが更新されたときに、自動的に変更履歴を表示する。", "contributors-description": "{count}人の素晴らしいコントリビューターに感謝!", "contributors-description-plural": "{count}人の素晴らしいコントリビューターに感謝!", "copy-info": "情報をコピー", "debug-disabled": "デバッグモードが無効になりました", "debug-enabled": "デバッグモードが有効です", "info-copied": "クリップボードにコピーしました", + "noctalia-available": "利用可能:", "noctalia-desc": "Wayland のために丁寧に作り込まれた、Quickshell 製の洗練されたミニマルなデスクトップシェル。", "noctalia-git-commit": "Git コミット:", "noctalia-installed-version": "現在のバージョン:", "noctalia-latest-version": "最新のバージョン:", + "noctalia-qs-version": "Noctalia QS バージョン:", "noctalia-title": "Noctalia shell", "privacy-policy": "プライバシーポリシー", "support": "支援する", @@ -720,6 +740,7 @@ "supporters-desc": "素晴らしいサポーターに心からの感謝を!", "supporters-desc-plural": "{count}名の素晴らしいサポーターの皆様に心より感謝いたします!", "supporters-loading": "サポーターを読み込み中...", + "system-board": "マザーボード:", "system-cpu": "CPU:", "system-disk": "ディスク:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "バーの密度", "appearance-desc": "バーの外観や位置をカスタマイズします。", "appearance-display-mode-description": "バーの表示タイミングを選択してください。", + "appearance-enable-exclusion-zone-inset-description": "排他ゾーンを1物理ピクセル縮小し、Barの端の下にぴったりと重なるようにウィンドウが表示されるようにします。", + "appearance-enable-exclusion-zone-inset-label": "インセット除外領域", "appearance-floating-description": "バーを浮かせて、カプセル型で表示します。", "appearance-floating-label": "フローティングバー", "appearance-font-scale-description": "バーに表示されるテキストのフォントサイズスケールを調整します。", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "コンポジタの概要がアクティブなときは、バーを非表示にしてパネルを閉じます。", "appearance-hide-on-overview-label": "概要のバーを非表示にする", "appearance-margins-description": "フローティングバーの周囲の余白を調整します。", - "appearance-margins-horizontal": "水平方向", - "appearance-margins-label": "余白", - "appearance-margins-vertical": "垂直方向", + "appearance-margins-horizontal": "水平マージン", + "appearance-margins-vertical": "垂直マージン", "appearance-outer-corners-description": "バーの角に、外向きの丸み(装飾)を表示します。", "appearance-outer-corners-label": "外側のコーナー", "appearance-position-description": "画面上のバーの配置場所を選択します。", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "バーの不透明度を個別に設定", "appearance-widget-spacing-description": "バー内の各ウィジェット間の間隔を調整します。", "appearance-widget-spacing-label": "ウィジェットの間隔", + "behavior-middle-click-command-description": "中央クリックで実行するコマンド。", + "behavior-middle-click-command-label": "中央クリックコマンド", + "behavior-middle-click-command-placeholder": "niri msg action 概要の切り替え", + "behavior-middle-click-description": "バーの空いている領域で中央クリックが何をするかを選択します。", + "behavior-middle-click-follow-mouse-description": "選択した中央クリックパネルをカーソル位置で開く。", + "behavior-middle-click-follow-mouse-label": "中央クリックでマウスを追従", + "behavior-middle-click-label": "バーの中央クリックアクション", + "behavior-right-click-command-description": "右クリック時に実行するコマンド。", + "behavior-right-click-command-label": "右クリックコマンド", + "behavior-right-click-command-placeholder": "notify-send \"右クリック\"", + "behavior-right-click-description": "バーの空いている領域で右クリックが何をするかを選択します。", + "behavior-right-click-follow-mouse-description": "選択した右クリックパネルをカーソル位置に開く。", + "behavior-right-click-follow-mouse-label": "右クリックでマウスに追従", + "behavior-right-click-label": "バーの右クリックアクション", + "behavior-wheel-wrap-description": "有効にすると、スクロールは最後の項目から最初の項目へ続きます。", + "behavior-wheel-wrap-label": "ループ", + "behavior-workspace-scroll-description": "バーの空いている領域でマウスホイールが何をするか選択します。", + "behavior-workspace-scroll-label": "バーのマウスホイールアクション", + "behavior-workspace-scroll-option-content": "コンテンツ", + "behavior-workspace-scroll-option-workspace": "ワークスペース", "monitor-configure-widgets": "ウィジェットを設定", "monitor-override-settings": "グローバル設定を上書き", "monitor-override-settings-description": "このモニターにカスタム設定を使用する。", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "認証が必要です", + "bluetooth-auto-connect-description": "Bluetoothが有効な場合、信頼できるペアリング済みデバイスに自動的に接続します。", + "bluetooth-auto-connect-label": "ペアリング済みデバイスを自動接続", "bluetooth-devices-unnamed": "名前のないデバイスは表示されません。", "bluetooth-discoverable": "このデバイスは、この設定タブが開いている間、{hostName} として検出可能です。", "bluetooth-rssi-polling-description": "bluetoothctl を介して接続されたデバイスの RSSI を定期的にサンプリングします。すべてのデバイスで利用できるとは限りません。有効にすると最小限のリソースを使用します。", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "コントロールセンターに表示する項目と、その順序をカスタマイズします。", "desc": "コントロールセンターの位置や挙動を設定します。", + "open-at-mouse-description": "バーの空いている領域を右クリックすると、カーソル付近にコントロールセンターを開きます。無効になっている場合、代わりに上記の配置設定が使用されます。", + "open-at-mouse-label": "右クリックでカーソル位置に開く", "position-description": "コントロールセンターを開いたときの表示位置を選択します。", "shortcuts-custom-button-command-description": "ボタンをクリックした際に実行するコマンド。", "shortcuts-custom-button-enable-on-state-logic-description": "チェックコマンドに基づいて、別のアイコンや「ON」状態を有効にします。", @@ -983,6 +1029,7 @@ "edit-mode-description": "編集モードを有効にして、デスクトップウィジェットの移動や配置変更を行います。有効にするとウィジェットに枠線が表示され、位置を変更できるようになります。", "edit-mode-exit-button": "編集モードを終了", "edit-mode-grid-snap-label": "グリッドにスナップ", + "edit-mode-grid-snap-scale-label": "グリッドスナップスケール", "edit-mode-label": "編集モード", "enabled-description": "デスクトップウィジェット全体を有効または無効にします。", "enabled-label": "デスクトップウィジェットを有効化", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "自動的に隠す", "appearance-display-description": "ドックの表示方法を選択します。", "appearance-display-exclusive": "領域を確保", + "appearance-dock-indicator-description": "ドックが非表示のときに小さなインジケーターを表示します。", + "appearance-dock-indicator-label": "ドックインジケーター", "appearance-floating-distance-description": "画面端からドックまでの距離を設定します。", "appearance-floating-distance-label": "画面端からの距離", - "appearance-frame-indicator-description": "Dockが非表示のとき、フレームに小さなインジケーターを表示する。", - "appearance-frame-indicator-label": "フレーム付きドックインジケーター", "appearance-group-apps-description": "同じアプリの複数のウィンドウを1つのDockエントリにグループ化します。", "appearance-group-apps-label": "同じアプリをグループ化", "appearance-group-click-action-cycle": "ウィンドウを切り替える", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "ドックのサイズ", "appearance-inactive-indicators-description": "現在アクティブなアプリだけでなく、すべてのアプリにインジケーターを表示します。", "appearance-inactive-indicators-label": "実行インジケーター", + "appearance-indicator-color-description": "隠れたドックのインジケーターの色を選択します。", + "appearance-indicator-color-label": "インジケーターの色", + "appearance-indicator-opacity-description": "隠れたドックのインジケーターの不透明度を調整します。", + "appearance-indicator-opacity-label": "インジケーターの不透明度", + "appearance-indicator-thickness-description": "隠れたドックのインジケーターを太く表示します (3pxの代わりに6px)。", + "appearance-indicator-thickness-label": "太いインジケーター", "appearance-launcher-position-description": "ランチャーアイコンをドックのどこに表示するかを選択します。", "appearance-launcher-position-end": "終了", "appearance-launcher-position-label": "ランチャーの位置", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "自動検出", "language-select-description": "アプリケーションのインターフェースで使用する言語を選択します。", "language-select-label": "表示言語", - "launch-setup-wizard": "セットアップウィザードを起動", + "launch-setup-wizard": "セットアップウィザード", "profile-desc": "ユーザーの詳細とアバターを編集します。", "profile-picture-description": "インターフェース全体に表示されるプロフィール画像。", "profile-picture-label": "プロフィール画像", @@ -1169,7 +1222,7 @@ }, "hooks": { "info-command-info-description": "• コマンドはシェル (sh -lc) 経由で実行されます
• コマンドはバックグラウンドで実行されます
• テストボタンは現在の設定値を使って実行されます", - "info-parameters-description": "• 壁紙フック:$1 = 壁紙パス、$2 = 画面名
• テーマ切り替えフック:$1 = true/false(ダークモードの状態)
• 画面ロック/ロック解除フック:パラメータなし
• パフォーマンスモードフック:パラメータなし
• セッションフック:$1 = アクション(シャットダウン/再起動)", + "info-parameters-description": "• 壁紙フック: $1 = 壁紙のパス, $2 = 画面名
• テーマ切り替えフック: $1 = true/false (Dark Modeの状態)
• 画面ロック/ロック解除フック: $1 = lock/unlock (画面ロックの状態)
• パフォーマンスモードフック: パラメータなし
• セッションフック: $1 = action (shutdown/reboot)", "info-parameters-label": "利用可能なパラメータ(引数)", "noctalia-started-description": "Noctaliaのロードが完了したときに実行するコマンド。", "noctalia-started-label": "Noctalia が起動しました", @@ -1206,7 +1259,14 @@ "custom-description": "一定期間の非アクティブ状態の後、シェルコマンドを実行します。", "custom-entry-command": "コマンド", "custom-entry-delete": "削除", + "custom-entry-edit": "カスタムコマンドを編集", + "custom-entry-name": "名前", + "custom-entry-name-placeholder": "例:ライトを消す", + "custom-entry-new": "新しいカスタムコマンド", "custom-entry-timeout": "非アクティブ時間", + "custom-entry-timeout-format": "{count} 秒", + "custom-entry-timeout-format-plural": "{count} 秒", + "custom-entry-unnamed": "名前のないコマンド", "custom-label": "カスタムアイドルコマンド", "enable-description": "一定時間操作がない場合、自動的に画面をオフにする、ロックする、またはサスペンドする。", "enable-label": "アイドル管理を有効にする", @@ -1214,15 +1274,17 @@ "fade-duration-label": "フェード時間", "lock-description": "ロック画面がアクティブになるまでの非アクティブ秒数。", "lock-label": "画面をロック", + "resume-command-label": "コマンドを再開", "screen-off-description": "モニターがオフになるまでの非アクティブな秒数。", "screen-off-label": "画面をオフにする", - "status-description": "Compositorが報告したアイドル時間。", + "status-description": "コンポジターによって報告されたアイドル時間。", "status-label": "アイドル時間", "suspend-description": "システムがサスペンドするまでの非アクティブな時間(秒)。", "tab-behavior": "動作", "tab-custom": "カスタム", - "timeouts-description": "ステージを無効にするには0に設定します。Keep Awakeがアクティブな間、タイムアウトは一時停止されます。", + "timeouts-description": "ステージを無効にするには0に設定します。スリープ防止がアクティブな間、タイムアウトは一時停止されます。", "timeouts-label": "タイムアウト", + "title": "アイドル", "unavailable": "このCompositorでは、ネイティブのアイドル監視は利用できません。" }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "時計のスタイル", "compact-lockscreen-description": "天気やメディアウィジェットを隠し、ログイン入力とシステムコントロールのみを表示します。", "compact-lockscreen-label": "コンパクトなロック画面", + "enable-lockscreen-media-controls-description": "ロック画面にインタラクティブなメディア再生コントロールを表示する。", + "enable-lockscreen-media-controls-label": "ロック画面のメディアコントロール", "lock-on-suspend-description": "システムのサスペンド時に、自動的に画面をロックします。", "lock-on-suspend-label": "サスペンド時にロック", "lock-screen-animations-description": "ロック画面のアニメーションを有効または無効にする。", @@ -1438,7 +1502,7 @@ "duration-desc": "OSD が自動的に隠れるまでの表示継続時間。", "duration-title": "自動非表示のタイムアウト", "enabled-description": "音量や輝度の変更をリアルタイムで表示します。", - "enabled-label": "OSD を有効化", + "enabled-label": "オンスクリーンディスプレイを有効に (OSD)", "events-desc": "画面表示をトリガーするイベントを選択してください。", "general-desc": "OSDの表示と動作を設定します。", "location-description": "OSD の表示位置を選択します。", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "セッションオプションにキーバインドのヒントを表示する。", "show-keybinds-label": "キーバインドを表示" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Noctalia パフォーマンスモードが有効な間、デスクトップウィジェットを表示したままにする。", + "noctalia-performance-disable-desktop-widgets-label": "パフォーマンスモードでデスクトップウィジェットを有効にする", + "noctalia-performance-disable-wallpaper-description": "Noctalia パフォーマンスモードが有効な間、デスクトップ、概要、ロック画面の壁紙を表示したままにします。", + "noctalia-performance-disable-wallpaper-label": "パフォーマンスモードで壁紙のレンダリングを有効にする", + "title": "システム" + }, "system-monitor": { "critical-color-label": "危険時の色", "custom-highlight-colors-title-label": "カスタムハイライト色", @@ -1573,8 +1644,8 @@ "polling-interval-label": "ポーリング間隔", "polling-section-description": "各システムメトリクスの更新頻度を設定します。", "polling-section-label": "ポーリング間隔", - "threshold-critical": "危険閾値", - "threshold-warning": "警告閾値", + "threshold-critical": "重大", + "threshold-warning": "警告", "thresholds-section-description": "各システムメトリックの警告/危険しきい値とポーリング間隔を調整します。", "use-custom-highlight-colors-description": "無効にすると、テーマのデフォルト強調色が使用されます。", "use-custom-highlight-colors-label": "カスタム強調色を使用する", @@ -1589,6 +1660,8 @@ "animation-speed-label": "アニメーション速度", "animation-speed-reset": "アニメーション速度をリセット", "appearance-desc": "ツールチップ、境界線、影などの視覚要素をカスタマイズします。", + "blur-behind-description": "バーとパネルの背後の領域を、コンポジターのぼかしプロトコルを使用してぼかします。", + "blur-behind-label": "背景ぼかし", "box-border-description": "コンテンツ領域の周りにアウトラインを表示します。", "box-border-label": "コンテナの輪郭", "box-border-radius-description": "サイドバー、カード、コンテンツパネルなど、主要なレイアウト部分の角の丸みを調整します。", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "パネルとバーを最前面に保持", "scaling-description": "バーを除く、ユーザーインターフェース全体のサイズを変更します。", "scaling-label": "インターフェースのスケール調整", + "scrollbar-always-visible-description": "コンテンツがスクロール可能な場合、スクロールバーを常に表示し、ホバー時のみ表示するのをやめる。", + "scrollbar-always-visible-label": "常にスクロールバーを表示", "settings-panel-header": "設定パネル", "settings-panel-mode-description": "設定画面の表示形式を選択します (再起動が必要な場合があります)。", "settings-panel-mode-label": "設定パネルの表示モード", @@ -1619,7 +1694,9 @@ "shadows-label": "ドロップシャドウ", "title": "ユーザーインターフェース", "tooltips-description": "インターフェース全体のツールチップの有効・無効を切り替えます。", - "tooltips-label": "ツールチップを表示" + "tooltips-label": "ツールチップを表示", + "translucent-widgets-description": "パネル内のボタン、タブ、その他のウィジェットを半透明にする。", + "translucent-widgets-label": "半透明のウィジェット" }, "wallpaper": { "automation-change-mode-alphabetical": "アルファベット順", @@ -1742,6 +1819,7 @@ "select-prompt": "以下から壁紙を選択します", "subheader": "美しい背景で雰囲気を演出しましょう。" }, + "welcome": "ようこそ", "welcome-note": "まずは基本設定から始めましょう。詳細なオプションは「設定」にあります。", "welcome-subtitle": "デスクトップをあなただけのものにしましょう", "welcome-title": "Noctaliaへようこそ!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "{widget} の設定" }, "system-monitor": { + "core-usage": "コア {id} 使用率", "cpu-temp": "CPU温度", "cpu-usage": "CPU使用率", "disk": "ディスク", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "アドレスをクリップボードにコピーしました", + "auto-connect-disabled": "自動接続が無効です", + "auto-connect-enabled": "自動接続が有効です", + "auto-connecting": "{count} 台のデバイスに接続中...", "confirm-code": "別のデバイスでコード{value}を確認してください。", "connect-failed": "デバイスに接続できませんでした", "disconnect-failed": "デバイスから切断できませんでした", @@ -1800,6 +1882,10 @@ "unavailable": "クリップボード履歴を利用できません", "unavailable-desc": "cliphist がインストールされていません。クリップボード履歴機能を使用するにはインストールしてください" }, + "custom-command-failed": { + "description": "コマンドが失敗しました: {command}\\n終了コード: {code}", + "title": "カスタムコマンドが失敗しました" + }, "do-not-disturb": { "disabled": "おやすみモードを無効化", "disabled-desc": "すべての通知を表示します", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "ウィジェットを追加", + "auto-connect": "このデバイスの自動接続を切り替える", + "bluetooth-auto-connect-off": "自動接続が無効です", + "bluetooth-auto-connect-on": "自動接続が有効です", "bluetooth-devices": "Bluetooth デバイス", "brightness-at": "画面の明るさ:{brightness}%", "click-to-start-recording": "画面録画(録画開始)", @@ -1951,6 +2040,11 @@ "resolution-label": "解像度", "resolution-mode-label": "モード", "solid-color-tooltip": "無地", + "sort-date-asc": "古い順に並べ替え", + "sort-date-desc": "新しい順に並べ替え", + "sort-name-asc": "名前でソート (A-Z)", + "sort-name-desc": "名前順に並べ替え (Z-A)", + "sort-random": "ランダムで並べ替え", "sorting-date-added": "追加日", "sorting-favorites": "お気に入り数", "sorting-label": "並べ替え", diff --git a/Assets/Translations/ko-KR.json b/Assets/Translations/ko-KR.json index 731b104d1..fce4865d6 100644 --- a/Assets/Translations/ko-KR.json +++ b/Assets/Translations/ko-KR.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "비주얼라이저의 색상을 선택합니다.", "color-name-label": "색으로 채우기", + "height-description": "사용자 지정 구성 요소 너비.", "hide-when-idle-description": "활성화하면 재생기가 재생 중이지 않을 때 비주얼라이저를 숨깁니다.", "hide-when-idle-label": "미디어 재생 중이 아닐 때 숨기기", "width-description": "구성 요소의 너비를 사용자 지정합니다." @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "텍스트 값 대신 미니 막대 차트로 통계를 표시합니다. 레이아웃이 변경되는 것을 방지합니다.", "compact-mode-label": "소형 모드", + "cpu-cores-description": "CPU 코어 사용량을 개별적으로 표시합니다.", + "cpu-cores-label": "CPU 코어", "cpu-frequency-description": "현재 CPU 클럭 속도를 GHz 단위로 표시합니다.", "cpu-frequency-label": "CPU 주파수 표시", "cpu-temperature-description": "가능한 경우 CPU 온도를 표시합니다.", @@ -291,6 +294,8 @@ "focused-color-label": "포커스된 작업 공간 색상", "follow-focused-screen-description": "바가 위치한 화면 대신 현재 포커스된 화면의 작업 공간을 표시합니다.", "follow-focused-screen-label": "포커스된 화면 따르기", + "font-weight-description": "작업 공간 내 텍스트의 시각적 가중치를 설정합니다.", + "font-weight-label": "글꼴 두께", "grouped-border-opacity-description": "작업 공간 컨테이너 테두리의 불투명도 수준을 설정합니다.", "grouped-border-opacity-label": "테두리 불투명도", "hide-unoccupied-description": "창이 없는 작업 공간을 표시하지 않습니다.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "모든 배터리 (결합됨)", "battery-health": "배터리 수명", "battery-level": "배터리 잔량", "capacity-level": "용량: {level}", @@ -374,6 +380,7 @@ "add": "추가", "appearance": "모양", "apply": "적용", + "auto-connect": "자동 연결", "automation": "자동화", "available": "사용 가능", "back": "뒤로", @@ -401,6 +408,7 @@ "contributors": "기여자", "copied-to-clipboard": "클립보드에 복사됨", "countdown": "카운트다운", + "customize": "사용자 지정", "date": "날짜", "default": "기본값", "delete": "삭제", @@ -423,6 +431,11 @@ "execute": "실행", "faithful": "충실하게", "focus": "포커스", + "font-weight-bold": "굵게", + "font-weight-light": "라이트", + "font-weight-medium": "중간", + "font-weight-regular": "보통", + "font-weight-semibold": "세미볼드", "frequency": "빈도", "gateway": "게이트웨이", "general": "일반", @@ -445,6 +458,7 @@ "lock": "잠금", "logout": "로그아웃", "look": "외관", + "margins": "여백", "media": "미디어", "media-player": "미디어 재생기", "memory": "메모리", @@ -467,6 +481,7 @@ "panels": "패널", "password": "비밀번호", "pause": "일시 정지", + "performance": "성능", "pin": "고정", "play": "재생", "polling": "폴링", @@ -519,6 +534,7 @@ "unpin": "고정 해제", "update": "업데이트", "upload": "업로드", + "userspace-reboot": "사용자 공간 재시작", "version": "버전", "vibrant": "선명하게", "visualizer": "비주얼라이저", @@ -703,16 +719,20 @@ "about": { "become-supporter": "후원자 되기", "changelog": "변경 로그 보기", + "changelog-on-startup": "업데이트 시 변경 로그 표시", + "changelog-on-startup-desc": "Noctalia 업데이트 시 변경 로그를 자동으로 표시합니다.", "contributors-description": "{count}명의 멋진 기여자에게 감사를 전합니다!", "contributors-description-plural": "{count}명의 멋진 기여자들에게 감사를 전합니다!", "copy-info": "정보 복사", "debug-disabled": "디버그 모드 비활성화됨", "debug-enabled": "디버그 모드 활성화됨", "info-copied": "정보가 클립보드에 복사됨", + "noctalia-available": "사용 가능:", "noctalia-desc": "Wayland를 위해 세심하게 제작된 매끄럽고 미니멀한 데스크톱 셸로, Quickshell로 빌드되었습니다.", "noctalia-git-commit": "Git 커밋:", "noctalia-installed-version": "설치된 버전:", "noctalia-latest-version": "최신 버전:", + "noctalia-qs-version": "Noctalia QS 버전:", "noctalia-title": "Noctalia 셸", "privacy-policy": "개인정보 처리방침", "support": "후원하기", @@ -720,6 +740,7 @@ "supporters-desc": "멋진 후원자분께 진심으로 감사드립니다!", "supporters-desc-plural": "저희의 {count}명의 멋진 후원자분들께 진심으로 감사드립니다!", "supporters-loading": "후원자 불러오는 중...", + "system-board": "메인보드:", "system-cpu": "CPU:", "system-disk": "디스크:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "바 밀도", "appearance-desc": "바의 모양과 위치를 사용자 지정합니다.", "appearance-display-mode-description": "바가 언제 표시될지 선택하세요.", + "appearance-enable-exclusion-zone-inset-description": "제외 영역을 1 물리 픽셀만큼 줄여 Bar 가장자리 아래로 창이 완벽하게 겹치도록 합니다.", + "appearance-enable-exclusion-zone-inset-label": "인셋 제외 영역", "appearance-floating-description": "바를 플로팅 '알약' 형태로 표시합니다.", "appearance-floating-label": "플로팅 바", "appearance-font-scale-description": "바에 표시되는 텍스트의 글꼴 크기 배율을 조정합니다.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "컴포지터 개요(Overview)가 활성 상태일 때 바를 숨기고 패널을 닫습니다.", "appearance-hide-on-overview-label": "개요에서 바 숨기기", "appearance-margins-description": "플로팅 바 주위의 여백을 조정합니다.", - "appearance-margins-horizontal": "수평", - "appearance-margins-label": "여백", - "appearance-margins-vertical": "수직", + "appearance-margins-horizontal": "수평 여백", + "appearance-margins-vertical": "수직 여백", "appearance-outer-corners-description": "바에 바깥쪽으로 굽은 모서리를 표시합니다.", "appearance-outer-corners-label": "외부 모서리", "appearance-position-description": "화면에서 바를 배치할 위치를 선택하세요.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "별도 바 불투명도 사용", "appearance-widget-spacing-description": "바의 각 위젯 사이 간격을 조정합니다.", "appearance-widget-spacing-label": "위젯 간격", + "behavior-middle-click-command-description": "가운데 클릭 시 실행할 명령.", + "behavior-middle-click-command-label": "가운데 클릭 명령", + "behavior-middle-click-command-placeholder": "niri msg action 개요 전환", + "behavior-middle-click-description": "바의 빈 영역에서 가운데 클릭 시 수행할 작업을 선택합니다.", + "behavior-middle-click-follow-mouse-description": "선택한 가운데 클릭 패널을 커서 위치에 엽니다.", + "behavior-middle-click-follow-mouse-label": "가운데 클릭 시 마우스 따라가기", + "behavior-middle-click-label": "바 가운데 클릭 동작", + "behavior-right-click-command-description": "마우스 오른쪽 클릭 시 실행할 명령.", + "behavior-right-click-command-label": "오른쪽 클릭 명령", + "behavior-right-click-command-placeholder": "notify-send \"오른쪽 클릭\"", + "behavior-right-click-description": "바의 빈 영역에서 마우스 오른쪽 버튼 클릭 시 수행할 작업을 선택하세요.", + "behavior-right-click-follow-mouse-description": "선택한 오른쪽 클릭 패널을 커서 위치에 엽니다.", + "behavior-right-click-follow-mouse-label": "마우스 오른쪽 클릭 시 마우스 따라가기", + "behavior-right-click-label": "바 오른쪽 클릭 동작", + "behavior-wheel-wrap-description": "활성화되면 스크롤이 마지막 항목에서 첫 번째 항목으로 계속됩니다.", + "behavior-wheel-wrap-label": "순환", + "behavior-workspace-scroll-description": "바의 비어 있는 영역에서 마우스 휠이 무엇을 할지 선택하세요.", + "behavior-workspace-scroll-label": "바 마우스 휠 동작", + "behavior-workspace-scroll-option-content": "콘텐츠", + "behavior-workspace-scroll-option-workspace": "작업 공간", "monitor-configure-widgets": "위젯 구성", "monitor-override-settings": "전역 설정 재정의", "monitor-override-settings-description": "이 모니터에 사용자 지정 설정을 사용합니다.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "인증 필요", + "bluetooth-auto-connect-description": "Bluetooth가 활성화되면 신뢰할 수 있는 페어링된 장치에 자동으로 연결합니다.", + "bluetooth-auto-connect-label": "페어링된 기기 자동 연결", "bluetooth-devices-unnamed": "이름 없는 장치는 표시되지 않습니다.", "bluetooth-discoverable": "이 장치는 이 설정 탭이 열려 있는 동안 {hostName}으로 검색될 수 있습니다.", "bluetooth-rssi-polling-description": "bluetoothctl을 통해 연결된 장치의 RSSI를 주기적으로 샘플링합니다. 모든 장치에서 사용 가능하지 않을 수 있으며, 활성화 시 최소한의 리소스를 사용합니다.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "제어 센터에 표시될 컨트롤과 순서를 사용자 지정합니다.", "desc": "제어 센터 패널의 위치와 동작을 구성합니다.", + "open-at-mouse-description": "바의 빈 공간을 오른쪽 클릭하면 커서 근처에 제어 센터를 엽니다. 비활성화된 경우, 대신 위의 위치 설정이 사용됩니다.", + "open-at-mouse-label": "마우스 오른쪽 클릭 시 커서 위치에 열기", "position-description": "제어 센터 패널이 열릴 때 나타날 위치를 선택하세요.", "shortcuts-custom-button-command-description": "버튼을 클릭했을 때 실행할 명령입니다.", "shortcuts-custom-button-enable-on-state-logic-description": "검사 명령에 따라 두 번째 아이콘과 '켜짐(hot)' 상태를 활성화합니다.", @@ -983,6 +1029,7 @@ "edit-mode-description": "바탕 화면 위젯을 이동하고 재배치하려면 편집 모드를 활성화하세요. 활성화되면 위젯에 드래그 윤곽선이 표시되며 재배치할 수 있습니다.", "edit-mode-exit-button": "편집 모드 종료", "edit-mode-grid-snap-label": "그리드 스냅", + "edit-mode-grid-snap-scale-label": "그리드 스냅 배율", "edit-mode-label": "편집 모드", "enabled-description": "바탕 화면 위젯을 완전히 활성화하거나 비활성화합니다.", "enabled-label": "바탕 화면 위젯 활성화", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "자동 숨기기", "appearance-display-description": "독의 동작 방식을 선택하세요.", "appearance-display-exclusive": "독점", + "appearance-dock-indicator-description": "독이 숨겨져 있을 때 작은 표시기를 표시합니다.", + "appearance-dock-indicator-label": "독 표시기", "appearance-floating-distance-description": "독과 화면 가장자리 사이의 거리를 설정합니다.", "appearance-floating-distance-label": "독 플로팅 거리", - "appearance-frame-indicator-description": "Dock이 숨겨져 있을 때 프레임에 작은 표시기를 표시합니다.", - "appearance-frame-indicator-label": "프레임 독 표시기", "appearance-group-apps-description": "동일한 앱의 여러 창을 하나의 Dock 항목으로 그룹화합니다.", "appearance-group-apps-label": "동일한 앱 그룹화", "appearance-group-click-action-cycle": "창 전환", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "독 크기", "appearance-inactive-indicators-description": "현재 활성 앱뿐만 아니라 모든 앱에 대해 표시기 알약을 표시합니다.", "appearance-inactive-indicators-label": "실행 중 표시기", + "appearance-indicator-color-description": "숨겨진 독 인디케이터의 색상을 선택합니다.", + "appearance-indicator-color-label": "인디케이터 색상", + "appearance-indicator-opacity-description": "숨겨진 독 인디케이터의 불투명도를 조정합니다.", + "appearance-indicator-opacity-label": "인디케이터 불투명도", + "appearance-indicator-thickness-description": "숨겨진 독 인디케이터를 더 두껍게 표시합니다 (3px 대신 6px).", + "appearance-indicator-thickness-label": "더 두꺼운 인디케이터", "appearance-launcher-position-description": "독에서 런처 아이콘이 나타날 위치를 선택하세요.", "appearance-launcher-position-end": "끝", "appearance-launcher-position-label": "런처 위치", @@ -1169,7 +1222,7 @@ }, "hooks": { "info-command-info-description": "• 명령은 셸(sh -lc)을 통해 실행됩니다.
• 명령은 백그라운드(detached)에서 실행됩니다.
• 테스트 버튼은 현재 값으로 실행됩니다.", - "info-parameters-description": "• 배경화면 훅: $1 = 배경화면 경로, $2 = 화면 이름
• 테마 전환 훅: $1 = true/false (다크 모드 상태)
• 화면 잠금/잠금 해제 훅: 매개변수 없음
• 성능 모드 훅: 매개변수 없음
• 세션 훅: $1 = 작업 (shutdown/reboot)", + "info-parameters-description": "• 배경화면 훅: $1 = 배경화면 경로, $2 = 화면 이름
• 테마 전환 훅: $1 = true/false (Dark Mode 상태)
• 화면 잠금/잠금 해제 훅: $1 = lock/unlock (화면 잠금 상태)
• 성능 모드 훅: 매개변수 없음
• 세션 훅: $1 = action (shutdown/reboot)", "info-parameters-label": "사용 가능한 매개변수", "noctalia-started-description": "Noctalia 로딩이 완료되었을 때 실행할 명령입니다.", "noctalia-started-label": "Noctalia 시작됨", @@ -1206,7 +1259,14 @@ "custom-description": "일정 시간 비활성 상태 후 셸 명령을 실행합니다.", "custom-entry-command": "명령", "custom-entry-delete": "삭제", + "custom-entry-edit": "사용자 지정 명령 편집", + "custom-entry-name": "이름", + "custom-entry-name-placeholder": "예: 조명 끄기", + "custom-entry-new": "새 사용자 지정 명령", "custom-entry-timeout": "비활성 시간", + "custom-entry-timeout-format": "{count} 초", + "custom-entry-timeout-format-plural": "{count} 초", + "custom-entry-unnamed": "이름 없는 명령어", "custom-label": "사용자 지정 유휴 명령", "enable-description": "일정 시간 비활성 상태 후 자동으로 화면을 끄거나, 잠그거나, 절전 모드로 전환합니다.", "enable-label": "유휴 관리 활성화", @@ -1214,15 +1274,17 @@ "fade-duration-label": "페이드 지속 시간", "lock-description": "잠금 화면이 활성화되기 전 비활성 시간(초).", "lock-label": "화면 잠금", + "resume-command-label": "명령 재개", "screen-off-description": "모니터가 꺼지기 전 비활성 시간(초).", "screen-off-label": "화면 끄기", - "status-description": "Compositor가 보고한 유휴 시간.", + "status-description": "컴포지터가 보고한 유휴 시간.", "status-label": "유휴 시간", "suspend-description": "시스템이 일시 중단되기 전 비활성 시간(초).", "tab-behavior": "동작", "tab-custom": "사용자 지정", - "timeouts-description": "단계를 비활성화하려면 0으로 설정하세요. Keep Awake가 활성화된 동안 시간 초과는 일시 중지됩니다.", + "timeouts-description": "단계를 비활성화하려면 0으로 설정하세요. 깨어 있기 가 활성화된 동안 시간 초과는 일시 중지됩니다.", "timeouts-label": "시간 초과", + "title": "유휴", "unavailable": "이 Compositor에서는 네이티브 유휴 모니터링을 사용할 수 없습니다." }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "시계 스타일", "compact-lockscreen-description": "로그인 입력과 시스템 제어만 표시하고 날씨 및 미디어 위젯을 숨깁니다.", "compact-lockscreen-label": "컴팩트 잠금 화면", + "enable-lockscreen-media-controls-description": "잠금 화면에 대화형 미디어 재생 제어를 표시합니다.", + "enable-lockscreen-media-controls-label": "잠금 화면 미디어 제어", "lock-on-suspend-description": "시스템 대기 모드 진입 시 자동으로 화면을 잠급니다.", "lock-on-suspend-label": "대기 모드 시 잠금", "lock-screen-animations-description": "잠금 화면 애니메이션 활성화 또는 비활성화.", @@ -1438,7 +1502,7 @@ "duration-desc": "OSD가 자동으로 숨겨지기 전까지 표시되는 시간입니다.", "duration-title": "자동 숨기기 시간 제한", "enabled-description": "볼륨 및 밝기 변경 사항을 실시간으로 표시합니다.", - "enabled-label": "OSD(화면 표시) 활성화", + "enabled-label": "OSD (화면 표시) 활성화", "events-desc": "OSD를 트리거할 이벤트를 선택하세요.", "general-desc": "OSD의 가시성과 동작을 구성합니다.", "location-description": "화면 표시가 나타나는 위치입니다.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "세션 옵션에 키 바인딩 힌트를 표시합니다.", "show-keybinds-label": "단축키 표시" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Noctalia 성능 모드가 활성화된 동안 데스크톱 위젯을 계속 표시합니다.", + "noctalia-performance-disable-desktop-widgets-label": "성능 모드에서 데스크톱 위젯 활성화", + "noctalia-performance-disable-wallpaper-description": "Noctalia 성능 모드가 활성화된 동안 데스크톱, 개요, 잠금 화면 배경화면을 계속 표시합니다.", + "noctalia-performance-disable-wallpaper-label": "성능 모드에서 배경화면 렌더링 활성화", + "title": "시스템" + }, "system-monitor": { "critical-color-label": "위험 색상", "custom-highlight-colors-title-label": "사용자 지정 강조 색상", @@ -1573,8 +1644,8 @@ "polling-interval-label": "폴링 간격", "polling-section-description": "각 시스템 값을 갱신하는 빈도를 정합니다.", "polling-section-label": "폴링 간격", - "threshold-critical": "위험 임계값", - "threshold-warning": "경고 임계값", + "threshold-critical": "심각", + "threshold-warning": "경고", "thresholds-section-description": "각 시스템 값에 대한 경고/위험 임계값을 조정합니다.", "use-custom-highlight-colors-description": "비활성화하면 테마 기본 강조 색상이 사용됩니다.", "use-custom-highlight-colors-label": "사용자 지정 강조 색상 사용", @@ -1589,6 +1660,8 @@ "animation-speed-label": "애니메이션 속도", "animation-speed-reset": "애니메이션 속도 초기화", "appearance-desc": "툴팁, 테두리, 그림자와 같은 시각적 요소를 사용자 지정합니다.", + "blur-behind-description": "컴포지터 블러 프로토콜을 사용하여 바와 패널 뒤 영역을 흐리게 처리합니다.", + "blur-behind-label": "배경 흐림", "box-border-description": "콘텐츠 영역 주위에 윤곽선을 표시합니다.", "box-border-label": "컨테이너 윤곽선", "box-border-radius-description": "사이드바, 카드, 콘텐츠 패널과 같은 주요 레이아웃 섹션의 모서리 둥글기를 조정합니다.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "패널과 바를 항상 위에 유지", "scaling-description": "바를 제외한 일반 사용자 인터페이스의 크기를 변경합니다.", "scaling-label": "인터페이스 배율", + "scrollbar-always-visible-description": "콘텐츠를 스크롤할 수 있을 때 스크롤바를 항상 표시하고, 마우스 오버 시에만 표시하지 않습니다.", + "scrollbar-always-visible-label": "항상 스크롤바 표시", "settings-panel-header": "설정 패널", "settings-panel-mode-description": "설정 레이아웃을 선택하세요 (다시 열어야 할 수 있음).", "settings-panel-mode-label": "설정 패널 모드", @@ -1619,7 +1694,9 @@ "shadows-label": "그림자", "title": "사용자 인터페이스", "tooltips-description": "인터페이스 전반에 툴팁을 활성화하거나 비활성화합니다.", - "tooltips-label": "툴팁 표시" + "tooltips-label": "툴팁 표시", + "translucent-widgets-description": "패널 내의 버튼, 탭 및 기타 위젯을 반투명하게 만듭니다.", + "translucent-widgets-label": "반투명 위젯" }, "wallpaper": { "automation-change-mode-alphabetical": "알파벳순", @@ -1742,6 +1819,7 @@ "select-prompt": "아래에서 배경화면을 선택하세요", "subheader": "아름다운 배경으로 분위기를 설정하세요." }, + "welcome": "환영합니다", "welcome-note": "시작하기 위한 몇 가지 기본 사항입니다 — 전체 옵션은 설정에 있습니다", "welcome-subtitle": "당신만의 독특한 데스크톱을 만들어 보세요", "welcome-title": "Noctalia에 오신 것을 환영합니다!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "{widget} 설정" }, "system-monitor": { + "core-usage": "코어 {id} 사용량", "cpu-temp": "CPU 온도", "cpu-usage": "CPU 사용량", "disk": "디스크", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "주소가 클립보드에 복사됨", + "auto-connect-disabled": "자동 연결 비활성화됨", + "auto-connect-enabled": "자동 연결 활성화됨", + "auto-connecting": "{count}개의 장치에 연결 중...", "confirm-code": "다른 장치에서 코드 {value}를 확인하세요.", "connect-failed": "장치 연결 실패", "disconnect-failed": "장치 연결 해제 실패", @@ -1800,6 +1882,10 @@ "unavailable": "클립보드 기록 사용 불가", "unavailable-desc": "'cliphist' 애플리케이션이 설치되어 있지 않습니다. 클립보드 기록 기능을 사용하려면 설치하세요" }, + "custom-command-failed": { + "description": "명령 실패: {command}\\n종료 코드: {code}", + "title": "사용자 지정 명령 실패" + }, "do-not-disturb": { "disabled": "방해 금지 모드 비활성화됨", "disabled-desc": "모든 알림 표시 중", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "위젯 추가", + "auto-connect": "이 장치의 자동 연결 토글", + "bluetooth-auto-connect-off": "자동 연결 비활성화됨", + "bluetooth-auto-connect-on": "자동 연결 활성화됨", "bluetooth-devices": "블루투스 장치", "brightness-at": "밝기: {brightness}%", "click-to-start-recording": "화면 녹화 (녹화 시작)", @@ -1951,6 +2040,11 @@ "resolution-label": "해상도", "resolution-mode-label": "모드", "solid-color-tooltip": "단색 배경", + "sort-date-asc": "오래된 순으로 정렬", + "sort-date-desc": "최신 순으로 정렬", + "sort-name-asc": "이름으로 정렬 (A-Z)", + "sort-name-desc": "이름으로 정렬 (Z-A)", + "sort-random": "무작위로 정렬", "sorting-date-added": "추가된 날짜", "sorting-favorites": "즐겨찾기", "sorting-label": "정렬 기준", diff --git a/Assets/Translations/ku.json b/Assets/Translations/ku.json index 0cb5fa464..4591edd90 100644 --- a/Assets/Translations/ku.json +++ b/Assets/Translations/ku.json @@ -60,7 +60,7 @@ "hide-if-not-detected-label": "Dema ku neyê dîtin veşêre", "low-battery-threshold-label": "Asta hişyariya betarî ya kêm", "show-noctalia-performance-description": "Guhêrbarê awaya performansê ya Noctalia di hundirê destgeha betariyê de nîşan bide.
Sî û anîmasyonan di Noctalia de neçalak dike da ku bikaranîna çavkaniyan kêm bike.", - "show-noctalia-performance-label": "Guhêrbarê performansa Noctalia nîşan bide", + "show-noctalia-performance-label": "Guhêrbarê Performansa Noctalia nîşan bide", "show-power-profile-description": "Hilbijartina profîla hêzê di hundirê destgeha betariyê de nîşan bide.", "show-power-profile-label": "Kontrolên profîla hêzê nîşan bide" }, @@ -413,6 +413,7 @@ "lock": "Kilît bike", "logout": "Derkeve", "look": "Xuyang", + "margins": "Qirax", "media": "Medya", "media-player": "Lîstikvanê medyayê", "memory": "Bîr", @@ -761,7 +762,6 @@ "appearance-hide-on-overview-label": "Li ser dîtinê bar veşêre", "appearance-margins-description": "Marginên dora bara herikbar eyar bike.", "appearance-margins-horizontal": "Asoyî", - "appearance-margins-label": "Qirax", "appearance-margins-vertical": "Stûnî", "appearance-outer-corners-description": "Goşeyên derveyî yên çemandî li ser barî nîşan bide.", "appearance-outer-corners-label": "Goşeyên derve", @@ -1016,7 +1016,7 @@ "language-select-auto-detect": "Xweber", "language-select-description": "Zimanê ku di navrûya sepanê de tê bikaranîn hilbijêre.", "language-select-label": "Zimanê sepanê", - "launch-setup-wizard": "Sêrbazê sazkirinê bide destpêkirin", + "launch-setup-wizard": "Sêrbazê sazkirinê", "profile-desc": "Hûrgilyên bikarhêner û avatarê xwe biguherîne.", "profile-picture-description": "Wêneyê profîla we ku li seranserê navrûyê xuya dibe.", "profile-picture-label": "Wêneyê profîlê", @@ -1040,7 +1040,7 @@ "info-parameters-description": "• Hooka dîwêr: $1 = rêya dîwêr, $2 = navê ekranê
• Hooka guherîna mijarê: $1 = rast/derew (rewşa moda tarî)
• Hookên kilîtkirin/vekirina ekranê: Parametre tune
• Hookên moda performansê: Parametre tune
• Hooka danişînê: $1 = çalakî (girtin/ji nû ve destpêkirin)", "info-parameters-label": "Parametreyên berdest", "noctalia-started-description": "Fermana ku dema Noctalia barkirina xwe qedand pêk were.", - "noctalia-started-label": "Noctalia Hêla Destpêkirin", + "noctalia-started-label": "Noctalia hêla destpêkirin", "noctalia-started-placeholder": "mînakî notify-send 'Noctalia hate barkirin'", "performance-mode-disabled-description": "Fermana ku dema moda performansê ya Noctalia bêbandor dibe, tê meşandin.", "performance-mode-disabled-label": "Rewaya kar rawestayî ye", @@ -1256,12 +1256,12 @@ "duration-desc": "Çiqas dirêj OSD berî ku bixweber veşêre, xuya dimîne.", "duration-title": "Demjimêra veşartina otomatîk", "enabled-description": "Guhertinên deng û ronahiyê di dema rast de nîşan bide.", - "enabled-label": "Dîmendera ser ekranê çalak bike", + "enabled-label": "Pêşandana ser ekranê (OSD) çalak bike", "events-desc": "Bûyerên ku pêşandana ser ekranê çalak dikin hilbijêre.", "general-desc": "Vebijêrkên dîtin û tevgera OSD'ê saz bike.", - "location-description": "Li ku derê dîmenderên ser ekranê xuya dibin.", + "location-description": "Li ku derê pêşandanên ser ekranê xuya dibin.", "monitors-desc": "OSD li ser çavdêrên taybet nîşan bide. Ger tu neyên hilbijartin, bi awayekî xwerû li ser hemûyan nîşan dide.", - "title": "Nîşandana ser dîmenderê", + "title": "Pêşandana ser ekranê", "types-brightness-description": "Dîmendera OSD nîşan bide dema ronahiya ekranê diguhere.", "types-brightness-label": "Ronahî", "types-custom-text-description": "OSD ya nivîsandeyên xwerû yên ji IPC'yê nîşan bide.", @@ -1521,7 +1521,7 @@ }, "skip-setup": "Sazkirinê Derbas Bike", "telemetry-wizard-done": "Gihîşt!", - "telemetry-wizard-note": "Hûn di kontrolê de ne - hûn dikarin vê yekê di mîhengan de her dem çalak an neçalak bikin", + "telemetry-wizard-note": "Hûn di kontrolê de ne — hûn dikarin vê yekê di mîhengan de her dem çalak an neçalak bikin", "telemetry-wizard-subtitle": "Me analîtîkên anonîm zêde kirin da ku alîkariya başkirina Noctalia bikin", "telemetry-wizard-title": "Nûkirina Nepenîtiyê", "wallpaper": { @@ -1618,7 +1618,7 @@ "noctalia-performance": { "disabled": "Rewşa performansê hat betalkirin", "enabled": "Rewşa performansê çalak bû", - "label": "Performansa Noctalîa" + "label": "Performansa Noctalia" }, "power-profile": { "changed": "Profîla hêzê hate guhertin", diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index 545fde545..ac9bd115e 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Selecteer de kleur voor de visualizer.", "color-name-label": "Vulkleur", + "height-description": "Aangepaste componentbreedte.", "hide-when-idle-description": "Indien ingeschakeld wordt de visualizer verborgen, tenzij een speler actief afspeelt.", "hide-when-idle-label": "Verbergen wanneer geen media speelt", "width-description": "Aangepaste componentbreedte." @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Statistieken weergeven als mini-staafdiagrammen in plaats van tekstwaarden. Voorkomt lay-outverschuiving.", "compact-mode-label": "Compacte modus", + "cpu-cores-description": "Het gebruik van CPU-kernen individueel weergeven.", + "cpu-cores-label": "CPU-kernen", "cpu-frequency-description": "De huidige CPU-kloksnelheid in GHz weergeven.", "cpu-frequency-label": "CPU-frequentie weergeven", "cpu-temperature-description": "Toon CPU-temperatuur indien beschikbaar.", @@ -291,6 +294,8 @@ "focused-color-label": "Kleur van de gefocuste Workspace", "follow-focused-screen-description": "Werkruimten weergeven van het momenteel gefocuste scherm, in plaats van het scherm waar de balk zich bevindt.", "follow-focused-screen-label": "Gefocust scherm volgen", + "font-weight-description": "Stel het visuele gewicht in voor tekst binnen de werkruimte.", + "font-weight-label": "Lettertypegewicht", "grouped-border-opacity-description": "Stel het dekkingsniveau in voor de randen van de werkruimtecontainer.", "grouped-border-opacity-label": "Randdekking", "hide-unoccupied-description": "Werkruimten zonder vensters niet weergeven.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Alle accu's (gecombineerd)", "battery-health": "Accuconditie", "battery-level": "Accuniveau", "capacity-level": "Capaciteit: {level}", @@ -374,6 +380,7 @@ "add": "Toevoegen", "appearance": "Uiterlijk", "apply": "Toepassen", + "auto-connect": "Automatisch verbinden", "automation": "Automatisering", "available": "Beschikbaar", "back": "Terug", @@ -401,6 +408,7 @@ "contributors": "Medewerkers", "copied-to-clipboard": "Gekopieerd naar klembord", "countdown": "Aftellen", + "customize": "Aanpassen", "date": "Datum", "default": "Standaard", "delete": "Verwijderen", @@ -423,6 +431,11 @@ "execute": "Uitvoeren", "faithful": "Getrouw", "focus": "Focus", + "font-weight-bold": "Vet", + "font-weight-light": "Licht", + "font-weight-medium": "Medium", + "font-weight-regular": "Normaal", + "font-weight-semibold": "Semi-vet", "frequency": "Frequentie", "gateway": "Poort", "general": "Algemeen", @@ -445,6 +458,7 @@ "lock": "Vergrendelen", "logout": "Uitloggen", "look": "Uiterlijk", + "margins": "Marges", "media": "Media", "media-player": "Mediaplayer", "memory": "Geheugen", @@ -467,6 +481,7 @@ "panels": "Panelen", "password": "Wachtwoord", "pause": "Pauze", + "performance": "Prestaties", "pin": "Vastmaken", "play": "Afspelen", "polling": "Polling", @@ -519,6 +534,7 @@ "unpin": "Losmaken", "update": "Update", "upload": "Uploaden", + "userspace-reboot": "Gebruikersruimte herstarten", "version": "Versie", "vibrant": "Levendig", "visualizer": "Visualiseerder", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Word supporter", "changelog": "Bekijk wijzigingslogboek", + "changelog-on-startup": "Toon wijzigingslogboek bij update", + "changelog-on-startup-desc": "Toon automatisch de changelog wanneer Noctalia wordt bijgewerkt.", "contributors-description": "Een shout-out naar onze {count} geweldige bijdrager!", "contributors-description-plural": "Een shout-out naar onze {count} geweldige bijdragers!", "copy-info": "Kopieer info", "debug-disabled": "Foutopsporingsmodus uitgeschakeld", "debug-enabled": "Foutopsporingsmodus ingeschakeld", "info-copied": "Info gekopieerd naar klembord", + "noctalia-available": "Beschikbaar:", "noctalia-desc": "Een strakke en minimale desktopshell, met zorg gemaakt voor Wayland en gebouwd met Quickshell.", "noctalia-git-commit": "Git commit:", "noctalia-installed-version": "Geïnstalleerde versie:", "noctalia-latest-version": "Nieuwste versie:", + "noctalia-qs-version": "Noctalia QS versie:", "noctalia-title": "Noctalia-shell", "privacy-policy": "Privacybeleid", "support": "Steun ons", @@ -720,6 +740,7 @@ "supporters-desc": "Een enorme dank aan onze geweldige supporter!", "supporters-desc-plural": "Enorm bedankt aan onze {count} geweldige supporters!", "supporters-loading": "Donateurs laden...", + "system-board": "Moederbord:", "system-cpu": "CPU:", "system-disk": "Schijf:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "Balkdichtheid", "appearance-desc": "Pas de uitstraling en positie van de balk aan.", "appearance-display-mode-description": "Kies wanneer de balk zichtbaar is.", + "appearance-enable-exclusion-zone-inset-description": "Verklein de uitsluitingszone met 1 fysieke pixel, zodat gelijkgelegen vensters perfect onder de rand van de Bar overlappen.", + "appearance-enable-exclusion-zone-inset-label": "Inspringende uitsluitingszone", "appearance-floating-description": "Toon de balk weer als een zwevende 'pil'.", "appearance-floating-label": "Zwevende balk", "appearance-font-scale-description": "De schaal van de lettergrootte aanpassen voor tekst die in de balk wordt weergegeven.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Verberg de balk en sluit panelen wanneer het compositoroverzicht actief is.", "appearance-hide-on-overview-label": "Balk verbergen in overzicht", "appearance-margins-description": "Pas de marges rond de zwevende balk aan.", - "appearance-margins-horizontal": "Horizontaal", - "appearance-margins-label": "Marges", - "appearance-margins-vertical": "Verticaal", + "appearance-margins-horizontal": "Horizontale marge", + "appearance-margins-vertical": "Verticale marge", "appearance-outer-corners-description": "Toon naar buiten afgeronde hoeken op de balk weer.", "appearance-outer-corners-label": "Buitenhoeken", "appearance-position-description": "Kies waar je de balk op het scherm wilt plaatsen.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Gebruik afzonderlijke balkdekking", "appearance-widget-spacing-description": "Pas de afstand tussen elke widget in de balk aan.", "appearance-widget-spacing-label": "Widgetafstand", + "behavior-middle-click-command-description": "Commando om uit te voeren bij middelklik.", + "behavior-middle-click-command-label": "Middelklik Commando", + "behavior-middle-click-command-placeholder": "niri msg action overzicht schakelen", + "behavior-middle-click-description": "Kies wat een middenklik doet op lege gebieden van de Balk.", + "behavior-middle-click-follow-mouse-description": "Open het geselecteerde middenklik-paneel op de cursorpositie.", + "behavior-middle-click-follow-mouse-label": "Middenklik volgt muis", + "behavior-middle-click-label": "Balk middenklik actie", + "behavior-right-click-command-description": "Commando om uit te voeren bij rechtermuisklik.", + "behavior-right-click-command-label": "Rechtermuisklikopdracht", + "behavior-right-click-command-placeholder": "notify-send \"Rechtermuisklik\"", + "behavior-right-click-description": "Kies wat een rechtermuisklik doet op lege gebieden van de balk.", + "behavior-right-click-follow-mouse-description": "Open het geselecteerde rechtermuisknop-paneel op de cursorpositie.", + "behavior-right-click-follow-mouse-label": "Rechtermuisklik volgt muis", + "behavior-right-click-label": "Actie bij rechtermuisklik op de Balk", + "behavior-wheel-wrap-description": "Indien ingeschakeld, gaat het scrollen door van het laatste naar het eerste item.", + "behavior-wheel-wrap-label": "Doorlopen", + "behavior-workspace-scroll-description": "Kies wat het muiswiel doet op lege gebieden van de balk.", + "behavior-workspace-scroll-label": "Muiswielactie van de Balk", + "behavior-workspace-scroll-option-content": "Inhoud", + "behavior-workspace-scroll-option-workspace": "Werkruimte", "monitor-configure-widgets": "Widgets configureren", "monitor-override-settings": "Globale instellingen overschrijven", "monitor-override-settings-description": "Gebruik aangepaste instellingen voor deze monitor.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Authenticatie vereist", + "bluetooth-auto-connect-description": "Automatisch verbinding maken met vertrouwde gekoppelde apparaten wanneer Bluetooth is ingeschakeld.", + "bluetooth-auto-connect-label": "Gekoppelde apparaten automatisch verbinden", "bluetooth-devices-unnamed": "Naamloze apparaten worden niet getoond.", "bluetooth-discoverable": "Dit apparaat is vindbaar als {hostName} zolang dit instellingen-tabblad geopend is.", "bluetooth-rssi-polling-description": "Periodiek RSSI van verbonden apparaten via bluetoothctl samplen. Mogelijk niet beschikbaar voor alle apparaten; gebruikt minimale middelen wanneer ingeschakeld.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Pas aan welke bedieningselementen in het bedieningscentrum verschijnen en in welke volgorde.", "desc": "Configureer de positie en het gedrag van het bedieningscentrumpaneel.", + "open-at-mouse-description": "Bij een rechtermuisklik op een leeg gedeelte van de Bar, open het Controlecentrum dicht bij de cursor. Indien uitgeschakeld, wordt de bovenstaande positie-instelling gebruikt.", + "open-at-mouse-label": "Openen bij cursor met rechtermuisklik", "position-description": "Kies waar het bedieningscentrumpaneel verschijnt wanneer het wordt geopend.", "shortcuts-custom-button-command-description": "Het commando dat wordt uitgevoerd wanneer op de knop wordt geklikt.", "shortcuts-custom-button-enable-on-state-logic-description": "Schakel een tweede pictogram en aan-status in op basis van een controlecommando.", @@ -983,6 +1029,7 @@ "edit-mode-description": "Schakel de bewerkingsmodus in om desktopwidgets te verplaatsen en te herpositioneren. In de ingeschakelde modus tonen widgets een sleepomtrek en kunnen ze worden verplaatst.", "edit-mode-exit-button": "Bewerkingsmodus verlaten", "edit-mode-grid-snap-label": "Raster uitlijnen", + "edit-mode-grid-snap-scale-label": "Rasteruitlijningsschaal", "edit-mode-label": "Bewerkingsmodus", "enabled-description": "Desktopwidgets volledig in- of uitschakelen.", "enabled-label": "Desktopwidgets inschakelen", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Automatisch verbergen", "appearance-display-description": "Kies hoe de dock zich gedraagt.", "appearance-display-exclusive": "Exclusief", + "appearance-dock-indicator-description": "Toon een kleine indicator wanneer het dock verborgen is.", + "appearance-dock-indicator-label": "Dock-indicator", "appearance-floating-distance-description": "Stel de afstand in tussen de dock en de rand van het scherm.", "appearance-floating-distance-label": "Afstand zwevende dock", - "appearance-frame-indicator-description": "Toon een kleine indicator op het frame wanneer het dock verborgen is.", - "appearance-frame-indicator-label": "Ingelijste dock-indicator", "appearance-group-apps-description": "Groepeer meerdere vensters van dezelfde app in één Dock-item.", "appearance-group-apps-label": "Dezelfde apps groeperen", "appearance-group-click-action-cycle": "Vensters wisselen", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Grootte van de dock", "appearance-inactive-indicators-description": "Toon indicatorpillen voor alle apps, niet alleen de actieve.", "appearance-inactive-indicators-label": "Lopende indicatoren", + "appearance-indicator-color-description": "Kies de kleur van de indicator voor het verborgen dock.", + "appearance-indicator-color-label": "Indicatorkleur", + "appearance-indicator-opacity-description": "Pas de ondoorzichtigheid van de indicator voor het verborgen dock aan.", + "appearance-indicator-opacity-label": "Ondoorzichtigheid van de indicator", + "appearance-indicator-thickness-description": "Schakel een dikkere indicator in voor het verborgen dock (6px in plaats van 3px).", + "appearance-indicator-thickness-label": "Dikkere indicator", "appearance-launcher-position-description": "Kies waar het starter-icoon in het dock verschijnt.", "appearance-launcher-position-end": "Einde", "appearance-launcher-position-label": "Positie van de starter", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Automatisch", "language-select-description": "Selecteer de taal die in de interface van de applicatie wordt gebruikt.", "language-select-label": "Applicatietaal", - "launch-setup-wizard": "Het configuratieprogramma starten", + "launch-setup-wizard": "Installatiewizard", "profile-desc": "Bewerk je gebruikersgegevens en avatar.", "profile-picture-description": "Je profielfoto die overal in de interface verschijnt.", "profile-picture-label": "Profielfoto", @@ -1169,7 +1222,7 @@ }, "hooks": { "info-command-info-description": "• Commando's worden uitgevoerd via shell (sh -lc)
• Commando's draaien op de achtergrond (losgekoppeld)
• Testknoppen worden uitgevoerd met de huidige waarden", - "info-parameters-description": "• Achtergrond-hook: $1 = pad naar achtergrond, $2 = schermnaam
• Thema-schakel-hook: $1 = true/false (Dark Mode-status)
• Schermvergrendeling/ontgrendeling-hooks: Geen parameters
• Prestatiemodus-hooks: Geen parameters
• Sessie-hook: $1 = actie (afsluiten/opnieuw opstarten)", + "info-parameters-description": "• Achtergrond-hook: $1 = achtergrondpad, $2 = schermnaam
• Themawissel-hook: $1 = true/false (Dark Mode-status)
• Schermvergrendeling/ontgrendeling-hooks: $1 = lock/unlock (schermvergrendelingsstatus)
• Prestatie-modus-hooks: Geen parameters
• Sessie-hook: $1 = action (shutdown/reboot)", "info-parameters-label": "Beschikbare parameters", "noctalia-started-description": "Opdracht die moet worden uitgevoerd wanneer Noctalia volledig is geladen.", "noctalia-started-label": "Noctalia gestart", @@ -1206,23 +1259,32 @@ "custom-description": "Voer een shell-commando uit na een periode van inactiviteit.", "custom-entry-command": "Commando", "custom-entry-delete": "Verwijderen", + "custom-entry-edit": "Aangepaste Opdracht Bewerken", + "custom-entry-name": "Naam", + "custom-entry-name-placeholder": "bijv. Lichten uitdoen", + "custom-entry-new": "Nieuwe Aangepaste Opdracht", "custom-entry-timeout": "Inactiviteitstijd", - "custom-label": "Aangepaste Inactiviteitscommando's", - "enable-description": "Schakel het scherm automatisch uit, vergrendel of zet in slaapstand na een periode van inactiviteit.", + "custom-entry-timeout-format": "{count} seconde", + "custom-entry-timeout-format-plural": "{count} seconden", + "custom-entry-unnamed": "Naamloos commando", + "custom-label": "Aangepaste inactiviteitscommando's", + "enable-description": "Schakel het scherm automatisch uit, vergrendel of onderbreek na een periode van inactiviteit.", "enable-label": "Inactiviteitsbeheer inschakelen", "fade-duration-description": "Seconden voor de fade-to-black animatie voordat elke actie wordt geactiveerd. Elke muisbeweging annuleert de fade.", "fade-duration-label": "Vervagingsduur", "lock-description": "Seconden van inactiviteit voordat het vergrendelscherm wordt geactiveerd.", "lock-label": "Scherm vergrendelen", + "resume-command-label": "Commando hervatten", "screen-off-description": "Seconden van inactiviteit voordat monitoren worden uitgeschakeld.", "screen-off-label": "Scherm uitschakelen", - "status-description": "Inactiviteitstijd zoals gerapporteerd door de Compositor.", + "status-description": "Inactiviteitstijd zoals gerapporteerd door de compositor.", "status-label": "Inactiviteitstijd", - "suspend-description": "Seconden van inactiviteit voordat het systeem in slaapstand gaat.", + "suspend-description": "Seconden van inactiviteit voordat het systeem onderbreekt.", "tab-behavior": "Gedrag", "tab-custom": "Aangepast", - "timeouts-description": "Stel in op 0 om een fase uit te schakelen. Time-outs worden gepauzeerd zolang Keep Awake actief is.", + "timeouts-description": "Stel in op 0 om een fase uit te schakelen. Time-outs worden gepauzeerd zolang 'Wakker houden' actief is.", "timeouts-label": "Time-outs", + "title": "Inactiviteit", "unavailable": "Native inactiviteitsmonitoring is niet beschikbaar op deze Compositor." }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "Klokstijl", "compact-lockscreen-description": "Toon alleen de logininvoer en systeemknoppen, verberg weer- en mediawidgets.", "compact-lockscreen-label": "Compact vergrendelscherm", + "enable-lockscreen-media-controls-description": "Toon interactieve mediabediening op het vergrendelscherm.", + "enable-lockscreen-media-controls-label": "Mediabediening op het Vergrendelscherm", "lock-on-suspend-description": "Vergrendel het scherm automatisch wanneer het systeem wordt onderbroken.", "lock-on-suspend-label": "Vergrendelen bij onderbreken", "lock-screen-animations-description": "Vergrendelschermanimaties in- of uitschakelen.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Toon toetscombinatiehints bij sessie-opties.", "show-keybinds-label": "Toetsencombinaties weergeven" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Houd desktopwidgets zichtbaar terwijl de Noctalia prestatiemodus is ingeschakeld.", + "noctalia-performance-disable-desktop-widgets-label": "Desktopwidgets inschakelen in prestatiemodus", + "noctalia-performance-disable-wallpaper-description": "Houd bureaublad-, overzicht- en vergrendelschermachtergronden zichtbaar terwijl de Noctalia prestatiemodus is ingeschakeld.", + "noctalia-performance-disable-wallpaper-label": "Achtergrondweergave inschakelen in prestatiemodus", + "title": "Systeem" + }, "system-monitor": { "critical-color-label": "Kritische kleur", "custom-highlight-colors-title-label": "Aangepaste markeerkleuren", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Peilingsinterval", "polling-section-description": "Configureer hoe vaak elke systeemmetriek wordt bijgewerkt.", "polling-section-label": "Peilingsintervallen", - "threshold-critical": "Kritische drempel", - "threshold-warning": "Waarschuwingsdrempel", + "threshold-critical": "Kritiek", + "threshold-warning": "Waarschuwing", "thresholds-section-description": "Pas de waarschuwing/kritieke drempels en de pollingsintervallen voor elke systeemmetriek aan.", "use-custom-highlight-colors-description": "Indien uitgeschakeld, worden de standaard markeerkleuren van het thema gebruikt.", "use-custom-highlight-colors-label": "Gebruik aangepaste markeerkleuren", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Animatiesnelheid", "animation-speed-reset": "Animatiesnelheid resetten", "appearance-desc": "Pas visuele elementen aan, zoals tooltips, randen en schaduwen.", + "blur-behind-description": "Vervaagt het gebied achter balken en panelen met behulp van het compositor-blurprotocol.", + "blur-behind-label": "Achtergrond vervagen", "box-border-description": "Toon een omlijning rond inhoudsgebieden.", "box-border-label": "Containeromtrek", "box-border-radius-description": "Past de hoekronding aan van belangrijke lay-outsecties, zoals zijbalken, kaarten en inhoudspanelen.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Panelen & balk bovenop houden", "scaling-description": "Wijzigt de grootte van de algemene gebruikersinterface, exclusief de balk.", "scaling-label": "Interfaceschaling", + "scrollbar-always-visible-description": "Houd schuifbalken altijd zichtbaar wanneer inhoud scrollbaar is, in plaats van ze alleen bij hover te tonen.", + "scrollbar-always-visible-label": "Schuifbalken altijd weergeven", "settings-panel-header": "Instellingenpaneel", "settings-panel-mode-description": "Kies lay-out voor instellingen (mogelijk opnieuw openen vereist).", "settings-panel-mode-label": "Instellingenpaneelmodus", @@ -1619,7 +1694,9 @@ "shadows-label": "Slagschaduwen", "title": "Gebruikersinterface", "tooltips-description": "Schakel tooltips in of uit in de hele interface.", - "tooltips-label": "Tooltips tonen" + "tooltips-label": "Tooltips tonen", + "translucent-widgets-description": "Maak knoppen, tabbladen en andere widgets in panelen semi-transparant.", + "translucent-widgets-label": "Doorzichtige widgets" }, "wallpaper": { "automation-change-mode-alphabetical": "Alfabetisch", @@ -1742,6 +1819,7 @@ "select-prompt": "Selecteer hieronder een achtergrond", "subheader": "Zet de toon met een mooie achtergrond." }, + "welcome": "Welkom", "welcome-note": "Een paar basisinstellingen om je op weg te helpen — alle opties vind je in instellingen", "welcome-subtitle": "Laten we uw bureaublad uniek maken", "welcome-title": "Welkom bij Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "Instellingen voor {widget}" }, "system-monitor": { + "core-usage": "Kern {id} gebruik", "cpu-temp": "CPU-temperatuur", "cpu-usage": "CPU-gebruik", "disk": "Schijf", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Adres gekopieerd naar klembord", + "auto-connect-disabled": "Automatisch verbinden uitgeschakeld", + "auto-connect-enabled": "Automatisch verbinden ingeschakeld", + "auto-connecting": "Verbinding maken met {count} appara(a)t(en)...", "confirm-code": "Bevestig code {value} op het andere apparaat.", "connect-failed": "Verbinding met apparaat mislukt", "disconnect-failed": "Verbinding verbreken met apparaat mislukt", @@ -1800,6 +1882,10 @@ "unavailable": "Klembordgeschiedenis niet beschikbaar", "unavailable-desc": "De toepassing 'cliphist' is niet geïnstalleerd. Installeer deze om functies voor klembordgeschiedenis te gebruiken" }, + "custom-command-failed": { + "description": "Commando mislukt: {command}\\nExitcode: {code}", + "title": "Aangepaste opdracht mislukt" + }, "do-not-disturb": { "disabled": "'Niet storen' uitgeschakeld", "disabled-desc": "Alle meldingen worden weergegeven", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Widget toevoegen", + "auto-connect": "Automatisch verbinden voor dit apparaat in-/uitschakelen", + "bluetooth-auto-connect-off": "Automatisch verbinden uitgeschakeld", + "bluetooth-auto-connect-on": "Automatisch verbinden ingeschakeld", "bluetooth-devices": "Bluetooth-apparaten", "brightness-at": "Helderheid: {brightness}%", "click-to-start-recording": "Schermrecorder (opname starten)", @@ -1951,6 +2040,11 @@ "resolution-label": "Resolutie", "resolution-mode-label": "Modus", "solid-color-tooltip": "Effen kleur achtergrond", + "sort-date-asc": "Sorteren op oudste eerst", + "sort-date-desc": "Sorteren op nieuwste eerst", + "sort-name-asc": "Sorteren op naam (A-Z)", + "sort-name-desc": "Sorteren op naam (Z-A)", + "sort-random": "Sorteer willekeurig", "sorting-date-added": "Datum toegevoegd", "sorting-favorites": "Favorieten", "sorting-label": "Sorteren op", diff --git a/Assets/Translations/nn-NO.json b/Assets/Translations/nn-NO.json index 153bfee75..39a0a688f 100644 --- a/Assets/Translations/nn-NO.json +++ b/Assets/Translations/nn-NO.json @@ -60,7 +60,7 @@ "hide-if-not-detected-label": "Gøym når ikkje funnen", "low-battery-threshold-label": "Varslingsterskel for lågt batterinivå", "show-noctalia-performance-description": "Viser ytingsmodusbrytaren for Noctalia i batterifanen.
Slår av skuggar og animasjonar i Noctalia for å minka ressursbruk.", - "show-noctalia-performance-label": "Vis ytingsmodusbrytaren for Noctalia", + "show-noctalia-performance-label": "Vis Noctalia Yting-brytaren", "show-power-profile-description": "Viser veljaren for energiprofil i batterifanen.", "show-power-profile-label": "Vis kontrollane for energiprofil" }, @@ -271,7 +271,7 @@ "colorize-icons-label": "Farga ikon", "drawer-enabled-description": "Når dette er på, viser ufeste systempanelobjekt seg i eit skuffepanel.
Når dette er avslege, viser alle systempanelobjekt seg i rad.", "drawer-enabled-label": "Aktiver skuff", - "hide-passive-description": "Når dette er på, gøymer systempanelobjekt med \"passiv\" status seg.", + "hide-passive-description": "Når dette er på, gøymer systempanelobjekt med 'passiv' status seg.", "hide-passive-label": "Gøym passive objekt" }, "volume": { @@ -439,6 +439,7 @@ "lock": "Lås", "logout": "Skriv ut", "look": "Sjå", + "margins": "Margar", "media": "Media", "media-player": "Mediaavspelar", "memory": "Minne", @@ -797,7 +798,7 @@ "appearance-density-label": "Tettleik på oppgåvelina", "appearance-desc": "Måtar til kor oppgåvelina ser ut og kvar ho er.", "appearance-display-mode-description": "Vel når baren er synleg.", - "appearance-floating-description": "Viser oppgåvelina som flytande \"pille\".", + "appearance-floating-description": "Viser oppgåvelina som flytande 'pille'.", "appearance-floating-label": "Flytande oppgåvelina", "appearance-frame-radius": "Innradius", "appearance-frame-settings-description": "Endrar rammetjukn og innradius på hyrnet", @@ -807,7 +808,6 @@ "appearance-hide-on-overview-label": "Gøym opggåvelina i oversyn", "appearance-margins-description": "Endrar på margane kring den flytande oppgåvelina.", "appearance-margins-horizontal": "Vassrett", - "appearance-margins-label": "Margar", "appearance-margins-vertical": "Loddrett", "appearance-outer-corners-description": "Viser hyrner som bøygjer ut på oppgåvelina.", "appearance-outer-corners-label": "Ytterhyrne", @@ -915,14 +915,14 @@ "desc": "Styrer stad og åtferd for kontrollsenteret.", "position-description": "Vel kvar på skjermen kontrollsenteret opnar seg.", "shortcuts-custom-button-command-description": "Kommandoen som køyrer når du klikkar på knappen.", - "shortcuts-custom-button-enable-on-state-logic-description": "Slå på eit anna ikon og \"varmt\" status frå ein sjekkekommando.", + "shortcuts-custom-button-enable-on-state-logic-description": "Slå på eit anna ikon og 'varmt' status frå ein sjekkekommando.", "shortcuts-custom-button-enable-on-state-logic-label": "Slå på \"på-stode\"-logikk", "shortcuts-custom-button-icon-description": "Vel eit ikon frå biblioteket.", "shortcuts-custom-button-on-clicked-label": "Kommando på venstreklikk", "shortcuts-custom-button-on-middle-clicked-description": "Kommando som køyrer når knappen er midtklikka.", - "shortcuts-custom-button-on-state-command-description": "Kommando som køyrer for å sjå om knappen er i \"på\"-stoda.", + "shortcuts-custom-button-on-state-command-description": "Kommando som køyrer for å sjå om knappen er i 'på'-stoda.", "shortcuts-custom-button-on-state-command-label": "På-stode sjekkekommando", - "shortcuts-custom-button-on-state-icon-description": "Ikonet for knappen når han er i \"på\"-stoda.", + "shortcuts-custom-button-on-state-icon-description": "Ikonet for knappen når han er i 'på'-stoda.", "shortcuts-custom-button-on-state-icon-label": "På-stode ikon", "shortcuts-custom-button-state-checks-add": "Legg til stodesjekk", "shortcuts-custom-button-state-checks-command": "Kommando som køyrer for denne stodesjekken.", @@ -1039,16 +1039,22 @@ "appearance-display-auto-hide": "Gøym automatisk", "appearance-display-description": "Vel korleis festepunktet ter seg", "appearance-display-exclusive": "Uthaldande", + "appearance-dock-indicator-description": "Syn ein framgangsvisar når festepunktet er gøymt.", + "appearance-dock-indicator-label": "Festepunktvisar", "appearance-floating-distance-description": "Set avstanden mellom festepunktet og skjermranda.", "appearance-floating-distance-label": "Festepunkt flyteavstand", - "appearance-frame-indicator-description": "Syn ein framgangsvisar på ramma når festepunktet er gøymt.", - "appearance-frame-indicator-label": "Festepunktvisar på ramma", "appearance-hide-show-speed-description": "Endrar på kor fort festepunktet viser og gøymer seg.", "appearance-hide-show-speed-label": "Vis/gøym fart", "appearance-icon-size-description": "Endra på kor stor festepunktet er.", "appearance-icon-size-label": "Storleik", "appearance-inactive-indicators-description": "Viser ei pille for kvar app, ikkje berre den opne.", "appearance-inactive-indicators-label": "Køyreteikn", + "appearance-indicator-color-description": "Vel fargen på indikatoren for den skjulte dokken.", + "appearance-indicator-color-label": "Indikatorfarge", + "appearance-indicator-opacity-description": "Juster gjennomsiktigheita for indikatoren for den skjulte dokken.", + "appearance-indicator-opacity-label": "Gjennomsiktigheit for indikator", + "appearance-indicator-thickness-description": "Bruk ein tjukkare indikator for den skjulte dokken (6 px i staden for 3 px).", + "appearance-indicator-thickness-label": "Tjukkare indikator", "appearance-pinned-static-description": "Alltid sender feste appikon til venstre i statisk rekkjefylgd.", "appearance-pinned-static-label": "Statiske feste appar", "appearance-position-description": "Vel kvar festepunktet står på skjermen.", @@ -1608,7 +1614,7 @@ "enter-command": "Skriv inn kommando som skal køyra (app eller eigendefinert skript)", "enter-ipc-identifier": "Skriv inn unik identifikator for IPC kommandoar", "enter-path": "Skriv inn filbane...", - "enter-text-to-collapse": "t.d. 'ingenting spelar av. Bruk /regex/ til mønster.", + "enter-text-to-collapse": "t.d. 'ingenting spelar av'. Bruk /regex/ til mønster.", "enter-tooltip": "Skriv inn hjelpetekst", "enter-width-pixels": "Skriv inn breidde i piksel", "keybind-recording": "Tek opp beinknapp...", diff --git a/Assets/Translations/pl.json b/Assets/Translations/pl.json index f11d1f327..0f31bb9e3 100644 --- a/Assets/Translations/pl.json +++ b/Assets/Translations/pl.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Wybierz kolor dla wizualizatora.", "color-name-label": "Kolor wypełnienia", + "height-description": "Niestandardowa szerokość komponentu.", "hide-when-idle-description": "Gdy włączone, wizualizator jest ukryty, chyba że odtwarzacz jest aktywny.", "hide-when-idle-label": "Ukryj, gdy nic nie jest odtwarzane", "width-description": "Niestandardowa szerokość komponentu." @@ -61,7 +62,7 @@ "hide-if-not-detected-label": "Ukryj się, jeśli nie wykryto", "low-battery-threshold-label": "Próg ostrzegania o niskim poziomie baterii", "show-noctalia-performance-description": "Wyświetl przełącznik trybu wydajności Noctalia w panelu baterii.
Wyłącza cienie i animacje w Noctalia, aby zmniejszyć zużycie zasobów.", - "show-noctalia-performance-label": "Pokaż przełącznik wydajności Noctalia", + "show-noctalia-performance-label": "Pokaż przełącznik Wydajności Noctalia", "show-power-profile-description": "Wyświetl wybór profilu zasilania w panelu baterii.", "show-power-profile-label": "Pokaż kontrolki profilu zasilania" }, @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Wyświetlaj statystyki jako mini wykresy słupkowe zamiast wartości tekstowych. Zapobiega przesunięciom układu.", "compact-mode-label": "Tryb kompaktowy", + "cpu-cores-description": "Wyświetlaj użycie rdzeni CPU indywidualnie.", + "cpu-cores-label": "Rdzenie CPU", "cpu-frequency-description": "Wyświetl aktualną prędkość zegara CPU w GHz.", "cpu-frequency-label": "Pokaż częstotliwość CPU", "cpu-temperature-description": "Pokaż odczyty temperatury procesora (jeśli dostępne).", @@ -291,6 +294,8 @@ "focused-color-label": "Kolor aktywnego obszaru roboczego", "follow-focused-screen-description": "Wyświetl obszary robocze z aktualnie aktywnego ekranu, zamiast ekranu, na którym znajduje się pasek.", "follow-focused-screen-label": "Podążaj za skupionym ekranem", + "font-weight-description": "Ustaw wizualną wagę tekstu w obszarze roboczym.", + "font-weight-label": "Grubość czcionki", "grouped-border-opacity-description": "Ustaw poziom krycia dla obramowań kontenerów obszaru roboczego.", "grouped-border-opacity-label": "Krycie obramowania", "hide-unoccupied-description": "Nie wyświetlaj obszarów roboczych bez okien.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Wszystkie baterie (łącznie)", "battery-health": "Stan baterii", "battery-level": "Poziom baterii", "capacity-level": "Pojemność: {level}", @@ -374,6 +380,7 @@ "add": "Dodaj", "appearance": "Wygląd", "apply": "Zastosuj", + "auto-connect": "Automatyczne łączenie", "automation": "Automatyzacja", "available": "Dostępne", "back": "Powrót", @@ -401,6 +408,7 @@ "contributors": "Współtwórcy", "copied-to-clipboard": "Skopiowano do schowka", "countdown": "Odliczanie", + "customize": "Dostosuj", "date": "Data", "default": "Domyślny", "delete": "Usuń", @@ -423,6 +431,11 @@ "execute": "Wykonaj", "faithful": "Wierny", "focus": "Skupienie", + "font-weight-bold": "Pogrubienie", + "font-weight-light": "Cienka", + "font-weight-medium": "Średni", + "font-weight-regular": "Zwykły", + "font-weight-semibold": "Półgruby", "frequency": "Częstotliwość", "gateway": "Brama", "general": "Ogólne", @@ -445,6 +458,7 @@ "lock": "Zablokuj", "logout": "Wyloguj", "look": "Wygląd", + "margins": "Marginesy", "media": "Media", "media-player": "Odtwarzacz multimedialny", "memory": "Pamięć", @@ -467,6 +481,7 @@ "panels": "Panele", "password": "Hasło", "pause": "Pauza", + "performance": "Wydajność", "pin": "Przypnij", "play": "Odtwórz", "polling": "Odpytywanie", @@ -519,6 +534,7 @@ "unpin": "Odepnij", "update": "Aktualizacja", "upload": "Wyślij", + "userspace-reboot": "Uruchom ponownie przestrzeń użytkownika", "version": "Wersja", "vibrant": "Żywy", "visualizer": "Wizualizator", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Zostań wspierającym", "changelog": "Zobacz dziennik zmian", + "changelog-on-startup": "Pokaż dziennik zmian po aktualizacji", + "changelog-on-startup-desc": "Automatycznie pokazuj dziennik zmian po aktualizacji Noctalia.", "contributors-description": "Podziękowania dla naszego {count} niesamowitego współtwórcy!", "contributors-description-plural": "Podziękowania dla naszych {count} niesamowitych współtwórców!", "copy-info": "Kopiuj informacje", "debug-disabled": "Tryb debugowania wyłączony", "debug-enabled": "Tryb debugowania włączony", "info-copied": "Informacje skopiowane do schowka", + "noctalia-available": "Dostępne:", "noctalia-desc": "Elegancka i minimalistyczna powłoka pulpitu stworzona dla Wayland, zbudowana w Quickshell.", "noctalia-git-commit": "Git commit:", "noctalia-installed-version": "Zainstalowana wersja:", "noctalia-latest-version": "Najnowsza wersja:", + "noctalia-qs-version": "Noctalia QS wersja:", "noctalia-title": "Noctalia shell", "privacy-policy": "Polityka prywatności", "support": "Wesprzyj nas", @@ -720,6 +740,7 @@ "supporters-desc": "Ogromne podziękowania dla naszego wspaniałego wspierającego!", "supporters-desc-plural": "Ogromne podziękowania dla naszych {count} wspaniałych wspierających!", "supporters-loading": "Ładowanie wspierających...", + "system-board": "Płyta główna:", "system-cpu": "Procesor:", "system-disk": "Dysk:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "Zagęszczenie paska", "appearance-desc": "Dostosuj wygląd i pozycję paska.", "appearance-display-mode-description": "Wybierz, kiedy pasek jest widoczny.", + "appearance-enable-exclusion-zone-inset-description": "Zmniejsz strefę wykluczenia o 1 fizyczny piksel, aby okna przylegające idealnie zachodziły pod krawędź Bar.", + "appearance-enable-exclusion-zone-inset-label": "Wewnętrzna strefa wykluczenia", "appearance-floating-description": "Wyświetl pasek jako pływającą 'pigułkę'.", "appearance-floating-label": "Pływający pasek", "appearance-font-scale-description": "Dostosuj skalę rozmiaru czcionki dla tekstu wyświetlanego na pasku.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Ukryj pasek i zamknij panele, gdy aktywny jest przegląd kompozytora.", "appearance-hide-on-overview-label": "Ukryj pasek w przeglądzie", "appearance-margins-description": "Dostosuj marginesy wokół pływającego paska.", - "appearance-margins-horizontal": "Poziome", - "appearance-margins-label": "Marginesy", - "appearance-margins-vertical": "Pionowe", + "appearance-margins-horizontal": "Margines poziomy", + "appearance-margins-vertical": "Margines pionowy", "appearance-outer-corners-description": "Wyświetl zaokrąglone narożniki na zewnątrz paska.", "appearance-outer-corners-label": "Narożniki zewnętrzne", "appearance-position-description": "Wybierz miejsce umieszczenia paska na ekranie.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Użyj osobnej przezroczystości słupków", "appearance-widget-spacing-description": "Dostosuj odstępy między każdym widżetem na pasku.", "appearance-widget-spacing-label": "Odstępy między widżetami", + "behavior-middle-click-command-description": "Polecenie do wykonania po środkowym kliknięciu.", + "behavior-middle-click-command-label": "Polecenie Środkowego Kliknięcia", + "behavior-middle-click-command-placeholder": "niri msg action przełącz przegląd", + "behavior-middle-click-description": "Wybierz, co robi środkowe kliknięcie na pustych obszarach Paska.", + "behavior-middle-click-follow-mouse-description": "Otwórz wybrany panel środkowego kliknięcia w pozycji kursora.", + "behavior-middle-click-follow-mouse-label": "Środkowe kliknięcie podąża za kursorem", + "behavior-middle-click-label": "Akcja środkowego kliknięcia Paska", + "behavior-right-click-command-description": "Polecenie do wykonania po kliknięciu prawym przyciskiem.", + "behavior-right-click-command-label": "Polecenie Prawego Przycisku Myszy", + "behavior-right-click-command-placeholder": "notify-send \"Prawy przycisk myszy\"", + "behavior-right-click-description": "Wybierz, co robi prawy przycisk myszy na pustych obszarach paska.", + "behavior-right-click-follow-mouse-description": "Otwórz wybrany panel prawego przycisku myszy w pozycji kursora.", + "behavior-right-click-follow-mouse-label": "Prawy przycisk myszy podąża za kursorem", + "behavior-right-click-label": "Akcja prawego przycisku myszy na Pasku", + "behavior-wheel-wrap-description": "Po włączeniu przewijanie kontynuuje się od ostatniego elementu do pierwszego.", + "behavior-wheel-wrap-label": "Cyklicznie", + "behavior-workspace-scroll-description": "Wybierz, co robi kółko myszy na pustych obszarach paska.", + "behavior-workspace-scroll-label": "Akcja kółka myszy Paska", + "behavior-workspace-scroll-option-content": "Treść", + "behavior-workspace-scroll-option-workspace": "Obszar roboczy", "monitor-configure-widgets": "Skonfiguruj widżety", "monitor-override-settings": "Zastąp ustawienia globalne", "monitor-override-settings-description": "Użyj niestandardowych ustawień dla tego monitora.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Wymagana autentykacja", + "bluetooth-auto-connect-description": "Automatycznie łącz się z zaufanymi sparowanymi urządzeniami, gdy Bluetooth jest włączony.", + "bluetooth-auto-connect-label": "Automatyczne łączenie sparowanych urządzeń", "bluetooth-devices-unnamed": "Nienazwane urządzenia nie są wyświetlane.", "bluetooth-discoverable": "To urządzenie jest wykrywalne jako {hostName}, dopóki ta zakładka ustawień jest otwarta.", "bluetooth-rssi-polling-description": "Okresowo próbkuje RSSI dla podłączonych urządzeń za pośrednictwem bluetoothctl. Może nie być dostępne dla wszystkich urządzeń; zużywa minimalne zasoby po włączeniu.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Dostosuj, które kontrolki pojawiają się w centrum sterowania i w jakiej kolejności.", "desc": "Konfiguruj pozycjonowanie i zachowanie panelu centrum sterowania.", + "open-at-mouse-description": "Po kliknięciu prawym przyciskiem myszy na pusty obszar Bar, otwórz Centrum Sterowania blisko kursora. Gdy wyłączone, zamiast tego używane jest powyższe ustawienie pozycji.", + "open-at-mouse-label": "Otwórz w miejscu kursora po prawym kliknięciu", "position-description": "Wybierz, gdzie pojawia się panel centrum sterowania po otwarciu.", "shortcuts-custom-button-command-description": "Polecenie do wykonania po kliknięciu przycisku.", "shortcuts-custom-button-enable-on-state-logic-description": "Włącz drugą ikonę i stan 'aktywny' na podstawie polecenia sprawdzającego.", @@ -983,6 +1029,7 @@ "edit-mode-description": "Włącz tryb edycji, aby przesuwać i zmieniać pozycję widżetów pulpitu. Gdy włączone, widżety pokazują ramkę przeciągania.", "edit-mode-exit-button": "Wyjdź z trybu edycji", "edit-mode-grid-snap-label": "Przyciągaj do siatki", + "edit-mode-grid-snap-scale-label": "Skala przyciągania do siatki", "edit-mode-label": "Tryb edycji", "enabled-description": "Włącz lub całkowicie wyłącz widżety pulpitu.", "enabled-label": "Włącz widżety pulpitu", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Auto-ukrywanie", "appearance-display-description": "Wybierz, jak ma zachowywać się dok.", "appearance-display-exclusive": "Ekskluzywny", + "appearance-dock-indicator-description": "Pokaż mały wskaźnik, gdy dock jest ukryty.", + "appearance-dock-indicator-label": "Wskaźnik docka", "appearance-floating-distance-description": "Ustaw odległość między dokiem a krawędzią ekranu.", "appearance-floating-distance-label": "Odległość pływania doku", - "appearance-frame-indicator-description": "Pokaż mały wskaźnik na ramce, gdy dock jest ukryty.", - "appearance-frame-indicator-label": "Wskaźnik docka w ramce", "appearance-group-apps-description": "Grupuj wiele okien z tej samej aplikacji w jeden wpis Docka.", "appearance-group-apps-label": "Grupuj te same aplikacje", "appearance-group-click-action-cycle": "Przełączaj okna", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Rozmiar doku", "appearance-inactive-indicators-description": "Wyświetlaj wskaźniki dla wszystkich uruchomionych aplikacji, nie tylko dla aktywnej.", "appearance-inactive-indicators-label": "Wskaźniki uruchomionych", + "appearance-indicator-color-description": "Wybierz kolor indykatora ukrytego doku.", + "appearance-indicator-color-label": "Kolor indykatora", + "appearance-indicator-opacity-description": "Dostosuj przezroczystość indykatora ukrytego doku.", + "appearance-indicator-opacity-label": "Przezroczystość indykatora", + "appearance-indicator-thickness-description": "Włącz grubszy indykator ukrytego doku (6 px zamiast 3 px).", + "appearance-indicator-thickness-label": "Grubszy indykator", "appearance-launcher-position-description": "Wybierz, gdzie ikona programu uruchamiającego pojawi się w docku.", "appearance-launcher-position-end": "Koniec", "appearance-launcher-position-label": "Pozycja uruchamiacza", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Automatyczny", "language-select-description": "Wybierz język używany w interfejsie aplikacji.", "language-select-label": "Język aplikacji", - "launch-setup-wizard": "Uruchom asystenta konfiguracji", + "launch-setup-wizard": "Kreator konfiguracji", "profile-desc": "Edytuj dane użytkownika i awatar.", "profile-picture-description": "Twoje zdjęcie profilowe pojawiające się w interfejsie.", "profile-picture-label": "Zdjęcie profilowe", @@ -1165,11 +1218,11 @@ "screen-corners-title": "Narożniki ekranu", "settings-copied": "Ustawienia skopiowane do schowka", "tab-basics": "Podstawy", - "tab-keybinds": "Skróty Klawiszowe" + "tab-keybinds": "Skróty klawiszowe" }, "hooks": { "info-command-info-description": "• Polecenia są wykonywane przez powłokę (sh -lc)
• Polecenia działają w tle (detached)
• Przyciski testowe wykonują się z aktualnymi wartościami", - "info-parameters-description": "• Hak do tapety: $1 = ścieżka do tapety, $2 = nazwa ekranu
• Hak przełączania motywu: $1 = prawda/fałsz (stan trybu ciemnego)
• Haki blokowania/odblokowywania ekranu: Brak parametrów
• Haki trybu wydajności: Brak parametrów
• Hak sesji: $1 = akcja (zamknięcie/ponowne uruchomienie))", + "info-parameters-description": "• Hook tapety: $1 = ścieżka tapety, $2 = nazwa ekranu
• Hook przełączania motywu: $1 = true/false (stan Dark Mode)
• Hooki blokowania/odblokowywania ekranu: $1 = lock/unlock (stan blokady ekranu)
• Hooki trybu wydajności: Brak parametrów
• Hook sesji: $1 = action (shutdown/reboot)", "info-parameters-label": "Dostępne parametry", "noctalia-started-description": "Polecenie do wykonania po zakończeniu ładowania Noctalii.", "noctalia-started-label": "Noctalia uruchomiona", @@ -1206,23 +1259,32 @@ "custom-description": "Uruchom polecenie shella po okresie bezczynności.", "custom-entry-command": "Polecenie", "custom-entry-delete": "Usuń", + "custom-entry-edit": "Edytuj Niestandardowe Polecenie", + "custom-entry-name": "Nazwa", + "custom-entry-name-placeholder": "np. Wyłącz światła", + "custom-entry-new": "Nowe Niestandardowe Polecenie", "custom-entry-timeout": "Czas bezczynności", - "custom-label": "Niestandardowe Polecenia Bezczynności", + "custom-entry-timeout-format": "{count} sekunda", + "custom-entry-timeout-format-plural": "{count} sekund", + "custom-entry-unnamed": "Nienazwana komenda", + "custom-label": "Niestandardowe polecenia bezczynności", "enable-description": "Automatycznie wyłącz ekran, zablokuj lub uśpij po okresie bezczynności.", "enable-label": "Włącz zarządzanie bezczynnością", "fade-duration-description": "Sekundy animacji wygaszania do czerni przed uruchomieniem każdej akcji. Każdy ruch myszy anuluje wygaszanie.", "fade-duration-label": "Czas trwania zanikania", "lock-description": "Sekundy bezczynności przed aktywacją ekranu blokady.", "lock-label": "Zablokuj ekran", + "resume-command-label": "Wznów polecenie", "screen-off-description": "Sekundy bezczynności, zanim monitory zostaną wyłączone.", "screen-off-label": "Wyłącz ekran", - "status-description": "Czas bezczynności zgłoszony przez Compositor.", - "status-label": "Czas Bezczynności", + "status-description": "Czas bezczynności zgłoszony przez compositora.", + "status-label": "Czas bezczynności", "suspend-description": "Sekundy bezczynności przed wstrzymaniem systemu.", "tab-behavior": "Zachowanie", "tab-custom": "Niestandardowy", - "timeouts-description": "Ustaw na 0, aby wyłączyć etap. Limity czasu są wstrzymane, gdy Keep Awake jest aktywny.", - "timeouts-label": "Limity Czasu", + "timeouts-description": "Ustaw na 0, aby wyłączyć etap. Limity czasu są wstrzymane, gdy 'Nie usypiaj' jest aktywny.", + "timeouts-label": "Limity czasu", + "title": "Bezczynność", "unavailable": "Natywne monitorowanie bezczynności nie jest dostępne na tym Compositorze." }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "Styl Zegara", "compact-lockscreen-description": "Pokaż tylko logowanie i kontrolki systemowe, ukrywając widżety pogody i mediów.", "compact-lockscreen-label": "Kompaktowy ekran blokady", + "enable-lockscreen-media-controls-description": "Pokaż interaktywne elementy sterujące odtwarzaniem multimediów na ekranie blokady.", + "enable-lockscreen-media-controls-label": "Sterowanie Multimediami na Ekranie Blokady", "lock-on-suspend-description": "Automatycznie blokuj ekran podczas uśpienia systemu.", "lock-on-suspend-label": "Blokuj przy uśpieniu", "lock-screen-animations-description": "Włącz lub wyłącz animacje ekranu blokady.", @@ -1438,12 +1502,12 @@ "duration-desc": "Jak długo OSD pozostaje widoczne przed automatycznym ukryciem.", "duration-title": "Czas wygasania OSD", "enabled-description": "Pokazuj zmiany głośności i jasności w czasie rzeczywistym.", - "enabled-label": "Włącz OSD (On-screen display)", + "enabled-label": "Włącz wyświetlanie ekranowe (OSD)", "events-desc": "Wybierz, które zdarzenia mają wywoływać wyświetlanie ekranowe.", "general-desc": "Skonfiguruj widoczność i zachowanie OSD.", "location-description": "Miejsce na ekranie, w którym pojawia się OSD.", "monitors-desc": "Pokazuj OSD na konkretnych monitorach. Domyślnie na wszystkich.", - "title": "OSD (On-Screen Display)", + "title": "Wyświetlanie ekranowe", "types-brightness-description": "Pokazuj OSD podczas zmiany jasności ekranu.", "types-brightness-label": "Jasność", "types-custom-text-description": "Pokazuj OSD dla własnych wiadomości tekstowych z IPC.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Wyświetl podpowiedzi skrótów klawiszowych w opcjach sesji.", "show-keybinds-label": "Pokaż skróty klawiszowe" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Zachowaj widoczność widżetów pulpitu, gdy tryb wydajności Noctalia jest włączony.", + "noctalia-performance-disable-desktop-widgets-label": "Włącz widżety pulpitu w trybie wydajności", + "noctalia-performance-disable-wallpaper-description": "Zachowaj widoczne tapety pulpitu, przeglądu i ekranu blokady, gdy tryb wydajności Noctalia jest włączony.", + "noctalia-performance-disable-wallpaper-label": "Włącz renderowanie tapety w trybie wydajności", + "title": "System" + }, "system-monitor": { "critical-color-label": "Kolor krytyczny", "custom-highlight-colors-title-label": "Własne kolory podświetlenia", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Interwał odświeżania", "polling-section-description": "Skonfiguruj, jak często każda metryka systemowa jest aktualizowana.", "polling-section-label": "Interwały odpytywania", - "threshold-critical": "Próg krytyczny", - "threshold-warning": "Próg ostrzegawczy", + "threshold-critical": "Krytyczny", + "threshold-warning": "Ostrzeżenie", "thresholds-section-description": "Dostosuj progi ostrzegawcze/krytyczne oraz interwały odświeżania dla każdej metryki systemu.", "use-custom-highlight-colors-description": "Gdy wyłączone, używane są domyślne kolory podświetlenia motywu.", "use-custom-highlight-colors-label": "Użyj własnych kolorów podświetlenia", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Prędkość animacji", "animation-speed-reset": "Resetuj prędkość animacji", "appearance-desc": "Dostosuj elementy wizualne, takie jak podpowiedzi, obramowania i cienie.", + "blur-behind-description": "Rozmywa obszar za paskami i panelami, używając protokołu rozmycia kompozytora.", + "blur-behind-label": "Rozmycie tła", "box-border-description": "Wyświetla obrys wokół obszarów zawartości.", "box-border-label": "Obrys kontenera", "box-border-radius-description": "Dostosowuje zaokrąglenie narożników głównych sekcji układu, takich jak paski boczne, karty i panele treści.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Trzymaj panele i pasek na wierzchu", "scaling-description": "Zmienia rozmiar ogólnego interfejsu użytkownika, z wyłączeniem paska.", "scaling-label": "Skalowanie interfejsu", + "scrollbar-always-visible-description": "Paski przewijania zawsze widoczne, gdy zawartość jest przewijalna, zamiast pokazywać je tylko po najechaniu myszą.", + "scrollbar-always-visible-label": "Zawsze pokazuj paski przewijania", "settings-panel-header": "Panel Ustawień", "settings-panel-mode-description": "Wybierz układ ustawień (może wymagać ponownego otwarcia).", "settings-panel-mode-label": "Tryb panelu ustawień", @@ -1619,7 +1694,9 @@ "shadows-label": "Cienie", "title": "Interfejs użytkownika", "tooltips-description": "Włącz lub wyłącz podpowiedzi w całym interfejsie.", - "tooltips-label": "Pokaż podpowiedzi" + "tooltips-label": "Pokaż podpowiedzi", + "translucent-widgets-description": "Spraw, aby przyciski, zakładki i inne widżety w panelach były półprzezroczyste.", + "translucent-widgets-label": "Półprzezroczyste widżety" }, "wallpaper": { "automation-change-mode-alphabetical": "Alfabetyczny", @@ -1742,6 +1819,7 @@ "select-prompt": "Wybierz tapetę poniżej", "subheader": "Ustaw nastrój dzięki pięknemu tłu." }, + "welcome": "Witaj", "welcome-note": "Tylko kilka podstaw na początek — pełne opcje znajdują się w ustawieniach", "welcome-subtitle": "Sprawmy, aby Twój pulpit był wyjątkowy", "welcome-title": "Witamy w Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "Ustawienia {widget}" }, "system-monitor": { + "core-usage": "Użycie rdzenia {id}", "cpu-temp": "Temperatura CPU", "cpu-usage": "Użycie CPU", "disk": "Dysk", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Adres skopiowany do schowka", + "auto-connect-disabled": "Automatyczne łączenie wyłączone", + "auto-connect-enabled": "Automatyczne łączenie włączone", + "auto-connecting": "Łączenie z {count} urządzeniem/urządzeniami...", "confirm-code": "Potwierdź kod {value} na drugim urządzeniu.", "connect-failed": "Nie udało się połączyć z urządzeniem", "disconnect-failed": "Nie udało się rozłączyć z urządzeniem", @@ -1800,6 +1882,10 @@ "unavailable": "Historia schowka niedostępna", "unavailable-desc": "Aplikacja 'cliphist' nie jest zainstalowana. Zainstaluj ją, aby korzystać z historii schowka" }, + "custom-command-failed": { + "description": "Polecenie nie powiodło się: {command}\\nKod wyjścia: {code}", + "title": "Niestandardowe polecenie nie powiodło się" + }, "do-not-disturb": { "disabled": "Nie przeszkadzać wyłączone", "disabled-desc": "Pokazywanie wszystkich powiadomień", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Dodaj widżet", + "auto-connect": "Przełącz automatyczne łączenie dla tego urządzenia", + "bluetooth-auto-connect-off": "Automatyczne łączenie wyłączone", + "bluetooth-auto-connect-on": "Automatyczne łączenie włączone", "bluetooth-devices": "Urządzenia Bluetooth", "brightness-at": "Jasność: {brightness}%", "click-to-start-recording": "Nagrywanie ekranu (start)", @@ -1951,6 +2040,11 @@ "resolution-label": "Rozdzielczość", "resolution-mode-label": "Tryb", "solid-color-tooltip": "Jednolite tło kolorystyczne", + "sort-date-asc": "Sortuj od najstarszych", + "sort-date-desc": "Sortuj od najnowszych", + "sort-name-asc": "Sortuj według nazwy (A-Z)", + "sort-name-desc": "Sortuj według nazwy (Z-A)", + "sort-random": "Sortuj losowo", "sorting-date-added": "Data dodania", "sorting-favorites": "Ulubione", "sorting-label": "Sortuj według", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 930b203a7..ba929625a 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Selecione a cor para o visualizador.", "color-name-label": "Cor de preenchimento", + "height-description": "Largura de componente personalizada.", "hide-when-idle-description": "Quando ativado, o visualizador fica oculto a menos que haja reprodução ativa.", "hide-when-idle-label": "Ocultar quando não houver reprodução", "width-description": "Largura do componente personalizado." @@ -61,7 +62,7 @@ "hide-if-not-detected-label": "Ocultar se não detectado", "low-battery-threshold-label": "Limite de aviso de bateria fraca", "show-noctalia-performance-description": "Exibir o interruptor do modo de desempenho Noctalia no painel da bateria.
Desativa sombras e animações no Noctalia para reduzir o uso de recursos.", - "show-noctalia-performance-label": "Mostrar alternância de Noctalia Desempenho", + "show-noctalia-performance-label": "Mostrar alternância Noctalia Desempenho", "show-power-profile-description": "Exibir a seleção de perfil de energia no painel da bateria.", "show-power-profile-label": "Mostrar controles de perfil de energia" }, @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Exibir as estatísticas como mini gráficos de barras em vez de valores de texto. Evita mudanças no layout.", "compact-mode-label": "Modo compacto", + "cpu-cores-description": "Exibir o uso dos núcleos da CPU individualmente.", + "cpu-cores-label": "Núcleos de CPU", "cpu-frequency-description": "Exibir a velocidade de clock atual da CPU em GHz.", "cpu-frequency-label": "Mostrar frequência da CPU", "cpu-temperature-description": "Mostrar as leituras de temperatura da CPU se disponíveis.", @@ -291,6 +294,8 @@ "focused-color-label": "Cor do workspace focado", "follow-focused-screen-description": "Exibir áreas de trabalho da tela atualmente em foco, em vez da tela onde a barra está localizada.", "follow-focused-screen-label": "Seguir tela em foco", + "font-weight-description": "Definir o peso visual para o texto na área de trabalho.", + "font-weight-label": "Peso da fonte", "grouped-border-opacity-description": "Definir o nível de opacidade para as bordas do contêiner do espaço de trabalho.", "grouped-border-opacity-label": "Opacidade da borda", "hide-unoccupied-description": "Não exibir áreas de trabalho sem janelas.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Todas as baterias (combinadas)", "battery-health": "Estado da bateria", "battery-level": "Nível da bateria", "capacity-level": "Capacidade: {level}", @@ -374,6 +380,7 @@ "add": "Adicionar", "appearance": "Aparência", "apply": "Aplicar", + "auto-connect": "Conexão automática", "automation": "Automação", "available": "Disponível", "back": "Voltar", @@ -401,6 +408,7 @@ "contributors": "Colaboradores", "copied-to-clipboard": "Copiado para a área de transferência", "countdown": "Contagem regressiva", + "customize": "Personalizar", "date": "Data", "default": "Padrão", "delete": "Apagar", @@ -423,6 +431,11 @@ "execute": "Executar", "faithful": "Fiel", "focus": "Foco", + "font-weight-bold": "Negrito", + "font-weight-light": "Leve", + "font-weight-medium": "Médio", + "font-weight-regular": "Regular", + "font-weight-semibold": "Seminegrito", "frequency": "Frequência", "gateway": "Porta de entrada", "general": "Geral", @@ -445,6 +458,7 @@ "lock": "Bloquear", "logout": "Sair", "look": "Aparência", + "margins": "Margens", "media": "Mídia", "media-player": "Reprodutor de mídia", "memory": "Memória", @@ -467,6 +481,7 @@ "panels": "Painéis", "password": "Senha", "pause": "Pausar", + "performance": "Desempenho", "pin": "Fixar", "play": "Reproduzir", "polling": "Sondagem", @@ -519,6 +534,7 @@ "unpin": "Desafixar", "update": "Atualização", "upload": "Enviar", + "userspace-reboot": "Reiniciar espaço de usuário", "version": "Versão", "vibrant": "Vibrante", "visualizer": "Visualizador", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Torne-se um apoiador", "changelog": "Ver histórico de alterações", + "changelog-on-startup": "Mostrar changelog na atualização", + "changelog-on-startup-desc": "Mostrar automaticamente o registo de alterações quando o Noctalia for atualizado.", "contributors-description": "Agradecimentos ao nosso incrível colaborador!", "contributors-description-plural": "Agradecimentos aos nossos {count} incríveis colaboradores!", "copy-info": "Copiar informações", "debug-disabled": "Modo de depuração desativado", "debug-enabled": "Modo de depuração ativado", "info-copied": "Informação copiada para a área de transferência", + "noctalia-available": "Disponível:", "noctalia-desc": "Um shell de desktop elegante e minimalista, cuidadosamente criado para Wayland, construído com Quickshell.", "noctalia-git-commit": "Commit Git:", "noctalia-installed-version": "Versão instalada:", "noctalia-latest-version": "Última versão:", + "noctalia-qs-version": "Noctalia QS versão:", "noctalia-title": "Shell Noctalia", "privacy-policy": "Política de privacidade", "support": "Apoie-nos", @@ -720,6 +740,7 @@ "supporters-desc": "Um enorme obrigado ao nosso incrível apoiador!", "supporters-desc-plural": "Um enorme obrigado aos nossos {count} incríveis apoiadores!", "supporters-loading": "Carregando apoiadores...", + "system-board": "Placa-mãe:", "system-cpu": "CPU:", "system-disk": "Disco:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "Densidade da barra", "appearance-desc": "Personalize a aparência e a posição da barra.", "appearance-display-mode-description": "Escolha quando a barra está visível.", + "appearance-enable-exclusion-zone-inset-description": "Reduza a zona de exclusão em 1 pixel físico para que as janelas alinhadas se estendam perfeitamente sob a borda da Bar.", + "appearance-enable-exclusion-zone-inset-label": "Zona de exclusão interna", "appearance-floating-description": "Exibe a barra como uma 'pílula' flutuante.", "appearance-floating-label": "Barra flutuante", "appearance-font-scale-description": "Ajustar a escala do tamanho da fonte para o texto exibido na barra.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Ocultar a barra e fechar os painéis quando a visão geral do compositor estiver ativa.", "appearance-hide-on-overview-label": "Ocultar barra na visão geral", "appearance-margins-description": "Ajuste as margens ao redor da barra flutuante.", - "appearance-margins-horizontal": "Horizontal", - "appearance-margins-label": "Margens", - "appearance-margins-vertical": "Vertical", + "appearance-margins-horizontal": "Margem horizontal", + "appearance-margins-vertical": "Margem vertical", "appearance-outer-corners-description": "Exibe cantos curvados para fora na barra.", "appearance-outer-corners-label": "Cantos externos", "appearance-position-description": "Escolha onde posicionar a barra na tela.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Usar opacidade separada para as barras", "appearance-widget-spacing-description": "Ajusta o espaçamento entre cada widget na barra.", "appearance-widget-spacing-label": "Espaçamento de widgets", + "behavior-middle-click-command-description": "Comando a executar no clique do meio.", + "behavior-middle-click-command-label": "Comando do Clique do Meio", + "behavior-middle-click-command-placeholder": "niri msg action alternar visão geral", + "behavior-middle-click-description": "Escolha o que o clique do meio faz nas áreas vazias da Barra.", + "behavior-middle-click-follow-mouse-description": "Abrir o painel de clique do meio selecionado na posição do cursor.", + "behavior-middle-click-follow-mouse-label": "Clique do meio segue o mouse", + "behavior-middle-click-label": "Ação de clique do meio na Barra", + "behavior-right-click-command-description": "Comando a executar ao clicar com o botão direito.", + "behavior-right-click-command-label": "Comando do Botão Direito", + "behavior-right-click-command-placeholder": "notify-send \"Clicar com o botão direito\"", + "behavior-right-click-description": "Escolha o que o clique direito faz nas áreas vazias da barra.", + "behavior-right-click-follow-mouse-description": "Abrir o painel de clique direito selecionado na posição do cursor.", + "behavior-right-click-follow-mouse-label": "Clique direito segue o rato", + "behavior-right-click-label": "Ação de clique direito na Barra", + "behavior-wheel-wrap-description": "Quando ativado, a rolagem continua do último item para o primeiro.", + "behavior-wheel-wrap-label": "Circular", + "behavior-workspace-scroll-description": "Escolha o que a roda do mouse faz em áreas vazias da barra.", + "behavior-workspace-scroll-label": "Ação da roda do rato da Barra", + "behavior-workspace-scroll-option-content": "Conteúdo", + "behavior-workspace-scroll-option-workspace": "Área de trabalho", "monitor-configure-widgets": "Configurar widgets", "monitor-override-settings": "Substituir configurações globais", "monitor-override-settings-description": "Usar definições personalizadas para este monitor.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Autenticação necessária", + "bluetooth-auto-connect-description": "Conectar automaticamente a dispositivos emparelhados confiáveis quando o Bluetooth estiver ativado.", + "bluetooth-auto-connect-label": "Conectar automaticamente dispositivos emparelhados", "bluetooth-devices-unnamed": "Dispositivos sem nome não são exibidos.", "bluetooth-discoverable": "Este dispositivo é detectável como {hostName} enquanto esta aba de configurações estiver aberta.", "bluetooth-rssi-polling-description": "Amostra periodicamente o RSSI de dispositivos conectados via bluetoothctl. Pode não estar disponível para todos os dispositivos; usa recursos mínimos quando ativado.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Personalize quais controles aparecem na central de controle e em que ordem.", "desc": "Configurar o posicionamento e o comportamento do painel da central de controle.", + "open-at-mouse-description": "Ao clicar com o botão direito numa área vazia da Bar, abre o Centro de Controle perto do cursor. Quando desativado, a definição de posição acima é usada em vez disso.", + "open-at-mouse-label": "Abrir no cursor ao clicar com o botão direito", "position-description": "Escolha onde o painel da central de controle aparece quando aberto.", "shortcuts-custom-button-command-description": "O comando a ser executado quando o botão é clicado.", "shortcuts-custom-button-enable-on-state-logic-description": "Ativa um segundo ícone e um estado 'quente' com base em um comando de verificação.", @@ -980,12 +1026,13 @@ "cpu-intensive-note": "Os widgets marcados com ! usam mais CPU e devem ser ativados com atenção.", "edit-mode-button-label": "Entrar no modo de edição", "edit-mode-controls-explanation": "Clique esquerdo e arraste: Mover ou redimensionar o widget.\nClique direito: Abrir as opções do menu de contexto.", - "edit-mode-description": "Ative o modo de edição para mover e reposicionar widgets da área de trabalho. Quando ativado, os widgets exibem um contorno de arrastar e podem ser reposicionados.", + "edit-mode-description": "Ative o modo de edição para mover e reposicionar widgets do desktop. Quando ativado, os widgets exibem um contorno de arrastar e podem ser reposicionados.", "edit-mode-exit-button": "Sair do modo de edição", "edit-mode-grid-snap-label": "Alinhar à grade", + "edit-mode-grid-snap-scale-label": "Escala de ajuste à grelha", "edit-mode-label": "Modo de edição", - "enabled-description": "Ativar ou desativar widgets da área de trabalho completamente.", - "enabled-label": "Ativar widgets da área de trabalho", + "enabled-description": "Ativar ou desativar widgets do desktop completamente.", + "enabled-label": "Ativar widgets do desktop", "general-desc": "Configure widgets que aparecem na sua área de trabalho.", "general-title": "Widgets para desktop", "media-player-enabled-description": "Mostrar um widget de reprodutor de mídia na área de trabalho.", @@ -998,7 +1045,7 @@ "media-player-show-buttons-label": "Mostrar botões", "media-player-show-visualizer-description": "Mostrar a sobreposição do visualizador de áudio.", "media-player-visualizer-type-description": "Escolha um tipo de visualização para o fundo do reprodutor de mídia na área de trabalho.", - "overview-enabled-description": "Mostrar widgets da área de trabalho enquanto a visão geral do compositor estiver aberta.", + "overview-enabled-description": "Mostrar widgets do desktop enquanto a visão geral do compositor estiver aberta.", "overview-enabled-label": "Mostrar na visão geral", "system-stat-layout-bottom": "Inferior", "system-stat-layout-description": "Escolha como a legenda é exibida em relação ao gráfico.", @@ -1010,7 +1057,7 @@ "system-stat-show-background-label": "Mostrar plano de fundo", "system-stat-stat-type-description": "Escolhe qual estatística do sistema exibir.", "system-stat-stat-type-label": "Tipo de Estatística", - "title": "Widgets da área de trabalho", + "title": "Widgets do desktop", "weather-enabled-description": "Mostrar um widget de clima na área de trabalho.", "weather-enabled-label": "Ativar widget de clima", "weather-show-background-description": "Mostrar o contêiner de fundo para o widget de clima." @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Ocultar automaticamente", "appearance-display-description": "Escolha como a dock se comporta.", "appearance-display-exclusive": "Exclusivo", + "appearance-dock-indicator-description": "Mostrar um pequeno indicador quando o dock estiver oculto.", + "appearance-dock-indicator-label": "Indicador do dock", "appearance-floating-distance-description": "Ajuste a distância de flutuação da borda da tela.", "appearance-floating-distance-label": "Distância de flutuação da dock", - "appearance-frame-indicator-description": "Mostrar um pequeno indicador na moldura quando o dock estiver oculto.", - "appearance-frame-indicator-label": "Indicador de doca emoldurado", "appearance-group-apps-description": "Agrupar várias janelas da mesma app em uma única entrada do Dock.", "appearance-group-apps-label": "Agrupar aplicativos semelhantes", "appearance-group-click-action-cycle": "Alternar janelas", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Tamanho do dock", "appearance-inactive-indicators-description": "Exibir indicadores de pílula para todos os aplicativos, não apenas para o aplicativo ativo no momento.", "appearance-inactive-indicators-label": "Indicadores de execução", + "appearance-indicator-color-description": "Escolha a cor do indicador da dock oculta.", + "appearance-indicator-color-label": "Cor do indicador", + "appearance-indicator-opacity-description": "Ajuste a opacidade do indicador da dock oculta.", + "appearance-indicator-opacity-label": "Opacidade do indicador", + "appearance-indicator-thickness-description": "Ativar um indicador da dock oculta mais espesso (6 px em vez de 3 px).", + "appearance-indicator-thickness-label": "Indicador mais espesso", "appearance-launcher-position-description": "Escolha onde o ícone do lançador aparece no dock.", "appearance-launcher-position-end": "Fim", "appearance-launcher-position-label": "Posição do lançador", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Automático", "language-select-description": "Selecione o idioma usado na interface da aplicação.", "language-select-label": "Idioma da aplicação", - "launch-setup-wizard": "Iniciar o assistente de configuração", + "launch-setup-wizard": "Assistente de configuração", "profile-desc": "Edite os detalhes do seu usuário e avatar.", "profile-picture-description": "Sua foto de perfil que aparece em toda a interface.", "profile-picture-label": "Foto de perfil", @@ -1165,14 +1218,14 @@ "screen-corners-title": "Cantos da tela", "settings-copied": "Configurações copiadas para a área de transferência", "tab-basics": "Básico", - "tab-keybinds": "Atalhos de Teclado" + "tab-keybinds": "Atalhos de teclado" }, "hooks": { "info-command-info-description": "• Comandos são executados via shell (sh -lc)
• Comandos rodam em segundo plano (desanexados)
• Botões de teste executam com os valores atuais", - "info-parameters-description": "• Hook de papel de parede: $1 = caminho do papel de parede, $2 = nome da tela
• Hook de alternância de tema: $1 = verdadeiro/falso (estado do modo escuro)
• Hooks de bloqueio/desbloqueio de tela: Sem parâmetros
• Hooks de modo de desempenho: Sem parâmetros
• Hook de sessão: $1 = ação (desligamento/reinicialização)", + "info-parameters-description": "• Hook de papel de parede: $1 = caminho do papel de parede, $2 = nome da tela
• Hook de alternância de tema: $1 = true/false (estado do Dark Mode)
• Hooks de bloqueio/desbloqueio de tela: $1 = lock/unlock (estado de bloqueio de tela)
• Hooks de modo de desempenho: Sem parâmetros
• Hook de sessão: $1 = action (shutdown/reboot)", "info-parameters-label": "Parâmetros disponíveis", "noctalia-started-description": "Comando para executar quando o Noctalia terminar de carregar.", - "noctalia-started-label": "Noctalia Iniciado", + "noctalia-started-label": "Noctalia iniciado", "noctalia-started-placeholder": "p. ex. notify-send 'Noctalia Carregado'", "performance-mode-disabled-description": "Comando a ser executado quando o modo de desempenho Noctalia é desabilitado.", "performance-mode-disabled-label": "Modo de desempenho desabilitado", @@ -1206,23 +1259,32 @@ "custom-description": "Executa um comando de shell após um período de inatividade.", "custom-entry-command": "Comando", "custom-entry-delete": "Excluir", + "custom-entry-edit": "Editar Comando Personalizado", + "custom-entry-name": "Nome", + "custom-entry-name-placeholder": "p. ex. Desligar luzes", + "custom-entry-new": "Novo Comando Personalizado", "custom-entry-timeout": "Tempo de inatividade", - "custom-label": "Comandos de Inatividade Personalizados", + "custom-entry-timeout-format": "{count} segundo", + "custom-entry-timeout-format-plural": "{count} segundos", + "custom-entry-unnamed": "Comando sem nome", + "custom-label": "Comandos de inatividade personalizados", "enable-description": "Desligar a tela, bloquear ou suspender automaticamente após um período de inatividade.", "enable-label": "Ativar gerenciamento de inatividade", "fade-duration-description": "Segundos para a animação de esmaecimento para preto antes de cada ação ser acionada. Qualquer movimento do rato cancela o esmaecimento.", "fade-duration-label": "Duração do esmaecimento", "lock-description": "Segundos de inatividade antes da ativação da tela de bloqueio.", "lock-label": "Bloquear tela", + "resume-command-label": "Retomar comando", "screen-off-description": "Segundos de inatividade antes que os monitores sejam desligados.", "screen-off-label": "Desligar tela", - "status-description": "Tempo ocioso conforme relatado pelo Compositor.", - "status-label": "Tempo Ocioso", + "status-description": "Tempo ocioso conforme relatado pelo compositor.", + "status-label": "Tempo ocioso", "suspend-description": "Segundos de inatividade antes que o sistema suspenda.", "tab-behavior": "Comportamento", "tab-custom": "Personalizado", - "timeouts-description": "Defina como 0 para desativar uma etapa. Os tempos limite são pausados enquanto o Keep Awake estiver ativo.", - "timeouts-label": "Tempos Limite", + "timeouts-description": "Defina como 0 para desativar uma etapa. Os tempos limite são pausados enquanto o 'Manter acordado' estiver ativo.", + "timeouts-label": "Tempos limite", + "title": "Inatividade", "unavailable": "A monitorização nativa de inatividade não está disponível neste Compositor." }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "Estilo do Relógio", "compact-lockscreen-description": "Mostrar apenas a entrada de login e os controles do sistema, ocultando os widgets de clima e mídia.", "compact-lockscreen-label": "Tela de bloqueio compacta", + "enable-lockscreen-media-controls-description": "Mostrar controles interativos de reprodução de mídia na tela de bloqueio.", + "enable-lockscreen-media-controls-label": "Controles de Mídia do Ecrã de Bloqueio", "lock-on-suspend-description": "Bloquear a tela automaticamente ao suspender o sistema.", "lock-on-suspend-label": "Bloquear ao suspender", "lock-screen-animations-description": "Ativar ou desativar as animações do ecrã de bloqueio.", @@ -1438,7 +1502,7 @@ "duration-desc": "Quanto tempo a OSD permanece visível antes de ocultar automaticamente.", "duration-title": "Tempo para ocultar", "enabled-description": "Mostrar alterações de volume e brilho em tempo real.", - "enabled-label": "Ativar exibição na tela", + "enabled-label": "Ativar exibição na tela (OSD)", "events-desc": "Selecione quais eventos acionam a exibição na tela.", "general-desc": "Configurar a visibilidade e o comportamento do OSD.", "location-description": "Onde a exibição na tela aparece.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Exibir dicas de atalhos de teclado nas opções de sessão.", "show-keybinds-label": "Mostrar atalhos de teclado" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Manter os widgets da área de trabalho visíveis enquanto o modo de desempenho Noctalia estiver ativado.", + "noctalia-performance-disable-desktop-widgets-label": "Ativar widgets de ambiente de trabalho no modo de desempenho", + "noctalia-performance-disable-wallpaper-description": "Mantenha os papéis de parede da área de trabalho, da visão geral e da tela de bloqueio visíveis enquanto o modo de desempenho Noctalia estiver ativado.", + "noctalia-performance-disable-wallpaper-label": "Ativar renderização de papel de parede no modo de desempenho", + "title": "Sistema" + }, "system-monitor": { "critical-color-label": "Cor crítica", "custom-highlight-colors-title-label": "Cores de destaque personalizadas", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Intervalo de pesquisa", "polling-section-description": "Configure a frequência com que cada métrica do sistema é atualizada.", "polling-section-label": "Intervalos de pesquisa", - "threshold-critical": "Limiar crítico", - "threshold-warning": "Limite de alerta", + "threshold-critical": "Crítico", + "threshold-warning": "Aviso", "thresholds-section-description": "Ajuste os limites de aviso/crítico e os intervalos de consulta para cada métrica do sistema.", "use-custom-highlight-colors-description": "Quando desativado, são usadas as cores de destaque padrão do tema.", "use-custom-highlight-colors-label": "Usar cores de destaque personalizadas", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Velocidade da animação", "animation-speed-reset": "Redefinir velocidade da animação", "appearance-desc": "Personalize elementos visuais como dicas de ferramentas, bordas e sombras.", + "blur-behind-description": "Desfoca a área atrás das barras e painéis usando o protocolo de desfoque do compositor.", + "blur-behind-label": "Desfoque de fundo", "box-border-description": "Exibe um contorno ao redor das áreas de conteúdo.", "box-border-label": "Contorno do recipiente", "box-border-radius-description": "Ajusta o arredondamento dos cantos das principais seções do layout, como barras laterais, cards e painéis de conteúdo.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Manter painéis e barra no topo", "scaling-description": "Altera o tamanho da interface geral do usuário, excluindo a barra.", "scaling-label": "Escalonamento da interface", + "scrollbar-always-visible-description": "Manter as barras de rolagem visíveis sempre que o conteúdo for rolável, em vez de mostrá-las apenas ao passar o mouse.", + "scrollbar-always-visible-label": "Sempre mostrar barras de rolagem", "settings-panel-header": "Painel de Definições", "settings-panel-mode-description": "Escolha o layout das configurações (pode ser necessário reabrir).", "settings-panel-mode-label": "Modo do painel de configurações", @@ -1619,7 +1694,9 @@ "shadows-label": "Sombras projetadas", "title": "Interface do usuário", "tooltips-description": "Ativar ou desativar dicas de ferramentas em toda a interface.", - "tooltips-label": "Mostrar dicas de ferramenta" + "tooltips-label": "Mostrar dicas de ferramenta", + "translucent-widgets-description": "Torne botões, abas e outros widgets dentro dos painéis semitransparentes.", + "translucent-widgets-label": "Widgets translúcidos" }, "wallpaper": { "automation-change-mode-alphabetical": "Alfabético", @@ -1742,6 +1819,7 @@ "select-prompt": "Selecione um papel de parede abaixo", "subheader": "Defina o ambiente com um belo fundo." }, + "welcome": "Bem-vindo", "welcome-note": "Apenas alguns ajustes básicos para começar — o restante está em configurações", "welcome-subtitle": "Vamos tornar o seu ambiente de trabalho único", "welcome-title": "Bem-vindo ao Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "Configurações de {widget}" }, "system-monitor": { + "core-usage": "Uso do núcleo {id}", "cpu-temp": "Temperatura da CPU", "cpu-usage": "Uso da CPU", "disk": "Disco", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Endereço copiado para a área de transferência", + "auto-connect-disabled": "Conexão automática desativada", + "auto-connect-enabled": "Conexão automática ativada", + "auto-connecting": "A conectar a {count} dispositivo(s)...", "confirm-code": "Confirme o código {value} no outro dispositivo.", "connect-failed": "Falha ao conectar ao dispositivo", "disconnect-failed": "Falha ao desconectar do dispositivo", @@ -1800,6 +1882,10 @@ "unavailable": "Histórico da área de transferência indisponível", "unavailable-desc": "O aplicativo 'cliphist' não está instalado. Por favor, instale-o para usar os recursos do histórico da área de transferência" }, + "custom-command-failed": { + "description": "Comando falhou: {command}\\nCódigo de saída: {code}", + "title": "Comando personalizado falhou" + }, "do-not-disturb": { "disabled": "'Não perturbe' desativado", "disabled-desc": "Mostrando todas as notificações", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Adicionar widget", + "auto-connect": "Alternar conexão automática para este dispositivo", + "bluetooth-auto-connect-off": "Conexão automática desativada", + "bluetooth-auto-connect-on": "Conexão automática ativada", "bluetooth-devices": "Dispositivos Bluetooth", "brightness-at": "Brilho: {brightness}%", "click-to-start-recording": "Gravador de tela (iniciar gravação)", @@ -1951,6 +2040,11 @@ "resolution-label": "Resolução", "resolution-mode-label": "Modo", "solid-color-tooltip": "Fundo de cor sólida", + "sort-date-asc": "Ordenar por mais antigo primeiro", + "sort-date-desc": "Ordenar por mais recente primeiro", + "sort-name-asc": "Ordenar por nome (A-Z)", + "sort-name-desc": "Ordenar por nome (Z-A)", + "sort-random": "Ordenar aleatoriamente", "sorting-date-added": "Data de adição", "sorting-favorites": "Favoritos", "sorting-label": "Ordenar por", diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index f8389e9fc..8e2faac06 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Выберите цвет для визуализатора.", "color-name-label": "Цвет заливки", + "height-description": "Пользовательская ширина компонента.", "hide-when-idle-description": "Если включено, визуализатор скрыт, пока плеер не начнёт воспроизведение.", "hide-when-idle-label": "Скрывать, когда медиа не воспроизводится", "width-description": "Пользовательская ширина компонента." @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Отображать статистику в виде мини-диаграмм вместо текстовых значений. Предотвращает сдвиг макета.", "compact-mode-label": "Компактный режим", + "cpu-cores-description": "Отображать использование ядер CPU по отдельности.", + "cpu-cores-label": "Ядра CPU", "cpu-frequency-description": "Отобразить текущую тактовую частоту CPU в ГГц.", "cpu-frequency-label": "Показать частоту CPU", "cpu-temperature-description": "Показывать показания температуры процессора, если доступны.", @@ -291,6 +294,8 @@ "focused-color-label": "Цвет активного рабочего пространства", "follow-focused-screen-description": "Отображать рабочие пространства с текущего активного экрана, а не с экрана, на котором расположена панель.", "follow-focused-screen-label": "Следовать за активным экраном", + "font-weight-description": "Установить визуальный вес текста в рабочем пространстве.", + "font-weight-label": "Толщина шрифта", "grouped-border-opacity-description": "Установить уровень прозрачности для границ контейнера рабочей области.", "grouped-border-opacity-label": "Прозрачность границы", "hide-unoccupied-description": "Не отображать рабочие пространства без окон.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Все батареи (объединенные)", "battery-health": "Состояние батареи", "battery-level": "Уровень заряда батареи", "capacity-level": "Ёмкость: {level}", @@ -374,6 +380,7 @@ "add": "Добавить", "appearance": "Внешний вид", "apply": "Применить", + "auto-connect": "Автоматическое подключение", "automation": "Автоматизация", "available": "Доступно", "back": "Назад", @@ -401,6 +408,7 @@ "contributors": "Участники", "copied-to-clipboard": "Скопировано в буфер обмена", "countdown": "Обратный отсчёт", + "customize": "Настроить", "date": "Дата", "default": "По умолчанию", "delete": "Удалить", @@ -423,6 +431,11 @@ "execute": "Выполнить", "faithful": "Верный", "focus": "Фокус", + "font-weight-bold": "Жирный", + "font-weight-light": "Тонкий", + "font-weight-medium": "Средний", + "font-weight-regular": "Обычный", + "font-weight-semibold": "Полужирный", "frequency": "Частота", "gateway": "Шлюз", "general": "Общее", @@ -445,6 +458,7 @@ "lock": "Заблокировать", "logout": "Выйти", "look": "Внешний вид", + "margins": "Отступы", "media": "Плеер", "media-player": "Медиаплеер", "memory": "Память", @@ -467,6 +481,7 @@ "panels": "Панели", "password": "Пароль", "pause": "Пауза", + "performance": "Производительность", "pin": "Закрепить", "play": "Воспроизвести", "polling": "Опрашивание", @@ -519,6 +534,7 @@ "unpin": "Открепить", "update": "Обновление", "upload": "Загрузить", + "userspace-reboot": "Перезагрузить пользовательское пространство", "version": "Версия", "vibrant": "Яркий", "visualizer": "Визуализатор", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Стать спонсором", "changelog": "Посмотреть список изменений", + "changelog-on-startup": "Показывать журнал изменений при обновлении", + "changelog-on-startup-desc": "Автоматически показывать журнал изменений при обновлении Noctalia.", "contributors-description": "Благодарим нашего замечательного участника: {count}!", "contributors-description-plural": "Благодарим наших замечательных участников: {count}!", "copy-info": "Копировать информацию", "debug-disabled": "Режим отладки отключен", "debug-enabled": "Режим отладки включен", "info-copied": "Информация скопирована в буфер обмена", + "noctalia-available": "Доступно:", "noctalia-desc": "Изящная и минималистичная оболочка рабочего стола, тщательно разработанная для Wayland, созданная с помощью Quickshell.", "noctalia-git-commit": "Git коммит:", "noctalia-installed-version": "Установленная версия:", "noctalia-latest-version": "Последняя версия:", + "noctalia-qs-version": "Noctalia QS версия:", "noctalia-title": "Оболочка Noctalia", "privacy-policy": "Политика конфиденциальности", "support": "Поддержать нас", @@ -720,6 +740,7 @@ "supporters-desc": "Огромное спасибо нашему замечательному стороннику!", "supporters-desc-plural": "Огромное спасибо нашим {count} замечательным сторонникам!", "supporters-loading": "Загрузка спонсоров...", + "system-board": "Материнская плата:", "system-cpu": "ЦП:", "system-disk": "Диск:", "system-gpu": "Графический процессор", @@ -810,6 +831,8 @@ "appearance-density-label": "Плотность панели", "appearance-desc": "Настройка внешнего вида и положения панели.", "appearance-display-mode-description": "Выберите, когда панель видна.", + "appearance-enable-exclusion-zone-inset-description": "Уменьшите зону исключения на 1 физический пиксель, чтобы окна вплотную идеально заходили под край Bar.", + "appearance-enable-exclusion-zone-inset-label": "Внутренняя зона исключения", "appearance-floating-description": "Отображает панель как плавающую 'таблетку'.", "appearance-floating-label": "Плавающая панель", "appearance-font-scale-description": "Настройте масштаб размера шрифта для текста, отображаемого на панели.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Скрыть панель и закрыть панели, когда активен обзор компоновщика.", "appearance-hide-on-overview-label": "Скрыть панель в обзоре", "appearance-margins-description": "Настройка отступов вокруг плавающей панели.", - "appearance-margins-horizontal": "Горизонтальный", - "appearance-margins-label": "Отступы", - "appearance-margins-vertical": "Вертикальный", + "appearance-margins-horizontal": "Горизонтальный отступ", + "appearance-margins-vertical": "Вертикальный отступ", "appearance-outer-corners-description": "Отображает внешне изогнутые углы на панели.", "appearance-outer-corners-label": "Внешние углы", "appearance-position-description": "Выберите, где разместить панель на экране.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Использовать раздельную прозрачность столбцов", "appearance-widget-spacing-description": "Отрегулируйте интервал между каждым виджетом на панели.", "appearance-widget-spacing-label": "Расстояние между виджетами", + "behavior-middle-click-command-description": "Команда для выполнения по среднему клику.", + "behavior-middle-click-command-label": "Команда Среднего Клика", + "behavior-middle-click-command-placeholder": "niri msg action переключить обзор", + "behavior-middle-click-description": "Выберите действие средней кнопки мыши на пустых областях Панели.", + "behavior-middle-click-follow-mouse-description": "Открыть выбранную панель по среднему клику в позиции курсора.", + "behavior-middle-click-follow-mouse-label": "Средний клик следует за курсором", + "behavior-middle-click-label": "Действие по среднему клику на Панели", + "behavior-right-click-command-description": "Команда для выполнения по правому клику.", + "behavior-right-click-command-label": "Команда Правого Клика", + "behavior-right-click-command-placeholder": "notify-send \"Правый клик\"", + "behavior-right-click-description": "Выберите действие правого щелчка мыши на пустых областях панели.", + "behavior-right-click-follow-mouse-description": "Открыть выбранную панель по правому клику в позиции курсора.", + "behavior-right-click-follow-mouse-label": "Правый клик следует за курсором", + "behavior-right-click-label": "Действие по правому клику на Панели", + "behavior-wheel-wrap-description": "Если включено, прокрутка продолжается с последнего элемента к первому.", + "behavior-wheel-wrap-label": "По кругу", + "behavior-workspace-scroll-description": "Выберите, что делает колесо мыши на пустых областях панели.", + "behavior-workspace-scroll-label": "Действие колеса мыши Панели", + "behavior-workspace-scroll-option-content": "Содержимое", + "behavior-workspace-scroll-option-workspace": "Рабочая область", "monitor-configure-widgets": "Настроить виджеты", "monitor-override-settings": "Переопределить глобальные настройки", "monitor-override-settings-description": "Использовать пользовательские настройки для этого монитора.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Требуется аутентификация", + "bluetooth-auto-connect-description": "Автоматически подключаться к доверенным сопряженным устройствам при включении Bluetooth.", + "bluetooth-auto-connect-label": "Автоматически подключать сопряженные устройства", "bluetooth-devices-unnamed": "Безымянные устройства не отображаются.", "bluetooth-discoverable": "Это устройство обнаруживаемо как {hostName}, пока открыта эта вкладка настроек.", "bluetooth-rssi-polling-description": "Периодически опрашивает RSSI для подключенных устройств через bluetoothctl. Может быть недоступно для всех устройств; при включении использует минимальные ресурсы.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Настройте, какие элементы управления появляются в центре управления и в каком порядке.", "desc": "Настройка позиционирования и поведения панели центра управления.", + "open-at-mouse-description": "При нажатии правой кнопкой мыши на пустую область Панели, откройте Центр управления рядом с курсором. Если отключено, вместо этого используется настройка положения выше.", + "open-at-mouse-label": "Открывать у курсора по правому клику", "position-description": "Выберите, где появляется панель центра управления при открытии.", "shortcuts-custom-button-command-description": "Команда для выполнения при нажатии на кнопку.", "shortcuts-custom-button-enable-on-state-logic-description": "Включить вторую иконку и 'активное' состояние на основе команды проверки.", @@ -983,6 +1029,7 @@ "edit-mode-description": "Включите режим редактирования, чтобы перемещать и изменять положение виджетов рабочего стола. В этом режиме виджеты отображаются с контуром перетаскивания и могут быть перемещены.", "edit-mode-exit-button": "Выйти из режима редактирования", "edit-mode-grid-snap-label": "Привязка к сетке", + "edit-mode-grid-snap-scale-label": "Масштаб привязки к сетке", "edit-mode-label": "Режим редактирования", "enabled-description": "Включить или отключить виджеты рабочего стола полностью.", "enabled-label": "Включить виджеты рабочего стола", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Автоматически скрывать", "appearance-display-description": "Выберите поведение дока.", "appearance-display-exclusive": "Исключительно", + "appearance-dock-indicator-description": "Показывать небольшой индикатор, когда док скрыт.", + "appearance-dock-indicator-label": "Индикатор дока", "appearance-floating-distance-description": "Установите расстояние между доком и краем экрана.", "appearance-floating-distance-label": "Расстояние плавающего дока", - "appearance-frame-indicator-description": "Показывать небольшой индикатор на рамке, когда док скрыт.", - "appearance-frame-indicator-label": "Рамочный индикатор дока", "appearance-group-apps-description": "Группировать несколько окон одного и того же приложения в одну запись Dock.", "appearance-group-apps-label": "Группировать одинаковые приложения", "appearance-group-click-action-cycle": "Переключение окон", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Размер дока", "appearance-inactive-indicators-description": "Отображать индикаторы для всех приложений, а не только для активного.", "appearance-inactive-indicators-label": "Бегущие индикаторы", + "appearance-indicator-color-description": "Выберите цвет индикатора скрытого дока.", + "appearance-indicator-color-label": "Цвет индикатора", + "appearance-indicator-opacity-description": "Настройте непрозрачность индикатора скрытого дока.", + "appearance-indicator-opacity-label": "Непрозрачность индикатора", + "appearance-indicator-thickness-description": "Использовать более толстый индикатор скрытого дока (6 пк вместо 3 пк).", + "appearance-indicator-thickness-label": "Более толстый индикатор", "appearance-launcher-position-description": "Выберите, где значок запуска приложений отображается в доке.", "appearance-launcher-position-end": "Конец", "appearance-launcher-position-label": "Позиция запускателя", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Автоматически", "language-select-description": "Выберите язык, используемый в интерфейсе приложения.", "language-select-label": "Язык приложения", - "launch-setup-wizard": "Запустить мастер настройки", + "launch-setup-wizard": "Мастер настройки", "profile-desc": "Редактируйте данные пользователя и аватар.", "profile-picture-description": "Ваша фотография профиля, которая появляется во всем интерфейсе.", "profile-picture-label": "Фотография профиля", @@ -1165,11 +1218,11 @@ "screen-corners-title": "Углы экрана", "settings-copied": "Настройки скопированы в буфер обмена", "tab-basics": "Основы", - "tab-keybinds": "Горячие Клавиши" + "tab-keybinds": "Горячие клавиши" }, "hooks": { "info-command-info-description": "• Команды выполняются через shell (sh -lc)
• Команды выполняются в фоновом режиме (отдельно)
• Кнопки 'Тест' выполняются с текущими значениями", - "info-parameters-description": "• Хук обоев: $1 = путь к обоям, $2 = имя экрана
• Хук переключения темы: $1 = true/false (состояние тёмного режима)
• Хуки блокировки/разблокировки экрана: Без параметров
• Хуки режима производительности: Без параметров
• Хук сеанса: $1 = действие (выключением/перезагрузкой)", + "info-parameters-description": "• Хук обоев: $1 = путь к обоям, $2 = имя экрана
• Хук переключения темы: $1 = true/false (состояние Dark Mode)
• Хуки блокировки/разблокировки экрана: $1 = lock/unlock (состояние блокировки экрана)
• Хуки режима производительности: Без параметров
• Хук сессии: $1 = action (shutdown/reboot)", "info-parameters-label": "Доступные параметры", "noctalia-started-description": "Команда для выполнения после завершения загрузки Noctalia.", "noctalia-started-label": "Noctalia запущена", @@ -1206,23 +1259,32 @@ "custom-description": "Выполнить команду оболочки после периода бездействия.", "custom-entry-command": "Команда", "custom-entry-delete": "Удалить", + "custom-entry-edit": "Редактировать Пользовательскую Команду", + "custom-entry-name": "Имя", + "custom-entry-name-placeholder": "напр. Выключить свет", + "custom-entry-new": "Новая Пользовательская Команда", "custom-entry-timeout": "Время бездействия", - "custom-label": "Пользовательские Команды Простоя", + "custom-entry-timeout-format": "{count} секунда", + "custom-entry-timeout-format-plural": "{count} секунд", + "custom-entry-unnamed": "Безымянная команда", + "custom-label": "Пользовательские команды простоя", "enable-description": "Автоматически выключать экран, блокировать или приостанавливать работу после периода бездействия.", "enable-label": "Включить управление бездействием", "fade-duration-description": "Секунды для анимации затемнения перед выполнением каждого действия. Любое движение мыши отменяет затемнение.", "fade-duration-label": "Длительность затухания", "lock-description": "Секунды бездействия до активации экрана блокировки.", "lock-label": "Заблокировать экран", + "resume-command-label": "Возобновить команду", "screen-off-description": "Секунды бездействия до выключения мониторов.", "screen-off-label": "Выключить экран", - "status-description": "Время простоя, сообщаемое Compositor.", - "status-label": "Время Простоя", + "status-description": "Время простоя, сообщаемое композитором.", + "status-label": "Время простоя", "suspend-description": "Секунды бездействия до приостановки работы системы.", "tab-behavior": "Поведение", "tab-custom": "Пользовательский", - "timeouts-description": "Установите 0, чтобы отключить этап. Тайм-ауты приостанавливаются, пока Keep Awake активен.", + "timeouts-description": "Установите 0, чтобы отключить этап. Тайм-ауты приостанавливаются, пока 'Не засыпать' активен.", "timeouts-label": "Тайм-ауты", + "title": "Бездействие", "unavailable": "Нативный мониторинг бездействия недоступен на этом Compositorе." }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "Стиль Часов", "compact-lockscreen-description": "Показывать только поле для ввода логина и системные элементы управления, скрывая виджеты погоды и медиа.", "compact-lockscreen-label": "Компактный экран блокировки", + "enable-lockscreen-media-controls-description": "Показывать интерактивные элементы управления воспроизведением медиа на экране блокировки.", + "enable-lockscreen-media-controls-label": "Элементы Управления Мультимедиа на Экране Блокировки", "lock-on-suspend-description": "Автоматически блокировать экран при приостановке работы системы.", "lock-on-suspend-label": "Блокировать при приостановке", "lock-screen-animations-description": "Включить или отключить анимации экрана блокировки.", @@ -1443,7 +1507,7 @@ "general-desc": "Настроить видимость и поведение уведомлений (OSD).", "location-description": "Расположение экранных уведолений.", "monitors-desc": "Показывать OSD на определённых мониторах. По умолчанию на всех, если ни один не выбран.", - "title": "Уведомления (OSD)", + "title": "Уведомления", "types-brightness-description": "Показывать OSD при изменении яркости экрана.", "types-brightness-label": "Яркость", "types-custom-text-description": "Показывать OSD для пользовательских текстовых сообщений через IPC.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Отображать подсказки по горячим клавишам в параметрах сеанса.", "show-keybinds-label": "Показать сочетания клавиш" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Оставлять виджеты рабочего стола видимыми, пока режим производительности Noctalia включен.", + "noctalia-performance-disable-desktop-widgets-label": "Включить виджеты рабочего стола в режиме производительности", + "noctalia-performance-disable-wallpaper-description": "Сохраняйте видимыми обои рабочего стола, обзора и экрана блокировки, пока режим производительности Noctalia включен.", + "noctalia-performance-disable-wallpaper-label": "Включить рендеринг обоев в режиме производительности", + "title": "Система" + }, "system-monitor": { "critical-color-label": "Критический цвет", "custom-highlight-colors-title-label": "Пользовательские цвета выделения", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Интервал опроса", "polling-section-description": "Настройте частоту обновления каждой системной метрики.", "polling-section-label": "Интервалы опроса", - "threshold-critical": "Критический порог", - "threshold-warning": "Предупредительный порог", + "threshold-critical": "Критический", + "threshold-warning": "Предупреждение", "thresholds-section-description": "Настройте пороги предупреждения/критические пороги и интервалы опроса для каждой системной метрики.", "use-custom-highlight-colors-description": "Если отключено, используются цвета выделения по умолчанию темы.", "use-custom-highlight-colors-label": "Использовать пользовательские цвета выделения", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Скорость анимации", "animation-speed-reset": "Сбросить скорость анимации", "appearance-desc": "Настройте визуальные элементы, такие как всплывающие подсказки, границы и тени.", + "blur-behind-description": "Размывает область за панелями и всплывающими окнами, используя протокол размытия композитора.", + "blur-behind-label": "Размытие фона", "box-border-description": "Отображает контур вокруг областей содержимого.", "box-border-label": "Контур контейнера", "box-border-radius-description": "Настраивает скругление углов основных разделов макета, таких как боковые панели, карточки и панели контента.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Держать панели и панель задач сверху", "scaling-description": "Изменяет размер общего пользовательского интерфейса, исключая панель.", "scaling-label": "Масштабирование интерфейса", + "scrollbar-always-visible-description": "Всегда показывать полосы прокрутки, когда содержимое прокручивается, вместо того чтобы отображать их только при наведении.", + "scrollbar-always-visible-label": "Всегда показывать полосы прокрутки", "settings-panel-header": "Панель настроек", "settings-panel-mode-description": "Выберите раскладку настроек (может потребоваться перезапуск).", "settings-panel-mode-label": "Режим панели настроек", @@ -1619,7 +1694,9 @@ "shadows-label": "Отбрасываемые тени", "title": "Пользовательский интерфейс", "tooltips-description": "Включить или отключить всплывающие подсказки во всем интерфейсе.", - "tooltips-label": "Показывать всплывающие подсказки" + "tooltips-label": "Показывать всплывающие подсказки", + "translucent-widgets-description": "Сделайте кнопки, вкладки и другие виджеты внутри панелей полупрозрачными.", + "translucent-widgets-label": "Полупрозрачные виджеты" }, "wallpaper": { "automation-change-mode-alphabetical": "Алфавитный", @@ -1725,7 +1802,7 @@ }, "skip-setup": "Пропустить настройку", "telemetry-wizard-done": "Понял!", - "telemetry-wizard-note": "Вы контролируете ситуацию — вы можете включить или отключить это в любое время в настройках", + "telemetry-wizard-note": "Вы всё контролируете — вы можете включить или отключить это в любое время в настройках", "telemetry-wizard-subtitle": "Мы добавили анонимную аналитику, чтобы помочь улучшить Noctalia", "telemetry-wizard-title": "Обновление конфиденциальности", "wallpaper": { @@ -1742,6 +1819,7 @@ "select-prompt": "Выберите обои ниже", "subheader": "Создайте настроение с красивым фоном." }, + "welcome": "Добро пожаловать", "welcome-note": "Всего несколько основ для начала — полные настройки находятся в настройках", "welcome-subtitle": "Давайте сделаем ваш рабочий стол уникальным", "welcome-title": "Добро пожаловать в Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "Настройки виджета {widget}" }, "system-monitor": { + "core-usage": "Использование ядра {id}", "cpu-temp": "Температура ЦП", "cpu-usage": "Использование ЦП", "disk": "Диск", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Адрес скопирован в буфер обмена", + "auto-connect-disabled": "Автоподключение отключено", + "auto-connect-enabled": "Автоподключение включено", + "auto-connecting": "Подключение к {count} устройству(ам)...", "confirm-code": "Подтвердите код {value} на другом устройстве.", "connect-failed": "Не удалось подключиться к устройству", "disconnect-failed": "Не удалось отключиться от устройства", @@ -1800,6 +1882,10 @@ "unavailable": "История буфера обмена недоступна", "unavailable-desc": "Приложение 'cliphist' не установлено. Пожалуйста, установите его, чтобы использовать функции истории буфера обмена" }, + "custom-command-failed": { + "description": "Команда не выполнена: {command}\\nКод выхода: {code}", + "title": "Пользовательская команда не выполнена" + }, "do-not-disturb": { "disabled": "Режим 'Не беспокоить' отключен", "disabled-desc": "Отображаются все уведомления", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Добавить виджет", + "auto-connect": "Переключить автоподключение для этого устройства", + "bluetooth-auto-connect-off": "Автоподключение отключено", + "bluetooth-auto-connect-on": "Автоподключение включено", "bluetooth-devices": "Устройства Bluetooth", "brightness-at": "Яркость: {brightness}%", "click-to-start-recording": "Запись экрана (начать запись)", @@ -1951,6 +2040,11 @@ "resolution-label": "Разрешение", "resolution-mode-label": "Режим", "solid-color-tooltip": "Однотонный фон", + "sort-date-asc": "Сортировать от старых к новым", + "sort-date-desc": "Сортировать от новых к старым", + "sort-name-asc": "Сортировать по имени (А-Я)", + "sort-name-desc": "Сортировать по имени (Я-А)", + "sort-random": "Сортировать случайно", "sorting-date-added": "Дата добавления", "sorting-favorites": "Избранное", "sorting-label": "Сортировать по", diff --git a/Assets/Translations/sv.json b/Assets/Translations/sv.json index df64a0653..537204da1 100644 --- a/Assets/Translations/sv.json +++ b/Assets/Translations/sv.json @@ -5,12 +5,12 @@ "connect-vpn": "Anslut till {name}", "cycle-visualizer": "Växla visualiserare", "disable-bluetooth": "Inaktivera Bluetooth", - "disable-dnd": "Inaktivera Stör inte", + "disable-dnd": "Inaktivera 'Stör inte'", "disable-wifi": "Inaktivera Wi-Fi", "disconnect-vpn": "Koppla från {name}", "dock-settings": "Dockinställningar", "enable-bluetooth": "Aktivera Bluetooth", - "enable-dnd": "Aktivera Stör inte", + "enable-dnd": "Aktivera 'Stör inte'", "enable-wifi": "Aktivera Wi-Fi", "launcher-settings": "Startinställningar", "lower-to-bottom": "Sänk till botten", @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Välj färg för visualiseraren.", "color-name-label": "Fyllningsfärg", + "height-description": "Anpassad komponentbredd.", "hide-when-idle-description": "När denna funktion är aktiverad döljs visualiseraren om ingen spelare är aktiv.", "hide-when-idle-label": "Dölj när ingen media spelas upp", "width-description": "Anpassad komponentbredd." @@ -70,7 +71,7 @@ "apply-all-label": "Tillämpa rullningsändringar på alla bildskärmar" }, "clock": { - "clock-display-description": "Anpassa klockans visning genom att lägga till symboler från listan nedan. För att använda 12-timmarsformatet måste du inkludera symbolen \"AP\".", + "clock-display-description": "Anpassa klockans visning genom att lägga till symboler från listan nedan. För att använda 12-timmarsformatet måste du inkludera symbolen 'AP'.", "clock-display-label": "Klockvisning", "custom-font-description": "Välj ett anpassat teckensnitt för klockvisningen.", "custom-font-label": "Anpassat teckensnitt", @@ -117,7 +118,7 @@ "hide-mode-label": "Dölj läge", "hide-mode-max-transparent": "Maximalt expanderad men transparent", "icon-description": "Välj en ikon från biblioteket.", - "ipc-identifier-description": "Unik identifierare för IPC-kommandon. Använd denna identifierare med \"qs -c noctalia-shell ipc call cb [action] [identifier]\"för att styra denna knapp via IPC.", + "ipc-identifier-description": "Unik identifierare för IPC-kommandon. Använd denna identifierare med 'qs -c noctalia-shell ipc call cb [action] [identifier]' för att styra denna knapp via IPC.", "ipc-identifier-label": "IPC-identifierare", "left-click-description": "Kommando som ska utföras när knappen vänsterklickas.", "left-click-label": "Vänsterklick", @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Visa statistik som minibar-diagram istället för textvärden. Förhindrar layoutförskjutning. ", "compact-mode-label": "Kompakt läge", + "cpu-cores-description": "Visa CPU-kärnornas användning individuellt.", + "cpu-cores-label": "CPU-kärnor", "cpu-frequency-description": "Visa aktuell CPU-klockhastighet i GHz.", "cpu-frequency-label": "Visa CPU-frekvens", "cpu-temperature-description": "Visa CPU-temperaturvärden om tillgängliga.", @@ -274,7 +277,7 @@ "colorize-icons-label": "Färglägg ikoner", "drawer-enabled-description": "När funktionen är aktiverad visas fackikoner som inte är fästa i en låda.
När funktionen är inaktiverad visas alla fackikoner i rad.", "drawer-enabled-label": "Aktivera låda", - "hide-passive-description": "När funktionen är aktiverad döljs fackikoner med statusen \"Passiv\".", + "hide-passive-description": "När funktionen är aktiverad döljs fackikoner med statusen 'Passiv'.", "hide-passive-label": "Dölj passiva ikoner" }, "volume": { @@ -291,6 +294,8 @@ "focused-color-label": "Fokuserad arbetsområdesfärg", "follow-focused-screen-description": "Visa arbetsområden från den skärm som för närvarande är fokuserad, snarare än den skärm där fältet finns.", "follow-focused-screen-label": "Följ fokuserad skärm", + "font-weight-description": "Ställ in den visuella vikten för text inom arbetsytan.", + "font-weight-label": "Teckensnittsvikt", "grouped-border-opacity-description": "Ställ in opacitetsnivån för arbetsytans behållargränser.", "grouped-border-opacity-label": "Ramopacitet", "hide-unoccupied-description": "Visa inte arbetsytor utan fönster.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Alla batterier (kombinerat)", "battery-health": "Batteriets hälsa", "battery-level": "Batterinivå", "capacity-level": "Kapacitet: {level}", @@ -374,6 +380,7 @@ "add": "Lägg till", "appearance": "Utseende", "apply": "Tillämpa", + "auto-connect": "Automatisk anslutning", "automation": "Automation", "available": "Tillgänglig", "back": "Tillbaka", @@ -401,6 +408,7 @@ "contributors": "Bidragsgivare", "copied-to-clipboard": "Kopierat till urklipp", "countdown": "Nedräkning", + "customize": "Anpassa", "date": "Datum", "default": "Standard", "delete": "Ta bort", @@ -423,6 +431,11 @@ "execute": "Exekvera", "faithful": "Trogen", "focus": "Fokus", + "font-weight-bold": "Fet", + "font-weight-light": "Lätt", + "font-weight-medium": "Mellan", + "font-weight-regular": "Vanlig", + "font-weight-semibold": "Halvfet", "frequency": "Frekvens", "gateway": "Gateway", "general": "Allmänt", @@ -445,6 +458,7 @@ "lock": "Lås", "logout": "Logga ut", "look": "Titta", + "margins": "Marginaler", "media": "Media", "media-player": "Mediaspelare", "memory": "Minne", @@ -467,6 +481,7 @@ "panels": "Paneler", "password": "Lösenord", "pause": "Paus", + "performance": "Prestanda", "pin": "Fäst", "play": "Spela upp", "polling": "Pollning", @@ -505,7 +520,7 @@ "start": "Starta", "stop": "Stoppa", "supporters": "Stödjare", - "suspend": "Avbryt", + "suspend": "Strömsparläge", "templates": "Mallar", "tertiary": "Tertiär", "test": "Testa", @@ -519,6 +534,7 @@ "unpin": "Avfäst", "update": "Uppdatera", "upload": "Skicka upp", + "userspace-reboot": "Starta om användarutrymme", "version": "Version", "vibrant": "Levande", "visualizer": "Visualiserare", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Bli supporter", "changelog": "Visa ändringslogg", + "changelog-on-startup": "Visa ändringslogg vid uppdatering", + "changelog-on-startup-desc": "Visa ändringsloggen automatiskt när Noctalia uppdateras.", "contributors-description": "Tack till våra {count} fantastiska bidragsgivare!", "contributors-description-plural": "Tack till våra {count} fantastiska bidragsgivare!", "copy-info": "Kopiera info", "debug-disabled": "Felsökningsläge inaktiverat", "debug-enabled": "Felsökningsläge aktiverat", "info-copied": "Info kopierad till urklipp", + "noctalia-available": "Tillgänglig:", "noctalia-desc": "Ett elegant och minimalistiskt skrivbordsskal som är noggrant utformat för Wayland och byggt med Quickshell.", "noctalia-git-commit": "Git-commit:", "noctalia-installed-version": "Installerad version:", "noctalia-latest-version": "Senaste version:", + "noctalia-qs-version": "Noctalia QS version:", "noctalia-title": "Noctalia-skal", "privacy-policy": "Sekretesspolicy", "support": "Stöd oss", @@ -720,6 +740,7 @@ "supporters-desc": "Ett stort tack till vår fantastiska supporter!", "supporters-desc-plural": "Ett stort tack till våra {count} fantastiska supportrar!", "supporters-loading": "Laddar supportrar...", + "system-board": "Moderkort:", "system-cpu": "CPU:", "system-disk": "Disk:", "system-gpu": "GPU:", @@ -810,7 +831,9 @@ "appearance-density-label": "Stapeldensitet", "appearance-desc": "Anpassa stapelns utseende och position.", "appearance-display-mode-description": "Välj när listen är synlig.", - "appearance-floating-description": "Visa stapeln som en flytande \"piller\".", + "appearance-enable-exclusion-zone-inset-description": "Minska exklusionszonen med 1 fysisk pixel så att fönster som ligger kant i kant perfekt överlappar under Bar-kanten.", + "appearance-enable-exclusion-zone-inset-label": "Infälld uteslutningszon", + "appearance-floating-description": "Visa stapeln som en flytande 'piller'.", "appearance-floating-label": "Flytande stapel", "appearance-font-scale-description": "Justera skalningen av teckenstorleken för text som visas i fältet.", "appearance-font-scale-label": "Teckensnittsskala", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Dölj fältet och stäng panelerna när kompositörsöversikten är aktiv.", "appearance-hide-on-overview-label": "Dölj fältet på översikten", "appearance-margins-description": "Justera marginalerna runt det flytande fältet.", - "appearance-margins-horizontal": "Horisontell", - "appearance-margins-label": "Marginaler", - "appearance-margins-vertical": "Vertikal", + "appearance-margins-horizontal": "Horisontell marginal", + "appearance-margins-vertical": "Vertikal marginal", "appearance-outer-corners-description": "Visa utåtböjda hörn på stapeln.", "appearance-outer-corners-label": "Ytterhörn", "appearance-position-description": "Välj var fältet ska placeras på skärmen.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Använd separat opacitet för fältet", "appearance-widget-spacing-description": "Justera avståndet mellan varje widget i fältet.", "appearance-widget-spacing-label": "Widgetavstånd", + "behavior-middle-click-command-description": "Kommando att köra vid mellanklick.", + "behavior-middle-click-command-label": "Mellanklick-kommando", + "behavior-middle-click-command-placeholder": "niri msg action växla översikt", + "behavior-middle-click-description": "Välj vad ett mellanklick gör på tomma områden av Fältet.", + "behavior-middle-click-follow-mouse-description": "Öppna den valda mellanklickspanelen vid muspekarens position.", + "behavior-middle-click-follow-mouse-label": "Mellanklick följer mus", + "behavior-middle-click-label": "Fältets mittenklicksåtgärd", + "behavior-right-click-command-description": "Kommando att köra vid högerklick.", + "behavior-right-click-command-label": "Högerklickskommando", + "behavior-right-click-command-placeholder": "notify-send \"Högerklick\"", + "behavior-right-click-description": "Välj vad högerklick gör på tomma områden av fältet.", + "behavior-right-click-follow-mouse-description": "Öppna den valda högerklickspanelen vid markörpositionen.", + "behavior-right-click-follow-mouse-label": "Högerklick följer musen", + "behavior-right-click-label": "Åtgärd vid högerklick på Fältet", + "behavior-wheel-wrap-description": "När aktiverat fortsätter rullningen från det sista objektet till det första.", + "behavior-wheel-wrap-label": "Cykla", + "behavior-workspace-scroll-description": "Välj vad mushjulet gör på tomma områden i fältet.", + "behavior-workspace-scroll-label": "Musrullningsåtgärd för Fältet", + "behavior-workspace-scroll-option-content": "Innehåll", + "behavior-workspace-scroll-option-workspace": "Arbetsyta", "monitor-configure-widgets": "Konfigurera widgetar", "monitor-override-settings": "Åsidosätt globala inställningar", "monitor-override-settings-description": "Använd anpassade inställningar för denna skärm.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Autentisering krävs", + "bluetooth-auto-connect-description": "Anslut automatiskt till betrodda parkopplade enheter när Bluetooth är aktiverat.", + "bluetooth-auto-connect-label": "Anslut parade enheter automatiskt", "bluetooth-devices-unnamed": "Namnlösa enheter visas inte.", "bluetooth-discoverable": "Denna enhet är upptäckbar som {hostName} medan denna inställningsflik är öppen.", "bluetooth-rssi-polling-description": "Samplar periodiskt RSSI för anslutna enheter via bluetoothctl. Kanske inte tillgängligt för alla enheter; använder minimala resurser när det är aktiverat.", @@ -932,16 +976,18 @@ "control-center": { "cards-desc": "Anpassa vilka kontroller som ska visas i kontrollcentret och i vilken ordning.", "desc": "Konfigurera kontrollpanelens placering och beteende.", + "open-at-mouse-description": "När du högerklickar på ett tomt område av Fältet, öppna Kontrollcentret nära markören. Om inaktiverat används istället positioneringsinställningen ovan.", + "open-at-mouse-label": "Öppna vid markören med högerklick", "position-description": "Välj var kontrollpanelen ska visas när den öppnas.", "shortcuts-custom-button-command-description": "Kommandot som ska utföras när knappen klickas på.", - "shortcuts-custom-button-enable-on-state-logic-description": "Aktivera en andra ikon och aktivt tillstånd baserat på ett kontrollkommando.", + "shortcuts-custom-button-enable-on-state-logic-description": "Aktivera en andra ikon och 'aktivt' tillstånd baserat på ett kontrollkommando.", "shortcuts-custom-button-enable-on-state-logic-label": "Aktivera logik för aktivt tillstånd", "shortcuts-custom-button-icon-description": "Välj en ikon från biblioteket.", "shortcuts-custom-button-on-clicked-label": "Kommando vid vänsterklick", "shortcuts-custom-button-on-middle-clicked-description": "Kommando som ska köras när knappen mittklickas.", - "shortcuts-custom-button-on-state-command-description": "Kommando som ska utföras för att kontrollera om knappen ska vara i läge \"på\". Returnerar 0 för på, icke-noll för av.", + "shortcuts-custom-button-on-state-command-description": "Kommando som ska utföras för att kontrollera om knappen ska vara i läge 'på'. Returnerar 0 för på, icke-noll för av.", "shortcuts-custom-button-on-state-command-label": "Kommando för att kontrollera om knappen är på", - "shortcuts-custom-button-on-state-icon-description": "Ikonen för knappen när den är på.", + "shortcuts-custom-button-on-state-icon-description": "Ikonen för knappen när den är 'på'.", "shortcuts-custom-button-on-state-icon-label": "Ikon för på-läge", "shortcuts-custom-button-state-checks-add": "Lägg till tillståndskontroll", "shortcuts-custom-button-state-checks-command": "Kommando att utföra för denna tillståndskontroll", @@ -983,6 +1029,7 @@ "edit-mode-description": "Aktivera redigeringsläge för att flytta och omplacera skrivbordswidgets. När det är aktiverat visas en dragram runt widgets och de kan omplaceras.", "edit-mode-exit-button": "Avsluta redigeringsläge", "edit-mode-grid-snap-label": "Rutnätsfästning", + "edit-mode-grid-snap-scale-label": "Rutnätsfästningsskala", "edit-mode-label": "Redigeringsläge", "enabled-description": "Aktivera eller inaktivera skrivbordswidgets helt.", "enabled-label": "Aktivera skrivbordswidgetar", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Dölj automatiskt", "appearance-display-description": "Välj hur dockan ska fungera.", "appearance-display-exclusive": "Exklusiv", + "appearance-dock-indicator-description": "Visa en liten indikator när dockan är dold.", + "appearance-dock-indicator-label": "Dockindikator", "appearance-floating-distance-description": "Ställ in avståndet mellan dockan och skärmens kant.", "appearance-floating-distance-label": "Dockens flytande avstånd", - "appearance-frame-indicator-description": "Visa en liten indikator på ramen när dockan är dold.", - "appearance-frame-indicator-label": "Inramad dockindikator", "appearance-group-apps-description": "Gruppera flera fönster från samma app till en Dock-post.", "appearance-group-apps-label": "Gruppera samma appar", "appearance-group-click-action-cycle": "Växla fönster", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Dockstorlek", "appearance-inactive-indicators-description": "Visa indikatorer för alla appar, inte bara den som är aktiv för tillfället.", "appearance-inactive-indicators-label": "Indikatorer som körs", + "appearance-indicator-color-description": "Välj färg för den dolda dockans indikator.", + "appearance-indicator-color-label": "Indikatorfärg", + "appearance-indicator-opacity-description": "Justera opaciteten för den dolda dockans indikator.", + "appearance-indicator-opacity-label": "Indikatorns opacitet", + "appearance-indicator-thickness-description": "Aktivera en tjockare indikator för den dolda dockan (6 px istället för 3 px).", + "appearance-indicator-thickness-label": "Tjockare indikator", "appearance-launcher-position-description": "Välj var startprogrammets ikon visas i dockan.", "appearance-launcher-position-end": "Slut", "appearance-launcher-position-label": "Startarens position", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Automatiskt", "language-select-description": "Välj det språk som ska användas i programmets gränssnitt.", "language-select-label": "Programspråk", - "launch-setup-wizard": "Inställningsguide", + "launch-setup-wizard": "Installationsguide", "profile-desc": "Redigera dina användaruppgifter och avatar.", "profile-picture-description": "Din profilbild som visas i hela gränssnittet.", "profile-picture-label": "Profilbild", @@ -1169,11 +1222,11 @@ }, "hooks": { "info-command-info-description": "• Kommandon exekveras via shell (sh -lc)
• Kommandon körs i bakgrunden (fristående)
• Testknappar exekveras med aktuella värden", - "info-parameters-description": "• Bakgrundsbildskrok: $1 = bakgrundsbildens sökväg, $2 = skärmnamn
• Temaväxlingskrok: $1 = sant/falskt (mörkt läge)
• Skärmlåsnings-/upplåsningskrokar: Inga parametrar
• Prestandalägeskrokar: Inga parametrar
• Sessionskrok: $1 = åtgärd (stänga av/starta om)", + "info-parameters-description": "• Bakgrundsbildskrok: $1 = bakgrundsbildens sökväg, $2 = skärmnamn
• Temaväxlingskrok: $1 = true/false (Dark Mode-status)
• Skärmlås/upplåsningskrokar: $1 = lock/unlock (skärmlåsets status)
• Prestandalägeskrokar: Inga parametrar
• Sessionskrok: $1 = action (shutdown/reboot)", "info-parameters-label": "Tillgängliga parametrar", "noctalia-started-description": "Kommando som ska utföras när Noctalia har laddats klart.", "noctalia-started-label": "Noctalia startat", - "noctalia-started-placeholder": "t.ex. notify-send \"Noctalia laddat\"", + "noctalia-started-placeholder": "t.ex. notify-send 'Noctalia laddat'", "performance-mode-disabled-description": "Kommando som ska utföras när Noctalia prestandaläget är inaktiverat.", "performance-mode-disabled-label": "Prestandaläget inaktiverat", "performance-mode-disabled-placeholder": "t.ex. notify-send \"Prestanda\"\"Läget inaktiverat\"", @@ -1206,23 +1259,32 @@ "custom-description": "Kör ett skal-kommando efter en period av inaktivitet.", "custom-entry-command": "Kommando", "custom-entry-delete": "Ta bort", + "custom-entry-edit": "Redigera Anpassat Kommando", + "custom-entry-name": "Namn", + "custom-entry-name-placeholder": "t.ex. Släck lamporna", + "custom-entry-new": "Nytt Anpassat Kommando", "custom-entry-timeout": "Inaktivitetstid", - "custom-label": "Anpassade Inaktivitetskommandon", + "custom-entry-timeout-format": "{count} sekund", + "custom-entry-timeout-format-plural": "{count} sekunder", + "custom-entry-unnamed": "Namnlöst kommando", + "custom-label": "Anpassade inaktivitetskommandon", "enable-description": "Stäng automatiskt av skärmen, lås eller försätt i viloläge efter en period av inaktivitet.", "enable-label": "Aktivera inaktivitetshantering", "fade-duration-description": "Sekunder för animationen som tonar till svart innan varje åtgärd utlöses. Alla musrörelser avbryter nedtoningen.", "fade-duration-label": "Toningstid", "lock-description": "Sekunder av inaktivitet innan låsskärmen aktiveras.", "lock-label": "Lås skärm", + "resume-command-label": "Återuppta kommando", "screen-off-description": "Sekunder av inaktivitet innan skärmarna stängs av.", "screen-off-label": "Stäng av skärmen", - "status-description": "Inaktiv tid som rapporterats av Compositor.", - "status-label": "Inaktiv Tid", + "status-description": "Inaktiv tid som rapporterats av compositorn.", + "status-label": "Inaktiv tid", "suspend-description": "Sekunder av inaktivitet innan systemet försätts i viloläge.", "tab-behavior": "Beteende", "tab-custom": "Anpassad", - "timeouts-description": "Ställ in på 0 för att inaktivera ett steg. Tidsgränser pausas medan Keep Awake är aktivt.", + "timeouts-description": "Ställ in på 0 för att inaktivera ett steg. Tidsgränser pausas medan 'Håll vaken' är aktivt.", "timeouts-label": "Tidsgränser", + "title": "Inaktivitet", "unavailable": "Inbyggd inaktivitetsövervakning är inte tillgänglig på denna Compositor." }, "indicator": { @@ -1235,7 +1297,7 @@ "execute-title": "Exekvering", "settings-annotation-tool-description": "Kommando som körs när du klickar på anteckningsknappen i klippbordshistoriken. Bilden kommer att skickas till detta kommando.", "settings-annotation-tool-label": "Anteckningsverktyg", - "settings-annotation-tool-placeholder": "t.ex. \"gradia\", \"satty -f -\"", + "settings-annotation-tool-placeholder": "t.ex. 'gradia', 'satty -f -'", "settings-auto-paste-description": "Klistra in det valda klippbordsinnehållet automatiskt. Kräver wtype. ", "settings-auto-paste-label": "Automatisk klistra in", "settings-clip-preview-description": "Visa en förhandsgranskning av klippbordets innehåll när du använder kommandot >clip.", @@ -1248,7 +1310,7 @@ "settings-clipboard-watch-image-label": "Bildövervakningskommando", "settings-clipboard-watch-text-description": "Fullständig kommandosträng skickad till wl-paste för textändringar (kräver omstart).", "settings-clipboard-watch-text-label": "Textbevakningskommando", - "settings-custom-launch-prefix-description": "Prefixkommandon med en anpassad startfunktion (t.ex. \"runapp\"för systemd-integration).", + "settings-custom-launch-prefix-description": "Prefixkommandon med en anpassad startfunktion (t.ex. 'runapp' för systemd-integration).", "settings-custom-launch-prefix-enabled-description": "Använd ett anpassat prefix för att starta applikationer istället för standardmetoden.", "settings-custom-launch-prefix-enabled-label": "Aktivera anpassat startprefix", "settings-custom-launch-prefix-label": "Anpassat startprefix", @@ -1275,7 +1337,7 @@ "settings-show-icon-background-label": "Visa ikonbakgrund", "settings-sort-by-usage-description": "När denna funktion är aktiverad visas de program som används oftast först i listan.", "settings-sort-by-usage-label": "Sortera efter mest använda", - "settings-terminal-command-description": "Kommando för att starta en terminal. T.ex. \"kitty -e\"eller \"gnome-terminal --\".", + "settings-terminal-command-description": "Kommando för att starta en terminal. T.ex. 'kitty -e' eller 'gnome-terminal --'.", "settings-terminal-command-label": "Terminalkommando", "settings-use-app2unit-description": "Använder en alternativ startmetod för att bättre hantera app-processer och förhindra problem.", "settings-use-app2unit-label": "Använd App2Unit för att starta applikationer", @@ -1334,6 +1396,8 @@ "clock-style-label": "Klockstil", "compact-lockscreen-description": "Visa endast inloggningsfältet och systemkontrollerna, dölj väder- och mediewidgets.", "compact-lockscreen-label": "Kompakt låsskärm", + "enable-lockscreen-media-controls-description": "Visa interaktiva mediekontroller på låsskärmen.", + "enable-lockscreen-media-controls-label": "Mediekontroller på Låsskärmen", "lock-on-suspend-description": "Lås skärmen automatiskt när systemet pausas.", "lock-on-suspend-label": "Lås vid paus", "lock-screen-animations-description": "Aktivera eller inaktivera låsskärmsanimeringar.", @@ -1345,7 +1409,7 @@ "monitors-desc": "Visa låsskärm på specifika bildskärmar. Standard är alla om inga väljs.", "password-chars-description": "Söta ikoner som används för att dölja ditt lösenord.", "password-chars-label": "Slumpmässiga lösenordsikoner", - "show-hibernate-description": "Visa alternativet \"viloläge\"i strömkontrollerna.", + "show-hibernate-description": "Visa alternativet 'viloläge' i strömkontrollerna.", "show-hibernate-label": "Visa viloläge", "show-session-buttons-description": "Tillåt åtkomst till ströminställningar från låsskärmen. ", "show-session-buttons-label": "Strömkontroller", @@ -1441,9 +1505,9 @@ "enabled-label": "Aktivera skärmdisplay", "events-desc": "Välj vilka händelser som ska utlösa skärmdisplayen.", "general-desc": "Konfigurera OSD:s synlighet och beteende.", - "location-description": "Var On-Screen Display visas.", + "location-description": "Var skärmdisplayen visas.", "monitors-desc": "Visa OSD på specifika skärmar. Standardinställningen är alla om ingen är vald.", - "title": "On-Screen Display", + "title": "Skärmdisplay", "types-brightness-description": "Visa OSD när skärmens ljusstyrka ändras.", "types-brightness-label": "Ljusstyrka", "types-custom-text-description": "Visa OSD för anpassade textmeddelanden från IPC.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Visa kortkommando-tips på sessionsalternativ.", "show-keybinds-label": "Visa kortkommandon" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Håll skrivbordswidgets synliga medan Noctalia prestandaläget är aktiverat.", + "noctalia-performance-disable-desktop-widgets-label": "Aktivera skrivbordswidgetar i prestandaläge", + "noctalia-performance-disable-wallpaper-description": "Behåll skrivbords-, översikts- och låsskärmsbakgrundsbilder synliga när Noctalia prestandaläget är aktiverat.", + "noctalia-performance-disable-wallpaper-label": "Aktivera bakgrundsbildsåtergivning i prestandaläge", + "title": "System" + }, "system-monitor": { "critical-color-label": "Kritisk färg", "custom-highlight-colors-title-label": "Anpassade markeringsfärger", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Pollningsintervall", "polling-section-description": "Konfigurera hur ofta varje systemmetrik uppdateras.", "polling-section-label": "Pollningsintervall", - "threshold-critical": "Kritisk tröskel", - "threshold-warning": "Varningsgräns", + "threshold-critical": "Kritisk", + "threshold-warning": "Varning", "thresholds-section-description": "Justera varnings-/kritiska trösklar för varje systemmetrik.", "use-custom-highlight-colors-description": "När denna funktion är inaktiverad används standardfärgerna för markeringar. ", "use-custom-highlight-colors-label": "Använd anpassade markeringsfärger", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Animationshastighet", "animation-speed-reset": "Återställ animationshastighet", "appearance-desc": "Anpassa visuella element som verktygstips, kantlinjer och skuggor.", + "blur-behind-description": "Suddar ut området bakom fält och paneler med hjälp av kompositorns suddighetsprotokoll.", + "blur-behind-label": "Bakgrundsoskärpa", "box-border-description": "Visa en kontur runt innehållsområden.", "box-border-label": "Behållarkontur", "box-border-radius-description": "Justerar hörnrundningen för större layoutsektioner, såsom sidofält, kort och innehållspaneler.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Håll paneler och fält överst", "scaling-description": "Ändrar storleken på det allmänna användargränssnittet, exklusive fältet.", "scaling-label": "Gränssnittsskalning", + "scrollbar-always-visible-description": "Håll rullningslister synliga när innehåll är rullbart, istället för att bara visa dem vid hovring.", + "scrollbar-always-visible-label": "Visa alltid rullningslister", "settings-panel-header": "Inställningspanel", "settings-panel-mode-description": "Välj inställningslayout (kan kräva att du öppnar om).", "settings-panel-mode-label": "Inställningspanelens läge", @@ -1619,7 +1694,9 @@ "shadows-label": "Fallskugga", "title": "Användargränssnitt", "tooltips-description": "Aktivera eller inaktivera verktygstips i hela gränssnittet.", - "tooltips-label": "Visa verktygstips" + "tooltips-label": "Visa verktygstips", + "translucent-widgets-description": "Gör knappar, flikar och andra widgets inuti paneler halvtransparenta.", + "translucent-widgets-label": "Genomskinliga widgetar" }, "wallpaper": { "automation-change-mode-alphabetical": "Alfabetiskt", @@ -1682,7 +1759,7 @@ "enter-command": "Ange kommando att utföra (app eller anpassat skript)", "enter-ipc-identifier": "Ange unik identifierare för IPC-kommandon", "enter-path": "Ange sökväg...", - "enter-text-to-collapse": "t.ex. \"ingenting spelas upp\". Använd /regex/ för mönster.", + "enter-text-to-collapse": "t.ex. 'ingenting spelas upp'. Använd /regex/ för mönster.", "enter-tooltip": "Ange verktygstips", "enter-width-pixels": "Ange bredd i bildpunkter", "keybind-recording": "Spelar in tangentbindning...", @@ -1742,6 +1819,7 @@ "select-prompt": "Välj en bakgrundsbild nedan", "subheader": "Skapa stämning med en vacker bakgrund." }, + "welcome": "Välkommen", "welcome-note": "Bara några grundläggande tips för att komma igång — alla alternativ finns i inställningarna", "welcome-subtitle": "Låt oss göra ditt skrivbord unikt för dig", "welcome-title": "Välkommen till Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "Inställningar för {widget}" }, "system-monitor": { + "core-usage": "Kärna {id} användning", "cpu-temp": "CPU-temperatur", "cpu-usage": "CPU-användning", "disk": "Disk", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Adress kopierad till urklipp", + "auto-connect-disabled": "Automatisk anslutning inaktiverad", + "auto-connect-enabled": "Automatisk anslutning aktiverad", + "auto-connecting": "Ansluter till {count} enhet(er)...", "confirm-code": "Bekräfta koden {value} på den andra enheten.", "connect-failed": "Det gick inte att ansluta till enheten", "disconnect-failed": "Det gick inte att koppla bort från enheten", @@ -1798,12 +1880,16 @@ "clipboard": { "long-text": "Lång text", "unavailable": "Urklippshistorik otillgänglig", - "unavailable-desc": "Applikationen \"cliphist\"är inte installerad. Installera den för att kunna använda klippbordshistorikfunktionerna" + "unavailable-desc": "Applikationen 'cliphist' är inte installerad. Installera den för att kunna använda klippbordshistorikfunktionerna" + }, + "custom-command-failed": { + "description": "Kommando misslyckades: {command}\\nAvslutningskod: {code}", + "title": "Anpassat kommando misslyckades" }, "do-not-disturb": { - "disabled": "Stör inte inaktiverat", + "disabled": "'Stör inte' inaktiverat", "disabled-desc": "Visar alla aviseringar", - "enabled": "Stör inte aktiverat", + "enabled": "'Stör inte' aktiverat", "enabled-desc": "Du hittar dessa aviseringar i din historik" }, "donation-opened": "Donationssidan öppnades i din webbläsare", @@ -1835,8 +1921,8 @@ "title": "Bearbetning av mallar misslyckades" }, "vpn": { - "connected": "Ansluten till \"{name}\"", - "disconnected": "Frånkopplad från \"{name}\"" + "connected": "Ansluten till '{name}'", + "disconnected": "Frånkopplad från '{name}'" }, "wallpaper-colors": { "disabled": "Bakgrundsfärger inaktiverade", @@ -1844,16 +1930,19 @@ "label": "Bakgrundsfärger" }, "wifi": { - "connected": "Ansluten till \"{ssid}\"", + "connected": "Ansluten till '{ssid}'", "connection-failed": "Anslutningen misslyckades", "connection-timeout": "Anslutningstidsgräns", - "disconnected": "Kopplade från \"{ssid}\"", + "disconnected": "Kopplade från '{ssid}'", "incorrect-password": "Felaktigt lösenord", "network-not-found": "Nätverket hittades inte" } }, "tooltips": { "add-widget": "Lägg till widget", + "auto-connect": "Växla automatisk anslutning för denna enhet", + "bluetooth-auto-connect-off": "Automatisk anslutning inaktiverad", + "bluetooth-auto-connect-on": "Automatisk anslutning aktiverad", "bluetooth-devices": "Bluetooth-enheter", "brightness-at": "Ljusstyrka: {brightness}%", "click-to-start-recording": "Skärminspelare (starta inspelning)", @@ -1951,6 +2040,11 @@ "resolution-label": "Upplösning", "resolution-mode-label": "Läge", "solid-color-tooltip": "Enfärgad bakgrund", + "sort-date-asc": "Sortera efter äldst först", + "sort-date-desc": "Sortera efter nyast först", + "sort-name-asc": "Sortera efter namn (A-Z)", + "sort-name-desc": "Sortera efter namn (Z-A)", + "sort-random": "Sortera slumpmässigt", "sorting-date-added": "Datum tillagt", "sorting-favorites": "Favoriter", "sorting-label": "Sortera efter", diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 7d401b378..b97b65ee1 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Görselleştirici için renk seçin.", "color-name-label": "Dolgu rengi", + "height-description": "Özel bileşen genişliği.", "hide-when-idle-description": "Etkinleştirildiğinde, çalar aktif bir şekilde çalmadığı sürece görselleştirici gizlidir.", "hide-when-idle-label": "Boşken gizle", "width-description": "Özel bileşen genişliği." @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "İstatistikleri metin değerleri yerine mini çubuk grafikler olarak görüntüleyin. Düzen kaymasını önler.", "compact-mode-label": "Kompakt mod", + "cpu-cores-description": "CPU çekirdeklerinin kullanımını ayrı ayrı görüntüle.", + "cpu-cores-label": "CPU Çekirdekleri", "cpu-frequency-description": "Mevcut CPU saat hızını GHz cinsinden görüntüle.", "cpu-frequency-label": "CPU frekansını göster", "cpu-temperature-description": "Mevcut işlemci sıcaklık okumalarını gösterir.", @@ -291,6 +294,8 @@ "focused-color-label": "Odaklanılan çalışma alanı rengi", "follow-focused-screen-description": "Çubuğun bulunduğu ekran yerine, şu anda odaklanmış ekrandaki çalışma alanlarını göster.", "follow-focused-screen-label": "Odaklanmış ekranı takip et", + "font-weight-description": "Çalışma alanı içindeki metin için görsel ağırlığı ayarlayın.", + "font-weight-label": "Yazı tipi ağırlığı", "grouped-border-opacity-description": "Çalışma alanı kapsayıcı kenarlıklarının opaklık düzeyini ayarlayın.", "grouped-border-opacity-label": "Kenarlık opaklığı", "hide-unoccupied-description": "Penceresi olmayan çalışma alanlarını gösterme.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Tüm bataryalar (birleşik)", "battery-health": "Pil sağlığı", "battery-level": "Pil seviyesi", "capacity-level": "Kapasite: {level}", @@ -374,6 +380,7 @@ "add": "Ekle", "appearance": "Görünüm", "apply": "Uygula", + "auto-connect": "Otomatik bağlan", "automation": "Otomasyon", "available": "Mevcut", "back": "Geri", @@ -401,6 +408,7 @@ "contributors": "Katkıda bulunanlar", "copied-to-clipboard": "Panoya kopyalandı", "countdown": "Geri sayım", + "customize": "Özelleştir", "date": "Tarih", "default": "Varsayılan", "delete": "Sil", @@ -423,6 +431,11 @@ "execute": "Yürüt", "faithful": "Sadık", "focus": "Odaklanma", + "font-weight-bold": "Kalın", + "font-weight-light": "İnce", + "font-weight-medium": "Orta", + "font-weight-regular": "Normal", + "font-weight-semibold": "Yarı kalın", "frequency": "Sıklık", "gateway": "Geçit", "general": "Genel", @@ -445,6 +458,7 @@ "lock": "Kilitle", "logout": "Çıkış yap", "look": "Görünüm", + "margins": "Kenar boşlukları", "media": "Medya", "media-player": "Medya oynatıcı", "memory": "Hafıza", @@ -467,6 +481,7 @@ "panels": "Paneller", "password": "Şifre", "pause": "Duraklat", + "performance": "Performans", "pin": "Sabitle", "play": "Oynat", "polling": "Yoklama", @@ -519,6 +534,7 @@ "unpin": "Sabitlemeyi kaldır", "update": "Güncelleme", "upload": "Yükle", + "userspace-reboot": "Kullanıcı alanını yeniden başlat", "version": "Sürüm", "vibrant": "Canlı", "visualizer": "Görselleştirici", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Destekçi ol", "changelog": "Değişiklik günlüğünü görüntüle", + "changelog-on-startup": "Güncellemede değişiklik günlüğünü göster", + "changelog-on-startup-desc": "Noctalia güncellendiğinde değişiklik günlüğünü otomatik olarak göster.", "contributors-description": "{count} harika katılımcımıza teşekkürler!", "contributors-description-plural": "{count} harika katılımcımıza teşekkürler!", "copy-info": "Bilgileri kopyala", "debug-disabled": "Hata ayıklama modu devre dışı bırakıldı", "debug-enabled": "Hata ayıklama modu etkinleştirildi", "info-copied": "Bilgi panoya kopyalandı", + "noctalia-available": "Mevcut:", "noctalia-desc": "Wayland için düşünülmüş, Quickshell ile inşa edilmiş şık ve minimal masaüstü kabuğu.", "noctalia-git-commit": "Git commit:", "noctalia-installed-version": "Yüklü sürüm:", "noctalia-latest-version": "En son sürüm:", + "noctalia-qs-version": "Noctalia QS sürümü:", "noctalia-title": "Noctalia kabuğu", "privacy-policy": "Gizlilik politikası", "support": "Bizi destekleyin", @@ -720,6 +740,7 @@ "supporters-desc": "Harika destekçimize kocaman bir teşekkür!", "supporters-desc-plural": "{count} harika destekçimize kocaman teşekkürler!", "supporters-loading": "Destekçiler yükleniyor...", + "system-board": "Anakart:", "system-cpu": "İşlemci:", "system-disk": "Disk:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "Araç Çubuğu yoğunluğu", "appearance-desc": "Araç çubuğunun görünümünü ve konumunu özelleştirin.", "appearance-display-mode-description": "Çubuğun ne zaman görünür olacağını seçin.", + "appearance-enable-exclusion-zone-inset-description": "Hariç tutma bölgesini 1 fiziksel piksel kadar azaltın, böylece Bar kenarının altına tam oturan pencereler kusursuzca taşar.", + "appearance-enable-exclusion-zone-inset-label": "Girintili dışlama bölgesi", "appearance-floating-description": "Araç çubuğunu yüzen bir 'kapsül' olarak görüntüler. Not: Bu, ekran köşelerini kenarlara taşıyacaktır.", "appearance-floating-label": "Yüzen araç çubuğu", "appearance-font-scale-description": "Çubukta görüntülenen metin için yazı tipi boyutu ölçeğini ayarlayın.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Kompozitör önizlemesi aktif olduğunda çubuğu gizle ve panelleri kapat.", "appearance-hide-on-overview-label": "Genel bakışta çubuğu gizle", "appearance-margins-description": "Yüzen araç çubuğunun etrafındaki kenar boşluklarını ayarlayın.", - "appearance-margins-horizontal": "Yatay", - "appearance-margins-label": "Kenar boşlukları", - "appearance-margins-vertical": "Dikey", + "appearance-margins-horizontal": "Yatay kenar boşluğu", + "appearance-margins-vertical": "Dikey kenar boşluğu", "appearance-outer-corners-description": "Çubuğun dışa doğru kavisli köşelerini görüntüler.", "appearance-outer-corners-label": "Dış köşeler", "appearance-position-description": "Araç çubuğunu ekranda nereye yerleştireceğinizi seçin.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Ayrı çubuk opaklığı kullan", "appearance-widget-spacing-description": "Çubuktaki her bir widget arasındaki boşluğu ayarlayın.", "appearance-widget-spacing-label": "Widget aralığı", + "behavior-middle-click-command-description": "Orta tıklamada çalıştırılacak komut.", + "behavior-middle-click-command-label": "Orta Tıklama Komutu", + "behavior-middle-click-command-placeholder": "niri msg action genel bakışı değiştir", + "behavior-middle-click-description": "Çubuğun boş alanlarında orta tıklamanın ne yapacağını seçin.", + "behavior-middle-click-follow-mouse-description": "Seçilen orta tıklama panelini imleç konumunda aç.", + "behavior-middle-click-follow-mouse-label": "Orta tıklama fareyi takip etsin", + "behavior-middle-click-label": "Çubuk orta tıklama eylemi", + "behavior-right-click-command-description": "Sağ tıklamada çalıştırılacak komut.", + "behavior-right-click-command-label": "Sağ Tıklama Komutu", + "behavior-right-click-command-placeholder": "notify-send \"Sağ tıklama\"", + "behavior-right-click-description": "Çubuğun boş alanlarında sağ tıklamanın ne yapacağını seçin.", + "behavior-right-click-follow-mouse-description": "Seçili sağ tık panelini imleç konumunda aç.", + "behavior-right-click-follow-mouse-label": "Sağ tıklama fareyi takip etsin", + "behavior-right-click-label": "Çubuğa sağ tıklama eylemi", + "behavior-wheel-wrap-description": "Etkinleştirildiğinde, kaydırma son öğeden ilk öğeye doğru devam eder.", + "behavior-wheel-wrap-label": "Döngüsel", + "behavior-workspace-scroll-description": "Fare tekerleğinin çubuğun boş alanlarında ne yapacağını seçin.", + "behavior-workspace-scroll-label": "Çubuk fare tekerleği eylemi", + "behavior-workspace-scroll-option-content": "İçerik", + "behavior-workspace-scroll-option-workspace": "Çalışma Alanı", "monitor-configure-widgets": "Araçları yapılandır", "monitor-override-settings": "Genel ayarları geçersiz kıl", "monitor-override-settings-description": "Bu monitör için özel ayarları kullan.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Kimlik doğrulama gerekli", + "bluetooth-auto-connect-description": "Bluetooth etkinleştirildiğinde güvenilir eşleştirilmiş cihazlara otomatik olarak bağlan.", + "bluetooth-auto-connect-label": "Eşleştirilmiş cihazları otomatik bağla", "bluetooth-devices-unnamed": "Adlandırılmamış cihazlar gösterilmez.", "bluetooth-discoverable": "Bu ayarlar sekmesi açıkken bu cihaz {hostName} olarak keşfedilebilir.", "bluetooth-rssi-polling-description": "bluetoothctl aracılığıyla bağlı cihazların RSSI değerini periyodik olarak örnekler. Tüm cihazlar için mevcut olmayabilir; etkinleştirildiğinde minimum kaynak kullanır.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Kontrol merkezinde hangi kontrollerin ve hangi sırada görüneceğini özelleştirin.", "desc": "Kontrol merkezi paneli konumunu ve davranışını yapılandırın.", + "open-at-mouse-description": "Bar'ın boş bir alanına sağ tıkladığınızda, Kontrol Merkezi'ni imlecin yakınında açın. Devre dışı bırakıldığında, bunun yerine yukarıdaki konum ayarı kullanılır.", + "open-at-mouse-label": "Sağ tıklamada imleç konumunda aç", "position-description": "Kontrol merkezi paneli açıldığında nerede görüneceğini seçin.", "shortcuts-custom-button-command-description": "Düğmeye tıklandığında çalıştırılacak komut.", "shortcuts-custom-button-enable-on-state-logic-description": "Bir kontrol komutuna bağlı olarak ikinci bir simgeyi ve 'etkin' durumunu etkinleştir.", @@ -983,6 +1029,7 @@ "edit-mode-description": "Masaüstü araçlarını taşımak ve yeniden konumlandırmak için düzenleme modunu etkinleştirin. Etkinleştirildiğinde, araçlar bir sürükleme ana hattı gösterir ve yeniden konumlandırılabilir.", "edit-mode-exit-button": "Düzenleme modundan çık", "edit-mode-grid-snap-label": "Izgaraya hizala", + "edit-mode-grid-snap-scale-label": "Izgara hizalama ölçeği", "edit-mode-label": "Düzenleme modu", "enabled-description": "Masaüstü araçlarını tamamen etkinleştir veya devre dışı bırak.", "enabled-label": "Masaüstü araçlarını etkinleştir", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Otomatik gizle", "appearance-display-description": "Dock’un nasıl davranacağını seçin.", "appearance-display-exclusive": "Özel", + "appearance-dock-indicator-description": "Dock gizlendiğinde küçük bir gösterge göster.", + "appearance-dock-indicator-label": "Dock göstergesi", "appearance-floating-distance-description": "Dock ile ekran kenarı arasındaki mesafeyi ayarlayın.", "appearance-floating-distance-label": "Dock yüzen mesafesi", - "appearance-frame-indicator-description": "Dock gizlendiğinde çerçevede küçük bir gösterge göster.", - "appearance-frame-indicator-label": "Çerçeveli dock göstergesi", "appearance-group-apps-description": "Aynı uygulamanın birden fazla penceresini tek bir Dock girişinde grupla.", "appearance-group-apps-label": "Aynı uygulamaları grupla", "appearance-group-click-action-cycle": "Pencereler arasında geçiş yap", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Dock boyutu", "appearance-inactive-indicators-description": "Tüm uygulamalar için, sadece etkin olan için değil, gösterge haplarını göster.", "appearance-inactive-indicators-label": "Çalışan göstergeler", + "appearance-indicator-color-description": "Gizli dock göstergesinin rengini seçin.", + "appearance-indicator-color-label": "Gösterge rengi", + "appearance-indicator-opacity-description": "Gizli dock göstergesinin opaklığını ayarlayın.", + "appearance-indicator-opacity-label": "Gösterge opaklığı", + "appearance-indicator-thickness-description": "Daha kalın bir gizli dock göstergesi kullanın (3 px yerine 6 px).", + "appearance-indicator-thickness-label": "Daha kalın gösterge", "appearance-launcher-position-description": "Başlatıcı simgesinin dock'ta nerede görüneceğini seçin.", "appearance-launcher-position-end": "Son", "appearance-launcher-position-label": "Başlatıcı konumu", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Otomatik", "language-select-description": "Uygulama arayüzünde kullanılacak dili seçin.", "language-select-label": "Uygulama dili", - "launch-setup-wizard": "Kurulum sihirbazını başlat", + "launch-setup-wizard": "Kurulum sihirbazı", "profile-desc": "Kullanıcı detaylarınızı ve avatarınızı düzenleyin.", "profile-picture-description": "Arayüzün tamamında görünen profil fotoğrafınız.", "profile-picture-label": "Profil resmi", @@ -1165,14 +1218,14 @@ "screen-corners-title": "Ekran köşeleri", "settings-copied": "Ayarlar panoya kopyalandı", "tab-basics": "Temeller", - "tab-keybinds": "Tuş Atamaları" + "tab-keybinds": "Tuş atamaları" }, "hooks": { "info-command-info-description": "• Komutlar kabuk (sh -lc) ile yürütülür
• Komutlar arka planda yürütülür (ayrık)
• Test düğmeleri mevcut değerlerle yürütülür", - "info-parameters-description": "• Duvar kağıdı kancası: $1 = duvar kağıdı yolu, $2 = ekran adı
• Tema değiştirme kancası: $1 = doğru/yanlış (Karanlık Mod durumu)
• Ekran kilitleme/kilidi açma kancaları: Parametre yok
• Performans modu kancaları: Parametre yok
• Oturum kancası: $1 = eylem (kapatma/yeniden başlatma)", + "info-parameters-description": "• Duvar Kağıdı Kancası: $1 = duvar kağıdı yolu, $2 = ekran adı
• Tema Geçiş Kancası: $1 = true/false (Dark Mode durumu)
• Ekran Kilitleme/Kilit Açma Kancaları: $1 = lock/unlock (ekran kilidi durumu)
• Performans Modu Kancaları: Parametre yok
• Oturum Kancası: $1 = action (shutdown/reboot)", "info-parameters-label": "Kullanılabilir parametreler", "noctalia-started-description": "Noctalia yüklendiğinde çalıştırılacak komut.", - "noctalia-started-label": "Noctalia Başladı", + "noctalia-started-label": "Noctalia başladı", "noctalia-started-placeholder": "örn. notify-send 'Noctalia Yüklendi'", "performance-mode-disabled-description": "Noctalia performans modu devre dışı bırakıldığında çalıştırılacak komut.", "performance-mode-disabled-label": "Performans modu devre dışı bırakıldı", @@ -1206,23 +1259,32 @@ "custom-description": "Bir süre hareketsizlikten sonra bir kabuk komutu çalıştır.", "custom-entry-command": "Komut", "custom-entry-delete": "Sil", + "custom-entry-edit": "Özel Komutu Düzenle", + "custom-entry-name": "Ad", + "custom-entry-name-placeholder": "örn. Işıkları kapat", + "custom-entry-new": "Yeni Özel Komut", "custom-entry-timeout": "Boşta kalma süresi", - "custom-label": "Özel Boşta Kalma Komutları", + "custom-entry-timeout-format": "{count} saniye", + "custom-entry-timeout-format-plural": "{count} saniye", + "custom-entry-unnamed": "İsimsiz komut", + "custom-label": "Özel boşta kalma komutları", "enable-description": "Belirli bir süre işlem yapılmadığında ekranı otomatik olarak kapat, kilitle veya askıya al.", "enable-label": "Boşta kalma yönetimini etkinleştir", "fade-duration-description": "Her eylem tetiklenmeden önce kararan animasyonun saniye cinsinden süresi. Herhangi bir fare hareketi kararmayı iptal eder.", "fade-duration-label": "Solma süresi", "lock-description": "Kilit ekranı etkinleşmeden önceki hareketsizlik süresi (saniye).", "lock-label": "Ekranı Kilitle", + "resume-command-label": "Komutu devam ettir", "screen-off-description": "Monitörler kapanmadan önceki hareketsizlik süresi (saniye).", "screen-off-label": "Ekranı kapat", "status-description": "Compositor tarafından bildirilen boşta kalma süresi.", - "status-label": "Boşta Kalma Süresi", + "status-label": "Boşta kalma süresi", "suspend-description": "Sistem askıya alınmadan önceki hareketsizlik süresi (saniye).", "tab-behavior": "Davranış", "tab-custom": "Özel", - "timeouts-description": "Bir aşamayı devre dışı bırakmak için 0 olarak ayarlayın. Keep Awake aktifken zaman aşımları duraklatılır.", - "timeouts-label": "Zaman Aşımları", + "timeouts-description": "Bir aşamayı devre dışı bırakmak için 0 olarak ayarlayın. 'Uyanık kal' aktifken zaman aşımları duraklatılır.", + "timeouts-label": "Zaman aşımları", + "title": "Boşta", "unavailable": "Yerel boşta kalma izleme bu Compositor'da mevcut değil." }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "Saat Stili", "compact-lockscreen-description": "Sadece giriş girdisini ve sistem kontrollerini göster, hava durumu ve ortam araç takımlarını gizle.", "compact-lockscreen-label": "Kompakt ekran kilidi", + "enable-lockscreen-media-controls-description": "Kilit ekranında etkileşimli medya oynatma kontrollerini göster.", + "enable-lockscreen-media-controls-label": "Kilit Ekranı Medya Kontrolleri", "lock-on-suspend-description": "Sistemi askıya alırken otomatik olarak ekranı kilitler.", "lock-on-suspend-label": "Askıya alırken kilitle", "lock-screen-animations-description": "Kilit ekranı animasyonlarını etkinleştir veya devre dışı bırak.", @@ -1438,12 +1502,12 @@ "duration-desc": "OSD'nin otomatik olarak gizlenmeden önce ne kadar süreyle görüneceği.", "duration-title": "Otomatik gizleme zaman aşımı", "enabled-description": "Ses ve parlaklık değişikliklerini gerçek zamanlı olarak gösterin.", - "enabled-label": "Ekranda gösterim etkinleştir", - "events-desc": "Ekranda gösterim tetikleyecek olayları seçin.", + "enabled-label": "Ekran üstü gösterim (OSD) etkinleştir", + "events-desc": "Ekran üstü gösterimi tetikleyecek olayları seçin.", "general-desc": "OSD'nin görünürlüğünü ve davranışını yapılandırın.", - "location-description": "Ekran görüntülerinin nerede görüneceği.", + "location-description": "Ekran üstü gösterimlerin görüneceği konum.", "monitors-desc": "OSD'yi belirli ekranlarda gösterin. Hiçbiri seçilmezse varsayılan olarak tümünde gösterilir.", - "title": "Ekranda gösterim", + "title": "Ekran üstü gösterim", "types-brightness-description": "Ekran parlaklığı değiştiğinde OSD'yi göster.", "types-brightness-label": "Parlaklık", "types-custom-text-description": "IPC üzerinden özel metin iletileri için OSD göster.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Oturum seçeneklerinde tuş kombinasyonu ipuçlarını görüntüle.", "show-keybinds-label": "Klavye kısayollarını göster" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Noctalia performans modu etkinleştirildiğinde masaüstü widget'larını görünür tut.", + "noctalia-performance-disable-desktop-widgets-label": "Performans modunda masaüstü widget'larını etkinleştir", + "noctalia-performance-disable-wallpaper-description": "Noctalia performans modu etkinleştirildiğinde masaüstü, genel bakış ve kilit ekranı duvar kağıtlarını görünür tutun.", + "noctalia-performance-disable-wallpaper-label": "Performans modunda duvar kağıdı oluşturmayı etkinleştir", + "title": "Sistem" + }, "system-monitor": { "critical-color-label": "Kritik renk", "custom-highlight-colors-title-label": "Özel vurgulama renkleri", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Yoklama aralığı", "polling-section-description": "Her bir sistem metriğinin ne sıklıkta güncelleneceğini yapılandırın.", "polling-section-label": "Yoklama aralıkları", - "threshold-critical": "Kritik eşik", - "threshold-warning": "Uyarı eşiği", + "threshold-critical": "Kritik", + "threshold-warning": "Uyarı", "thresholds-section-description": "Her sistem ölçüsü için uyarı/kritik eşiklerini ve yoklama aralıklarını ayarlayın.", "use-custom-highlight-colors-description": "Devre dışı bırakıldığında, tema varsayılan vurgulama renkleri kullanılır.", "use-custom-highlight-colors-label": "Özel vurgulama renklerini kullan", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Animasyon hızı", "animation-speed-reset": "Animasyon hızını sıfırla", "appearance-desc": "Araç ipuçları, kenarlıklar ve gölgeler gibi görsel öğeleri özelleştirin.", + "blur-behind-description": "Çubukların ve panellerin arkasındaki alanı kompozitör bulanıklık protokolünü kullanarak bulanıklaştırır.", + "blur-behind-label": "Arka plan bulanıklığı", "box-border-description": "İçerik alanlarının etrafında bir çerçeve görüntüler.", "box-border-label": "Konteyner ana hattı", "box-border-radius-description": "Kenar çubukları, kartlar ve içerik panelleri gibi ana düzen bölümlerinin köşe yuvarlaklığını ayarlar.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Panelleri ve çubuğu üstte tut", "scaling-description": "Çubuğu hariç tutarak genel kullanıcı arayüzünün boyutunu değiştirir.", "scaling-label": "Arayüz ölçekleme", + "scrollbar-always-visible-description": "İçerik kaydırılabilir olduğunda kaydırma çubuklarını her zaman görünür tut, yalnızca üzerine gelindiğinde göstermek yerine.", + "scrollbar-always-visible-label": "Kaydırma çubuklarını her zaman göster", "settings-panel-header": "Ayarlar Paneli", "settings-panel-mode-description": "Ayarlar düzenini seçin (yeniden açılması gerekebilir).", "settings-panel-mode-label": "Ayarlar paneli modu", @@ -1619,7 +1694,9 @@ "shadows-label": "Gölge efektleri", "title": "Kullanıcı arayüzü", "tooltips-description": "Arayüz genelindeki ipuçlarını etkinleştirin veya devre dışı bırakın.", - "tooltips-label": "İpuçlarını göster" + "tooltips-label": "İpuçlarını göster", + "translucent-widgets-description": "Panellerin içindeki düğmeleri, sekmeleri ve diğer widget'ları yarı saydam yapın.", + "translucent-widgets-label": "Yarı saydam widget'lar" }, "wallpaper": { "automation-change-mode-alphabetical": "Alfabetik", @@ -1742,6 +1819,7 @@ "select-prompt": "Aşağıdan bir duvar kâğıdı seçin", "subheader": "Güzel bir arka planla ortamı ayarlayın." }, + "welcome": "Hoş Geldiniz", "welcome-note": "Başlamanız için birkaç temel ayar — tam seçenekler ayarlar'da", "welcome-subtitle": "Masaüstünüzü size özel yapalım", "welcome-title": "Noctalia'ya hoş geldiniz!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "{widget} ayarları" }, "system-monitor": { + "core-usage": "Çekirdek {id} kullanımı", "cpu-temp": "CPU sıcaklığı", "cpu-usage": "CPU kullanımı", "disk": "Disk", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Adres panoya kopyalandı", + "auto-connect-disabled": "Otomatik bağlantı devre dışı", + "auto-connect-enabled": "Otomatik bağlanma etkin", + "auto-connecting": "{count} cihaza bağlanılıyor...", "confirm-code": "Diğer cihazdaki {value} kodunu onayla.", "connect-failed": "Cihaza bağlanılamadı", "disconnect-failed": "Cihazla bağlantı kesilemedi", @@ -1800,6 +1882,10 @@ "unavailable": "Panoya geçmişi kullanılamıyor", "unavailable-desc": "'cliphist' uygulaması kurulu değil. Lütfen panoya geçmişi özelliklerini kullanmak için kurun" }, + "custom-command-failed": { + "description": "Komut başarısız oldu: {command}\\nÇıkış kodu: {code}", + "title": "Özel komut başarısız oldu" + }, "do-not-disturb": { "disabled": "'Rahatsız etme' devre dışı", "disabled-desc": "Tüm bildirimler gösteriliyor", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Araç takımı ekle", + "auto-connect": "Bu cihaz için otomatik bağlanmayı aç/kapa", + "bluetooth-auto-connect-off": "Otomatik bağlanma kapalı", + "bluetooth-auto-connect-on": "Otomatik bağlanma açık", "bluetooth-devices": "Bluetooth cihazları", "brightness-at": "Parlaklık: %{brightness}", "click-to-start-recording": "Ekran kaydedici (kaydı başlat)", @@ -1951,6 +2040,11 @@ "resolution-label": "Çözünürlük", "resolution-mode-label": "Mod", "solid-color-tooltip": "Düz renk arka plan", + "sort-date-asc": "En eskiden en yeniye sırala", + "sort-date-desc": "En yeniden en eskiye sırala", + "sort-name-asc": "Ada göre sırala (A-Z)", + "sort-name-desc": "Ada göre sırala (Z-A)", + "sort-random": "Rastgele sırala", "sorting-date-added": "Eklenme tarihi", "sorting-favorites": "Favoriler", "sorting-label": "Sırala", diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index 1433743f9..98c34dbbb 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "Вибрати колір для візуалізатора.", "color-name-label": "Колір заповнення", + "height-description": "Користувацька ширина компонента.", "hide-when-idle-description": "Коли увімкнено, візуалізатор приховано, якщо плеєр активно не відтворює.", "hide-when-idle-label": "Приховати, коли медіа не відтворюється", "width-description": "Власна ширина компонента." @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "Відображати статистику у вигляді міні-діаграм замість текстових значень. Запобігає зсуву макета.", "compact-mode-label": "Компактний режим", + "cpu-cores-description": "Відображати використання ядер CPU окремо.", + "cpu-cores-label": "Ядра CPU", "cpu-frequency-description": "Відобразити поточну тактову частоту CPU у ГГц.", "cpu-frequency-label": "Показати частоту CPU", "cpu-temperature-description": "Показувати показники температури ЦП, якщо доступно.", @@ -291,6 +294,8 @@ "focused-color-label": "Колір сфокусованого робочого столу", "follow-focused-screen-description": "Відображати робочі простори з поточного активного екрана, а не з екрана, на якому розташована панель.", "follow-focused-screen-label": "Слідувати за активним екраном", + "font-weight-description": "Встановити візуальну вагу тексту в робочому просторі.", + "font-weight-label": "Товщина шрифту", "grouped-border-opacity-description": "Встановити рівень непрозорості для меж контейнерів робочої області.", "grouped-border-opacity-label": "Прозорість межі", "hide-unoccupied-description": "Не відображати робочі простори без вікон.", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "Усі батареї (об'єднані)", "battery-health": "Стан батареї", "battery-level": "Рівень заряду акумулятора", "capacity-level": "Місткість: {level}", @@ -374,6 +380,7 @@ "add": "Додати", "appearance": "Зовнішній вигляд", "apply": "Подати заявку", + "auto-connect": "Автоматичне підключення", "automation": "Автоматизація", "available": "Доступний", "back": "Назад", @@ -401,6 +408,7 @@ "contributors": "Учасники", "copied-to-clipboard": "Скопійовано до буфера обміну", "countdown": "Зворотний відлік", + "customize": "Налаштувати", "date": "Дата", "default": "За замовчуванням", "delete": "Видалити", @@ -423,6 +431,11 @@ "execute": "Виконати", "faithful": "Вірний", "focus": "Зосередженість", + "font-weight-bold": "Жирний", + "font-weight-light": "Тонкий", + "font-weight-medium": "Середній", + "font-weight-regular": "Звичайний", + "font-weight-semibold": "Напівжирний", "frequency": "Частота", "gateway": "Шлюз", "general": "Загальне", @@ -445,6 +458,7 @@ "lock": "Заблокувати", "logout": "Вийти", "look": "Вигляд", + "margins": "Поля", "media": "Медіа", "media-player": "Медіапрогравач", "memory": "Пам'ять", @@ -467,6 +481,7 @@ "panels": "Панелі", "password": "Пароль", "pause": "Пауза", + "performance": "Продуктивність", "pin": "Закріпити", "play": "Відтворити", "polling": "Опитування", @@ -519,6 +534,7 @@ "unpin": "Відкріпити", "update": "Оновлення", "upload": "Вивантажити", + "userspace-reboot": "Перезавантажити простір користувача", "version": "Версія", "vibrant": "Яскравий", "visualizer": "Візуалізатор", @@ -703,16 +719,20 @@ "about": { "become-supporter": "Стати прихильником", "changelog": "Переглянути журнал змін", + "changelog-on-startup": "Показувати журнал змін під час оновлення", + "changelog-on-startup-desc": "Автоматично показувати журнал змін при оновленні Noctalia.", "contributors-description": "Подяка нашому {count} чудовому учаснику!", "contributors-description-plural": "Подяка нашим {count} чудовим учасникам!", "copy-info": "Копіювати інформацію", "debug-disabled": "Режим налагодження вимкнено", "debug-enabled": "Режим налагодження увімкнено", "info-copied": "Інформацію скопійовано до буфера обміну", + "noctalia-available": "Доступно:", "noctalia-desc": "Елегантна та мінімалістична оболонка робочого столу, ретельно створена для Wayland, побудована на Quickshell.", "noctalia-git-commit": "Git коміт:", "noctalia-installed-version": "Встановлена версія:", "noctalia-latest-version": "Остання версія:", + "noctalia-qs-version": "Noctalia QS версія:", "noctalia-title": "Оболонка Noctalia", "privacy-policy": "Політика конфіденційності", "support": "Підтримати нас", @@ -720,6 +740,7 @@ "supporters-desc": "Величезне дякую нашому чудовому прихильнику!", "supporters-desc-plural": "Величезна подяка нашим {count} чудовим прихильникам!", "supporters-loading": "Завантаження прихильників...", + "system-board": "Материнська плата:", "system-cpu": "ЦП:", "system-disk": "Диск:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "Щільність панелі", "appearance-desc": "Налаштуйте зовнішній вигляд та положення панелі.", "appearance-display-mode-description": "Виберіть, коли панель видима.", + "appearance-enable-exclusion-zone-inset-description": "Зменшіть зону виключення на 1 фізичний піксель, щоб вікна впритул ідеально заходили під край Bar.", + "appearance-enable-exclusion-zone-inset-label": "Внутрішня зона виключення", "appearance-floating-description": "Відображати панель як плаваючу 'капсулу'. Примітка: Це перемістить кути екрана до країв.", "appearance-floating-label": "Плаваюча панель (Острівець)", "appearance-font-scale-description": "Налаштуйте масштаб розміру шрифту для тексту, що відображається на панелі.", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "Приховати панель і закрити панелі, коли активний огляд компонувальника.", "appearance-hide-on-overview-label": "Приховати панель на огляді", "appearance-margins-description": "Налаштуйте поля навколо плаваючої панелі.", - "appearance-margins-horizontal": "Горизонтальні", - "appearance-margins-label": "Поля", - "appearance-margins-vertical": "Вертикальні", + "appearance-margins-horizontal": "Горизонтальний відступ", + "appearance-margins-vertical": "Вертикальний відступ", "appearance-outer-corners-description": "Відображає назовні закруглені кути на панелі.", "appearance-outer-corners-label": "Зовнішні кути", "appearance-position-description": "Виберіть, де розмістити панель на екрані.", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "Використовувати окрему прозорість стовпців", "appearance-widget-spacing-description": "Налаштуйте інтервал між кожним віджетом на панелі.", "appearance-widget-spacing-label": "Відстань між віджетами", + "behavior-middle-click-command-description": "Команда для виконання за середнім кліком.", + "behavior-middle-click-command-label": "Команда Середнього Кліку", + "behavior-middle-click-command-placeholder": "niri msg action перемкнути огляд", + "behavior-middle-click-description": "Виберіть, що робить середній клік на порожніх ділянках Панелі.", + "behavior-middle-click-follow-mouse-description": "Відкрити вибрану панель середнього кліку в позиції курсора.", + "behavior-middle-click-follow-mouse-label": "Середній клік слідує за курсором", + "behavior-middle-click-label": "Дія середнього кліку на Панелі", + "behavior-right-click-command-description": "Команда для виконання при правому кліку.", + "behavior-right-click-command-label": "Команда Правого Кліка", + "behavior-right-click-command-placeholder": "notify-send \"Правий клік\"", + "behavior-right-click-description": "Виберіть, що робить правий клік на порожніх областях панелі.", + "behavior-right-click-follow-mouse-description": "Відкрити вибрану панель правого кліка в позиції курсора.", + "behavior-right-click-follow-mouse-label": "Правий клік слідує за курсором", + "behavior-right-click-label": "Дія правого кліку на Панелі", + "behavior-wheel-wrap-description": "Якщо ввімкнено, прокрутка продовжується від останнього елемента до першого.", + "behavior-wheel-wrap-label": "По колу", + "behavior-workspace-scroll-description": "Виберіть, що робить коліщатко миші на порожніх ділянках панелі.", + "behavior-workspace-scroll-label": "Дія колеса миші Панелі", + "behavior-workspace-scroll-option-content": "Вміст", + "behavior-workspace-scroll-option-workspace": "Робоча область", "monitor-configure-widgets": "Налаштувати віджети", "monitor-override-settings": "Перевизначити глобальні налаштування", "monitor-override-settings-description": "Використовувати власні налаштування для цього монітора.", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "Потрібна автентифікація", + "bluetooth-auto-connect-description": "Автоматично підключатися до довірених сполучених пристроїв, коли Bluetooth увімкнено.", + "bluetooth-auto-connect-label": "Автоматично підключати спарені пристрої", "bluetooth-devices-unnamed": "Безіменні пристрої не відображаються.", "bluetooth-discoverable": "Цей пристрій можна виявити як {hostName}, поки ця вкладка налаштувань відкрита.", "bluetooth-rssi-polling-description": "Періодично вимірює RSSI для підключених пристроїв через bluetoothctl. Може бути недоступно для всіх пристроїв; використовує мінімальні ресурси, якщо увімкнено.", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "Налаштуйте, які елементи керування з'являються в центрі керування та в якому порядку.", "desc": "Налаштуйте положення та поведінку панелі центру керування.", + "open-at-mouse-description": "При натисканні правою кнопкою миші на порожній області Панелі, відкрийте Центр керування поруч з курсором. Якщо вимкнено, замість цього використовується налаштування позиції вище.", + "open-at-mouse-label": "Відкривати біля курсора за правим кліком", "position-description": "Виберіть, де з'являється панель центру керування при відкритті.", "shortcuts-custom-button-command-description": "Команда, яка буде виконана при натисканні кнопки.", "shortcuts-custom-button-enable-on-state-logic-description": "Увімкнути другу іконку та 'гарячий' стан на основі команди перевірки.", @@ -983,6 +1029,7 @@ "edit-mode-description": "Увімкніть режим редагування, щоб переміщувати та змінювати розташування віджетів робочого столу. У ввімкненому стані віджети відображають контур перетягування, і їх можна переміщувати.", "edit-mode-exit-button": "Вийти з режиму редагування", "edit-mode-grid-snap-label": "Прив'язка до сітки", + "edit-mode-grid-snap-scale-label": "Масштаб прив'язки до сітки", "edit-mode-label": "Режим редагування", "enabled-description": "Увімкнути або вимкнути віджети робочого столу повністю.", "enabled-label": "Увімкнути віджети робочого столу", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "Автоприховування", "appearance-display-description": "Виберіть, як поводиться док.", "appearance-display-exclusive": "Винятковий", + "appearance-dock-indicator-description": "Показувати невеликий індикатор, коли док приховано.", + "appearance-dock-indicator-label": "Індикатор дока", "appearance-floating-distance-description": "Встановіть відстань між доком і краєм екрана.", "appearance-floating-distance-label": "Відстань плавання дока", - "appearance-frame-indicator-description": "Показувати невеликий індикатор на рамці, коли док приховано.", - "appearance-frame-indicator-label": "Рамочний індикатор дока", "appearance-group-apps-description": "Групувати кілька вікон з однієї програми в один запис Dock.", "appearance-group-apps-label": "Групувати однакові програми", "appearance-group-click-action-cycle": "Перемикати вікна", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Розмір дока", "appearance-inactive-indicators-description": "Відображати індикатори-пігулки для всіх програм, а не лише для поточної активної.", "appearance-inactive-indicators-label": "Індикатори бігу", + "appearance-indicator-color-description": "Виберіть колір індикатора прихованого доку.", + "appearance-indicator-color-label": "Колір індикатора", + "appearance-indicator-opacity-description": "Налаштуйте непрозорість індикатора прихованого доку.", + "appearance-indicator-opacity-label": "Непрозорість індикатора", + "appearance-indicator-thickness-description": "Увімкнути товстіший індикатор прихованого доку (6 пікселів замість 3 пікселів).", + "appearance-indicator-thickness-label": "Товстіший індикатор", "appearance-launcher-position-description": "Виберіть, де значок запуску програм з'являється в доці.", "appearance-launcher-position-end": "Кінець", "appearance-launcher-position-label": "Позиція запускача", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "Автоматично", "language-select-description": "Виберіть мову інтерфейсу застосунку.", "language-select-label": "Мова застосунку", - "launch-setup-wizard": "Запустити майстер налаштування", + "launch-setup-wizard": "Майстер налаштування", "profile-desc": "Редагуйте дані користувача та аватар.", "profile-picture-description": "Ваше фото профілю, що відображається в інтерфейсі.", "profile-picture-label": "Фото профілю", @@ -1165,11 +1218,11 @@ "screen-corners-title": "Кути екрана", "settings-copied": "Налаштування скопійовано до буфера обміну", "tab-basics": "Основи", - "tab-keybinds": "Гарячі Клавіші" + "tab-keybinds": "Гарячі клавіші" }, "hooks": { "info-command-info-description": "• Команди виконуються через оболонку (sh -lc)
• Команди запускаються у фоні (відокремлено)
• Кнопки тестування виконують команди з поточними значеннями", - "info-parameters-description": "• Хук шпалер: $1 = шлях до шпалер, $2 = ім'я екрана
• Хук перемикання теми: $1 = true/false (стан темного режиму)
• Хуки блокування/розблокування екрана: Без параметрів
• Хуки режиму продуктивності: Без параметрів
• Хук сеансу: $1 = дія (вимкнення/перезавантаження)", + "info-parameters-description": "• Хук шпалер: $1 = шлях до шпалер, $2 = назва екрана
• Хук перемикання теми: $1 = true/false (стан Dark Mode)
• Хуки блокування/розблокування екрана: $1 = lock/unlock (стан блокування екрана)
• Хуки режиму продуктивності: Без параметрів
• Хук сесії: $1 = action (shutdown/reboot)", "info-parameters-label": "Доступні параметри", "noctalia-started-description": "Команда для виконання після завершення завантаження Noctalia.", "noctalia-started-label": "Noctalia запущено", @@ -1206,23 +1259,32 @@ "custom-description": "Виконати команду оболонки після періоду бездіяльності.", "custom-entry-command": "Команда", "custom-entry-delete": "Видалити", + "custom-entry-edit": "Редагувати Власну Команду", + "custom-entry-name": "Ім'я", + "custom-entry-name-placeholder": "напр. Вимкнути світло", + "custom-entry-new": "Нова Користувацька Команда", "custom-entry-timeout": "Час бездіяльності", - "custom-label": "Користувацькі Команди Бездіяльності", + "custom-entry-timeout-format": "{count} секунда", + "custom-entry-timeout-format-plural": "{count} секунд", + "custom-entry-unnamed": "Безіменна команда", + "custom-label": "Користувацькі команди бездіяльності", "enable-description": "Автоматично вимикати екран, блокувати або призупиняти роботу після періоду бездіяльності.", "enable-label": "Увімкнути керування бездіяльністю", "fade-duration-description": "Секунди для анімації затемнення до чорного перед виконанням кожної дії. Будь-який рух миші скасовує затемнення.", "fade-duration-label": "Тривалість згасання", "lock-description": "Секунди бездіяльності до активації екрана блокування.", "lock-label": "Заблокувати екран", + "resume-command-label": "Відновити команду", "screen-off-description": "Секунди бездіяльності до вимкнення моніторів.", "screen-off-label": "Вимкнути екран", - "status-description": "Час простою, повідомлений Compositor.", - "status-label": "Час Простою", + "status-description": "Час простою, який повідомляє композитор.", + "status-label": "Час простою", "suspend-description": "Секунди бездіяльності до призупинення роботи системи.", "tab-behavior": "Поведінка", "tab-custom": "Користувацький", - "timeouts-description": "Встановіть 0, щоб вимкнути етап. Тайм-аути призупиняються, поки Keep Awake активний.", + "timeouts-description": "Встановіть 0, щоб вимкнути етап. Тайм-аути призупиняються, поки 'Заборона сну' активний.", "timeouts-label": "Тайм-аути", + "title": "Бездіяльність", "unavailable": "Нативний моніторинг бездіяльності недоступний на цьому Compositorі." }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "Стиль Годинника", "compact-lockscreen-description": "Показувати тільки поле входу та системні елементи керування, приховуючи віджети погоди та медіа.", "compact-lockscreen-label": "Компактний екран блокування", + "enable-lockscreen-media-controls-description": "Показувати інтерактивні елементи керування відтворенням медіа на екрані блокування.", + "enable-lockscreen-media-controls-label": "Елементи Керування Медіа на Екрані Блокування", "lock-on-suspend-description": "Автоматично блокувати екран при призупиненні системи.", "lock-on-suspend-label": "Блокувати при призупиненні", "lock-screen-animations-description": "Увімкнути або вимкнути анімації екрана блокування.", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "Показувати підказки щодо комбінацій клавіш у параметрах сеансу.", "show-keybinds-label": "Показати комбінації клавіш" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "Залишати віджети робочого столу видимими, поки режим продуктивності Noctalia увімкнено.", + "noctalia-performance-disable-desktop-widgets-label": "Увімкнути віджети робочого столу в режимі продуктивності", + "noctalia-performance-disable-wallpaper-description": "Залишайте шпалери робочого столу, огляду та екрана блокування видимими, коли режим продуктивності Noctalia увімкнено.", + "noctalia-performance-disable-wallpaper-label": "Увімкнути відтворення шпалер у режимі продуктивності", + "title": "Система" + }, "system-monitor": { "critical-color-label": "Критичний колір", "custom-highlight-colors-title-label": "Власні кольори підсвічування", @@ -1573,8 +1644,8 @@ "polling-interval-label": "Інтервал опитування", "polling-section-description": "Налаштуйте, як часто оновлюється кожна системна метрика.", "polling-section-label": "Інтервали опитування", - "threshold-critical": "Критичний поріг", - "threshold-warning": "Порогове значення попередження", + "threshold-critical": "Критичний", + "threshold-warning": "Попередження", "thresholds-section-description": "Налаштуйте пороги попередження/критичні пороги та інтервали опитування для кожного системного показника.", "use-custom-highlight-colors-description": "Якщо вимкнено, використовуються кольори підсвічування за замовчуванням теми.", "use-custom-highlight-colors-label": "Використовувати власні кольори підсвічування", @@ -1589,6 +1660,8 @@ "animation-speed-label": "Швидкість анімації", "animation-speed-reset": "Скинути швидкість анімації", "appearance-desc": "Налаштовуйте візуальні елементи, такі як підказки, рамки та тіні.", + "blur-behind-description": "Розмиває область за панелями та спливаючими вікнами, використовуючи протокол розмиття композитора.", + "blur-behind-label": "Розмиття фону", "box-border-description": "Відображає контур навколо областей вмісту.", "box-border-label": "Контур контейнера", "box-border-radius-description": "Налаштовує заокруглення кутів основних розділів макета, таких як бічні панелі, картки та панелі контенту.", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "Тримати панелі та панель зверху", "scaling-description": "Змінює розмір загального користувацького інтерфейсу, окрім панелі.", "scaling-label": "Масштабування інтерфейсу", + "scrollbar-always-visible-description": "Завжди показувати смуги прокрутки, коли вміст можна прокручувати, замість того, щоб відображати їх лише при наведенні.", + "scrollbar-always-visible-label": "Завжди показувати смуги прокрутки", "settings-panel-header": "Панель налаштувань", "settings-panel-mode-description": "Виберіть макет налаштувань (може знадобитися перезапуск).", "settings-panel-mode-label": "Режим панелі налаштувань", @@ -1619,7 +1694,9 @@ "shadows-label": "Тіні", "title": "Користувацький інтерфейс", "tooltips-description": "Увімкнути або вимкнути підказки в інтерфейсі.", - "tooltips-label": "Показувати підказки" + "tooltips-label": "Показувати підказки", + "translucent-widgets-description": "Зробіть кнопки, вкладки та інші віджети всередині панелей напівпрозорими.", + "translucent-widgets-label": "Напівпрозорі віджети" }, "wallpaper": { "automation-change-mode-alphabetical": "Алфавітний", @@ -1742,6 +1819,7 @@ "select-prompt": "Виберіть шпалери нижче", "subheader": "Створіть настрій за допомогою красивого фону." }, + "welcome": "Ласкаво просимо", "welcome-note": "Лише кілька основних речей для початку — повні опції в налаштуваннях", "welcome-subtitle": "Зробімо ваш робочий стіл унікальним", "welcome-title": "Вітаємо в Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "Налаштування {widget}" }, "system-monitor": { + "core-usage": "Використання ядра {id}", "cpu-temp": "Температура ЦП", "cpu-usage": "Використання ЦП", "disk": "Диск", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "Адресу скопійовано до буфера обміну", + "auto-connect-disabled": "Автоматичне підключення вимкнено", + "auto-connect-enabled": "Автоматичне підключення увімкнено", + "auto-connecting": "Підключення до {count} пристрою(їв)...", "confirm-code": "Підтвердьте код {value} на іншому пристрої.", "connect-failed": "Не вдалося під'єднатися до пристрою", "disconnect-failed": "Не вдалося від'єднатися від пристрою", @@ -1800,6 +1882,10 @@ "unavailable": "Історія буфера обміну недоступна", "unavailable-desc": "Застосунок 'cliphist' не встановлений. Будь ласка, встановіть його для використання функцій історії буфера обміну" }, + "custom-command-failed": { + "description": "Команда не виконана: {command}\\nКод виходу: {code}", + "title": "Користувацька команда не виконана" + }, "do-not-disturb": { "disabled": "'Не турбувати' вимкнено", "disabled-desc": "Показ усіх сповіщень", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "Додати віджет", + "auto-connect": "Перемкнути автопідключення для цього пристрою", + "bluetooth-auto-connect-off": "Автоматичне підключення вимкнено", + "bluetooth-auto-connect-on": "Автоматичне підключення увімкнено", "bluetooth-devices": "Пристрої Bluetooth", "brightness-at": "Яскравість: {brightness}%", "click-to-start-recording": "Запис екрана (почати запис)", @@ -1951,6 +2040,11 @@ "resolution-label": "Роздільна здатність", "resolution-mode-label": "Режим", "solid-color-tooltip": "Однотонний фон", + "sort-date-asc": "Сортувати від найстаріших до нових", + "sort-date-desc": "Сортувати від нових до найстаріших", + "sort-name-asc": "Сортувати за назвою (А-Я)", + "sort-name-desc": "Сортувати за назвою (Я-А)", + "sort-random": "Сортувати випадково", "sorting-date-added": "Дата додавання", "sorting-favorites": "Обране", "sorting-label": "Сортувати за", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 5890027e1..e1e8854bb 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "选择可视化工具的颜色。", "color-name-label": "填充颜色", + "height-description": "自定义组件宽度。", "hide-when-idle-description": "启用后,除非正在播放媒体,否则隐藏可视化显示。", "hide-when-idle-label": "无媒体播放时隐藏", "width-description": "自定义组件的宽度。" @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "将数据以条形图显示以防止布局偏移。", "compact-mode-label": "紧凑模式", + "cpu-cores-description": "单独显示 CPU 核心使用率。", + "cpu-cores-label": "CPU 核心", "cpu-frequency-description": "显示当前 CPU 时钟速度(GHz)。", "cpu-frequency-label": "显示 CPU 频率", "cpu-temperature-description": "显示 CPU 温度(如果可用)。", @@ -291,6 +294,8 @@ "focused-color-label": "聚焦工作区颜色", "follow-focused-screen-description": "显示当前焦点屏幕的工作区,而不是任务栏所在屏幕的工作区。", "follow-focused-screen-label": "跟随焦点屏幕", + "font-weight-description": "设置工作区内文本的视觉权重。", + "font-weight-label": "字体粗细", "grouped-border-opacity-description": "设置工作区容器边框的不透明度级别。", "grouped-border-opacity-label": "边框不透明度", "hide-unoccupied-description": "不显示没有窗口的工作区。", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "所有电池(组合)", "battery-health": "电池健康", "battery-level": "电池电量", "capacity-level": "容量:{level}", @@ -374,6 +380,7 @@ "add": "添加", "appearance": "外观", "apply": "应用", + "auto-connect": "自动连接", "automation": "自动化", "available": "可用", "back": "返回", @@ -401,6 +408,7 @@ "contributors": "贡献者", "copied-to-clipboard": "已复制到剪贴板", "countdown": "倒计时", + "customize": "自定义", "date": "日期", "default": "默认", "delete": "删除", @@ -423,6 +431,11 @@ "execute": "执行", "faithful": "忠实", "focus": "专注", + "font-weight-bold": "粗体", + "font-weight-light": "细", + "font-weight-medium": "中", + "font-weight-regular": "常规", + "font-weight-semibold": "半粗体", "frequency": "频率", "gateway": "网关", "general": "通用", @@ -445,6 +458,7 @@ "lock": "锁屏", "logout": "退出登录", "look": "样式", + "margins": "边距", "media": "媒体", "media-player": "媒体播放器", "memory": "内存", @@ -467,6 +481,7 @@ "panels": "面板", "password": "密码", "pause": "暂停", + "performance": "性能", "pin": "固定", "play": "播放", "polling": "数据轮询", @@ -519,6 +534,7 @@ "unpin": "取消固定", "update": "更新", "upload": "上传", + "userspace-reboot": "重启用户空间", "version": "版本", "vibrant": "鲜艳", "visualizer": "可视化工具", @@ -703,16 +719,20 @@ "about": { "become-supporter": "成为支持者", "changelog": "查看更改日志", + "changelog-on-startup": "更新时显示更新日志", + "changelog-on-startup-desc": "Noctalia 更新时自动显示更新日志。", "contributors-description": "向我们 {count} 位超棒的贡献者致敬!", "contributors-description-plural": "向我们 {count} 位超棒的贡献者致敬!", "copy-info": "复制信息", "debug-disabled": "调试模式已禁用", "debug-enabled": "调试模式已启用", "info-copied": "信息已复制到剪贴板", + "noctalia-available": "可用:", "noctalia-desc": "一款为 Wayland 精心打造的时尚简约桌面 shell,基于 Quickshell 构建。", "noctalia-git-commit": "Git 提交:", "noctalia-installed-version": "已安装版本:", "noctalia-latest-version": "最新版本:", + "noctalia-qs-version": "Noctalia QS 版本:", "noctalia-title": "Noctalia shell", "privacy-policy": "隐私政策", "support": "支持我们", @@ -720,6 +740,7 @@ "supporters-desc": "非常感谢我们出色的支持者!", "supporters-desc-plural": "非常感谢我们的{count}位出色支持者!", "supporters-loading": "正在加载支持者...", + "system-board": "主板:", "system-cpu": "中央处理器:", "system-disk": "磁盘:", "system-gpu": "图形处理器:", @@ -810,6 +831,8 @@ "appearance-density-label": "状态栏密度", "appearance-desc": "自定义状态栏的外观和位置。", "appearance-display-mode-description": "选择栏何时可见。", + "appearance-enable-exclusion-zone-inset-description": "减少排除区域1个物理像素,以便齐平的窗口完美地延伸到 Bar 边缘下方。", + "appearance-enable-exclusion-zone-inset-label": "内嵌排除区域", "appearance-floating-description": "将状态栏显示为浮动的“药丸”形状。", "appearance-floating-label": "浮动状态栏", "appearance-font-scale-description": "调整栏中显示的文本的字体大小比例。", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "当合成器概览处于活动状态时,隐藏栏并关闭面板。", "appearance-hide-on-overview-label": "在概览中隐藏栏", "appearance-margins-description": "调整浮动状态栏周围的边距。", - "appearance-margins-horizontal": "水平", - "appearance-margins-label": "边距", - "appearance-margins-vertical": "垂直", + "appearance-margins-horizontal": "水平边距", + "appearance-margins-vertical": "垂直边距", "appearance-outer-corners-description": "在栏上显示向外弯曲的角。", "appearance-outer-corners-label": "外角", "appearance-position-description": "选择在屏幕上的位置。", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "使用单独的状态栏不透明度", "appearance-widget-spacing-description": "调整栏中每个小部件之间的间距。", "appearance-widget-spacing-label": "小部件间距", + "behavior-middle-click-command-description": "中键点击时执行的命令。", + "behavior-middle-click-command-label": "中键点击命令", + "behavior-middle-click-command-placeholder": "niri msg action 切换概览", + "behavior-middle-click-description": "选择在栏的空白区域中键单击的操作。", + "behavior-middle-click-follow-mouse-description": "在光标位置打开选定的中键点击面板。", + "behavior-middle-click-follow-mouse-label": "中键点击跟随鼠标", + "behavior-middle-click-label": "栏中键点击操作", + "behavior-right-click-command-description": "右键点击时执行的命令。", + "behavior-right-click-command-label": "右键点击命令", + "behavior-right-click-command-placeholder": "notify-send \"右键点击\"", + "behavior-right-click-description": "选择在栏的空白区域右键点击时执行的操作。", + "behavior-right-click-follow-mouse-description": "在光标位置打开选定的右键面板。", + "behavior-right-click-follow-mouse-label": "右键跟随鼠标", + "behavior-right-click-label": "栏右键点击操作", + "behavior-wheel-wrap-description": "启用后,滚动会从最后一项继续到第一项。", + "behavior-wheel-wrap-label": "循环", + "behavior-workspace-scroll-description": "选择鼠标滚轮在栏的空白区域执行的操作。", + "behavior-workspace-scroll-label": "栏鼠标滚轮操作", + "behavior-workspace-scroll-option-content": "内容", + "behavior-workspace-scroll-option-workspace": "工作区", "monitor-configure-widgets": "配置小部件", "monitor-override-settings": "覆盖全局设置", "monitor-override-settings-description": "为此显示器使用自定义设置。", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "需要认证", + "bluetooth-auto-connect-description": "当 Bluetooth 启用时,自动连接到受信任的已配对设备。", + "bluetooth-auto-connect-label": "自动连接已配对设备", "bluetooth-devices-unnamed": "未命名设备不显示。", "bluetooth-discoverable": "当此设置选项卡打开时,此设备可被发现为 {hostName}。", "bluetooth-rssi-polling-description": "通过 bluetoothctl 定期采样已连接设备的 RSSI。可能并非所有设备都可用;启用时占用最少资源。", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "自定义在控制中心显示的控制项及其顺序。", "desc": "配置控制中心面板的位置和行为。", + "open-at-mouse-description": "右键点击栏的空白区域时,在光标附近打开控制中心。禁用时,将使用上方的位置设置。", + "open-at-mouse-label": "右键点击时在光标处打开", "position-description": "选择打开控制中心时面板出现的位置。", "shortcuts-custom-button-command-description": "单击按钮时执行的命令。", "shortcuts-custom-button-enable-on-state-logic-description": "根据检查命令启用第二个图标和“高亮”状态。", @@ -983,6 +1029,7 @@ "edit-mode-description": "启用编辑模式以移动和重新定位桌面小组件。启用后,小组件会显示拖动轮廓,并且可以重新定位。", "edit-mode-exit-button": "退出编辑模式", "edit-mode-grid-snap-label": "网格对齐", + "edit-mode-grid-snap-scale-label": "网格吸附比例", "edit-mode-label": "编辑模式", "enabled-description": "完全启用或禁用桌面小部件。", "enabled-label": "启用桌面小部件", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "自动隐藏", "appearance-display-description": "选择 Dock 栏的行为方式。", "appearance-display-exclusive": "独占", + "appearance-dock-indicator-description": "当 Dock 隐藏时,显示一个小指示器。", + "appearance-dock-indicator-label": "Dock 指示器", "appearance-floating-distance-description": "调整距离屏幕边缘的浮动距离。", "appearance-floating-distance-label": "Dock 栏浮动距离", - "appearance-frame-indicator-description": "当 Dock 隐藏时,在边框上显示一个小指示器。", - "appearance-frame-indicator-label": "带边框的停靠栏指示器", "appearance-group-apps-description": "将同一应用的多个窗口分组到一个 Dock 条目中。", "appearance-group-apps-label": "分组相同应用", "appearance-group-click-action-cycle": "切换窗口", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Dock 栏大小", "appearance-inactive-indicators-description": "为所有应用显示指示器小药丸,而不仅仅是当前活跃的应用。", "appearance-inactive-indicators-label": "运行指示器", + "appearance-indicator-color-description": "选择隐藏停靠栏指示器的颜色。", + "appearance-indicator-color-label": "指示器颜色", + "appearance-indicator-opacity-description": "调整隐藏停靠栏指示器的不透明度。", + "appearance-indicator-opacity-label": "指示器不透明度", + "appearance-indicator-thickness-description": "启用较粗的隐藏停靠栏指示器(从 3px 增加到 6px)。", + "appearance-indicator-thickness-label": "较粗的指示器", "appearance-launcher-position-description": "选择启动器图标在停靠栏中的显示位置。", "appearance-launcher-position-end": "结束", "appearance-launcher-position-label": "启动器位置", @@ -1145,7 +1198,7 @@ "language-select-auto-detect": "自动检测", "language-select-description": "选择应用程序界面中使用的语言。", "language-select-label": "应用程序语言", - "launch-setup-wizard": "启动安装向导", + "launch-setup-wizard": "设置向导", "profile-desc": "编辑您的用户详细信息和头像。", "profile-picture-description": "在整个界面中显示您的个人头像。", "profile-picture-label": "头像", @@ -1169,7 +1222,7 @@ }, "hooks": { "info-command-info-description": "• 命令通过 shell 执行(sh -lc)
• 命令在后台运行(分离)
• 测试按钮使用当前值执行", - "info-parameters-description": "• 壁纸钩子:$1 = 壁纸路径,$2 = 屏幕名称
• 主题切换钩子:$1 = true/false(深色模式状态)
• 屏幕锁定/解锁钩子:无参数
• 性能模式钩子:无参数
• 会话钩子:$1 = 操作(关机/重启)", + "info-parameters-description": "• 壁纸挂钩: $1 = 壁纸路径, $2 = 屏幕名称
• 主题切换挂钩: $1 = true/false (Dark Mode状态)
• 屏幕锁定/解锁挂钩: $1 = lock/unlock (屏幕锁定状态)
• 性能模式挂钩: 无参数
• 会话挂钩: $1 = action (shutdown/reboot)", "info-parameters-label": "可用参数", "noctalia-started-description": "Noctalia 加载完成后要执行的命令。", "noctalia-started-label": "Noctalia 已启动", @@ -1206,7 +1259,14 @@ "custom-description": "在一段时间不活动后运行 shell 命令。", "custom-entry-command": "命令", "custom-entry-delete": "删除", + "custom-entry-edit": "编辑自定义命令", + "custom-entry-name": "名称", + "custom-entry-name-placeholder": "例如:关灯", + "custom-entry-new": "新建自定义命令", "custom-entry-timeout": "空闲时间", + "custom-entry-timeout-format": "{count} 秒", + "custom-entry-timeout-format-plural": "{count} 秒", + "custom-entry-unnamed": "未命名命令", "custom-label": "自定义空闲命令", "enable-description": "在一段时间不活动后,自动关闭屏幕、锁定或休眠。", "enable-label": "启用空闲管理", @@ -1214,15 +1274,17 @@ "fade-duration-label": "淡入淡出时长", "lock-description": "锁屏激活前的非活动秒数。", "lock-label": "锁定屏幕", + "resume-command-label": "恢复命令", "screen-off-description": "显示器关闭前的不活动秒数。", "screen-off-label": "关闭屏幕", - "status-description": "Compositor报告的空闲时间。", + "status-description": "由混成器报告的空闲时间。", "status-label": "空闲时间", "suspend-description": "系统挂起前的非活动秒数。", "tab-behavior": "行为", "tab-custom": "自定义", - "timeouts-description": "设置为0以禁用一个阶段。Keep Awake激活时,超时会暂停。", + "timeouts-description": "设置为0以禁用一个阶段。保持唤醒激活时,超时会暂停。", "timeouts-label": "超时", + "title": "空闲", "unavailable": "此 Compositor 不支持原生空闲监控。" }, "indicator": { @@ -1334,6 +1396,8 @@ "clock-style-label": "时钟样式", "compact-lockscreen-description": "仅显示登录输入和系统控制,隐藏天气和媒体小部件。", "compact-lockscreen-label": "紧凑型锁屏", + "enable-lockscreen-media-controls-description": "在锁屏界面显示交互式媒体播放控件。", + "enable-lockscreen-media-controls-label": "锁屏媒体控件", "lock-on-suspend-description": "系统挂起时自动锁定屏幕。", "lock-on-suspend-label": "挂起时锁定", "lock-screen-animations-description": "启用或禁用锁屏动画。", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "在会话选项上显示快捷键提示。", "show-keybinds-label": "显示快捷键" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "在 Noctalia 性能模式启用时保持桌面小部件可见。", + "noctalia-performance-disable-desktop-widgets-label": "在性能模式下启用桌面小部件", + "noctalia-performance-disable-wallpaper-description": "在启用 Noctalia 性能模式时,保持桌面、概览和锁屏壁纸可见。", + "noctalia-performance-disable-wallpaper-label": "在性能模式下启用壁纸渲染", + "title": "系统" + }, "system-monitor": { "critical-color-label": "严重颜色", "custom-highlight-colors-title-label": "自定义高亮颜色", @@ -1573,8 +1644,8 @@ "polling-interval-label": "轮询间隔", "polling-section-description": "配置每个系统指标的更新频率。", "polling-section-label": "轮询间隔", - "threshold-critical": "临界阈值", - "threshold-warning": "警告阈值", + "threshold-critical": "严重", + "threshold-warning": "警告", "thresholds-section-description": "为每个系统指标调整警告/严重阈值和轮询间隔。", "use-custom-highlight-colors-description": "禁用时将使用主题默认高亮颜色。", "use-custom-highlight-colors-label": "使用自定义高亮颜色", @@ -1589,6 +1660,8 @@ "animation-speed-label": "动画速度", "animation-speed-reset": "重置动画速度", "appearance-desc": "自定义视觉元素,如提示信息、边框和阴影。", + "blur-behind-description": "使用合成器模糊协议,模糊栏和面板后面的区域。", + "blur-behind-label": "背景模糊", "box-border-description": "显示内容区域周围的轮廓。", "box-border-label": "容器轮廓", "box-border-radius-description": "调整主要布局部分(如侧边栏、卡片和内容面板)的圆角程度。", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "将面板和栏保持在顶部", "scaling-description": "更改通用用户界面大小,不包括栏。", "scaling-label": "界面缩放", + "scrollbar-always-visible-description": "当内容可滚动时,保持滚动条始终可见,而不是仅在悬停时显示。", + "scrollbar-always-visible-label": "始终显示滚动条", "settings-panel-header": "设置面板", "settings-panel-mode-description": "选择「设置」面板的布局(可能需要重新打开「设置」面板才会生效)。", "settings-panel-mode-label": "「设置」面板模式", @@ -1619,7 +1694,9 @@ "shadows-label": "阴影", "title": "用户界面", "tooltips-description": "启用或禁用整个界面的提示信息。", - "tooltips-label": "显示提示信息(Tooltip)" + "tooltips-label": "显示提示信息(Tooltip)", + "translucent-widgets-description": "使面板内的按钮、选项卡和其他小部件半透明。", + "translucent-widgets-label": "半透明小部件" }, "wallpaper": { "automation-change-mode-alphabetical": "按字母顺序", @@ -1725,7 +1802,7 @@ }, "skip-setup": "跳过设置", "telemetry-wizard-done": "明白了!", - "telemetry-wizard-note": "我们尊重您的选择——您随时可以在“设置”中启用或禁用此功能", + "telemetry-wizard-note": "尽在掌控——您随时可在设置中启用或禁用此功能", "telemetry-wizard-subtitle": "我们添加了匿名分析功能以帮助改进 Noctalia", "telemetry-wizard-title": "隐私权更新", "wallpaper": { @@ -1742,6 +1819,7 @@ "select-prompt": "在下方选择一张壁纸", "subheader": "用精美壁纸营造氛围。" }, + "welcome": "欢迎", "welcome-note": "先进行一些基础设置——更多选项可在“设置”中找到", "welcome-subtitle": "让我们一起打造独一无二的桌面", "welcome-title": "欢迎来到 Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "{widget} 设置" }, "system-monitor": { + "core-usage": "核心 {id} 使用率", "cpu-temp": "CPU 温度", "cpu-usage": "CPU 使用率", "disk": "磁盘", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "地址已复制到剪贴板", + "auto-connect-disabled": "自动连接已禁用", + "auto-connect-enabled": "自动连接已启用", + "auto-connecting": "正在连接 {count} 个设备...", "confirm-code": "在另一台设备上确认代码 {value}。", "connect-failed": "无法连接到设备", "disconnect-failed": "无法断开与设备的连接", @@ -1800,6 +1882,10 @@ "unavailable": "剪贴板历史记录不可用", "unavailable-desc": "未安装 'cliphist' 应用程序。请安装它以使用剪贴板历史记录功能" }, + "custom-command-failed": { + "description": "命令失败: {command}\\n退出代码: {code}", + "title": "自定义命令失败" + }, "do-not-disturb": { "disabled": "'勿扰模式'已禁用", "disabled-desc": "显示所有通知", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "添加小部件", + "auto-connect": "切换此设备的自动连接", + "bluetooth-auto-connect-off": "自动连接已禁用", + "bluetooth-auto-connect-on": "自动连接已启用", "bluetooth-devices": "蓝牙设备", "brightness-at": "亮度:{brightness}%", "click-to-start-recording": "屏幕录制器(开始录制)", @@ -1951,6 +2040,11 @@ "resolution-label": "分辨率", "resolution-mode-label": "匹配模式", "solid-color-tooltip": "纯色背景", + "sort-date-asc": "按最早排序", + "sort-date-desc": "按最新排序", + "sort-name-asc": "按名称排序 (A-Z)", + "sort-name-desc": "按名称排序 (Z-A)", + "sort-random": "随机排序", "sorting-date-added": "上传日期", "sorting-favorites": "收藏量", "sorting-label": "排序方式", diff --git a/Assets/Translations/zh-TW.json b/Assets/Translations/zh-TW.json index 060f445cb..d37dff40a 100644 --- a/Assets/Translations/zh-TW.json +++ b/Assets/Translations/zh-TW.json @@ -41,6 +41,7 @@ "audio-visualizer": { "color-name-description": "選取視覺效果的顏色", "color-name-label": "填充顏色", + "height-description": "自訂元件寬度。", "hide-when-idle-description": "當啟用時, 如果沒有正在播放的媒體就隱藏視覺效果", "hide-when-idle-label": "無任何媒體播放時隱藏", "width-description": "自訂部件寬度" @@ -212,6 +213,8 @@ "system-monitor": { "compact-mode-description": "用迷你長條來取代文字數值, 可以避免佈局跑版", "compact-mode-label": "緊湊模式", + "cpu-cores-description": "個別顯示 CPU 核心使用率。", + "cpu-cores-label": "CPU 核心", "cpu-frequency-description": "顯示目前 CPU 時脈速度(GHz)。", "cpu-frequency-label": "顯示 CPU 頻率", "cpu-temperature-description": "如果可用即顯示 CPU 溫度", @@ -291,6 +294,8 @@ "focused-color-label": "聚焦的工作區顏色", "follow-focused-screen-description": "顯示正在聚焦的螢幕所擁有的工作區, 而不是工具列所在的螢幕擁有的工作區", "follow-focused-screen-label": "跟隨焦點所在的螢幕", + "font-weight-description": "設定工作區內文字的視覺權重。", + "font-weight-label": "字體粗細", "grouped-border-opacity-description": "設定工作區邊框的不透明度", "grouped-border-opacity-label": "邊框不透明度", "hide-unoccupied-description": "如果工作區沒有任何視窗則不要顯示", @@ -314,6 +319,7 @@ } }, "battery": { + "all-batteries": "所有電池(組合)", "battery-health": "電池健康度", "battery-level": "電池電量", "capacity-level": "容量:{level}", @@ -374,6 +380,7 @@ "add": "新增", "appearance": "外觀", "apply": "套用", + "auto-connect": "自動連線", "automation": "自動化", "available": "可用", "back": "返回", @@ -401,6 +408,7 @@ "contributors": "貢獻者", "copied-to-clipboard": "已複製到剪貼簿", "countdown": "倒數", + "customize": "自訂", "date": "日期", "default": "預設", "delete": "刪除", @@ -423,6 +431,11 @@ "execute": "執行", "faithful": "忠實", "focus": "關注", + "font-weight-bold": "粗體", + "font-weight-light": "細", + "font-weight-medium": "中", + "font-weight-regular": "一般", + "font-weight-semibold": "半粗體", "frequency": "頻率", "gateway": "網路閘道", "general": "一般", @@ -445,6 +458,7 @@ "lock": "鎖定", "logout": "登出", "look": "樣式", + "margins": "邊距", "media": "媒體", "media-player": "媒體播放器", "memory": "記憶體", @@ -467,6 +481,7 @@ "panels": "面板", "password": "密碼", "pause": "暫停", + "performance": "效能", "pin": "釘選", "play": "播放", "polling": "輪詢", @@ -519,6 +534,7 @@ "unpin": "解除釘選", "update": "更新", "upload": "上傳", + "userspace-reboot": "重新啟動使用者空間", "version": "版本", "vibrant": "鮮明", "visualizer": "視覺效果", @@ -703,16 +719,20 @@ "about": { "become-supporter": "成為支持者", "changelog": "檢視更新日誌", + "changelog-on-startup": "更新時顯示變更日誌", + "changelog-on-startup-desc": "Noctalia 更新時自動顯示更新日誌。", "contributors-description": "特別感謝我們這{count}位超讚的貢獻者!!", "contributors-description-plural": "特別感謝我們這{count}位超讚的貢獻者!!", "copy-info": "複製資訊", "debug-disabled": "偵錯模式已停用", "debug-enabled": "偵錯模式已啟用", "info-copied": "資訊已複製至剪貼簿", + "noctalia-available": "可用:", "noctalia-desc": "一個為 Wayland 精心打造, 兼具簡潔及流暢的桌面介面, 使用 Quickshell 建置", "noctalia-git-commit": "Git commit:", "noctalia-installed-version": "安裝版本:", "noctalia-latest-version": "最新版本:", + "noctalia-qs-version": "Noctalia QS 版本:", "noctalia-title": "Noctalia Shell", "privacy-policy": "隱私權政策", "support": "支持我們", @@ -720,6 +740,7 @@ "supporters-desc": "非常感謝我們出色的支持者!", "supporters-desc-plural": "非常感謝我們的{count}位出色支持者!", "supporters-loading": "正在載入支持者...", + "system-board": "主機板:", "system-cpu": "CPU:", "system-disk": "磁碟:", "system-gpu": "GPU:", @@ -810,6 +831,8 @@ "appearance-density-label": "工具列密度", "appearance-desc": "自訂工具列的外觀及位置", "appearance-display-mode-description": "選擇列何時可見", + "appearance-enable-exclusion-zone-inset-description": "減少排除區域1個實體像素,以便齊平的視窗能完美地延伸到 Bar 邊緣下方。", + "appearance-enable-exclusion-zone-inset-label": "內嵌排除區域", "appearance-floating-description": "將工具列以懸浮的圓弧長條狀顯示", "appearance-floating-label": "懸浮工具列", "appearance-font-scale-description": "調整列中顯示的文字的字體大小比例。", @@ -821,9 +844,8 @@ "appearance-hide-on-overview-description": "當在合成器概覽時, 隱藏工具列並關閉面板", "appearance-hide-on-overview-label": "概覽時隱藏工具列", "appearance-margins-description": "調整懸浮工具列的邊距", - "appearance-margins-horizontal": "水平", - "appearance-margins-label": "邊距", - "appearance-margins-vertical": "垂直", + "appearance-margins-horizontal": "水平邊距", + "appearance-margins-vertical": "垂直邊距", "appearance-outer-corners-description": "將工具列向外延伸顯示畫面圓角", "appearance-outer-corners-label": "外側圓角", "appearance-position-description": "選擇要把工具列放在螢幕哪裡", @@ -840,6 +862,26 @@ "appearance-use-separate-opacity-label": "分開設定工具列不透明度", "appearance-widget-spacing-description": "調整列中每個小工具之間的間距。", "appearance-widget-spacing-label": "小工具間距", + "behavior-middle-click-command-description": "中鍵點擊時執行的指令。", + "behavior-middle-click-command-label": "中鍵點擊指令", + "behavior-middle-click-command-placeholder": "niri msg action 切換概覽", + "behavior-middle-click-description": "選擇在欄的空白區域中鍵點擊的操作。", + "behavior-middle-click-follow-mouse-description": "在游標位置開啟選定的中鍵點擊面板。", + "behavior-middle-click-follow-mouse-label": "中鍵點擊跟隨滑鼠", + "behavior-middle-click-label": "列中鍵點擊動作", + "behavior-right-click-command-description": "右鍵點擊時執行的命令。", + "behavior-right-click-command-label": "右鍵點擊命令", + "behavior-right-click-command-placeholder": "notify-send \"右鍵點擊\"", + "behavior-right-click-description": "選擇在列的空白區域右鍵點擊時執行的操作。", + "behavior-right-click-follow-mouse-description": "在游標位置開啟選定的右鍵面板。", + "behavior-right-click-follow-mouse-label": "右鍵跟隨滑鼠", + "behavior-right-click-label": "欄右鍵點擊操作", + "behavior-wheel-wrap-description": "啟用後,捲動會從最後一項繼續到第一項。", + "behavior-wheel-wrap-label": "循環", + "behavior-workspace-scroll-description": "選擇滑鼠滾輪在欄的空白區域執行什麼操作。", + "behavior-workspace-scroll-label": "列滑鼠滾輪動作", + "behavior-workspace-scroll-option-content": "內容", + "behavior-workspace-scroll-option-workspace": "工作區", "monitor-configure-widgets": "設定小工具", "monitor-override-settings": "覆寫全域設定", "monitor-override-settings-description": "在這個顯示器上使用自訂設定", @@ -916,6 +958,8 @@ }, "connections": { "authentication-required": "需要驗證", + "bluetooth-auto-connect-description": "當 Bluetooth 啟用時,自動連接到受信任的已配對裝置。", + "bluetooth-auto-connect-label": "自動連接已配對裝置", "bluetooth-devices-unnamed": "未命名裝置不顯示。", "bluetooth-discoverable": "當此設定分頁開啟時,此裝置可被發現為 {hostName}。", "bluetooth-rssi-polling-description": "透過 bluetoothctl 定期取樣已連接裝置的 RSSI。可能並非所有裝置都可用;啟用時佔用最少資源。", @@ -932,6 +976,8 @@ "control-center": { "cards-desc": "自訂控制中心出現的控制項目及排序", "desc": "設定控制中心面板的位置和行為", + "open-at-mouse-description": "當右鍵點擊欄的空白區域時,在游標附近開啟控制中心。若已停用,則改用上方的位置設定。", + "open-at-mouse-label": "右鍵點擊時在游標處開啟", "position-description": "選擇當開啟控制中心時該從哪裡出現", "shortcuts-custom-button-command-description": "在按鈕按下時要執行的命令", "shortcuts-custom-button-enable-on-state-logic-description": "啟用第二個圖示與「熱區」狀態,並根據檢查命令判斷。", @@ -983,6 +1029,7 @@ "edit-mode-description": "啟用編輯模式以移動小工具; 當啟用時, 小工具會顯示一個可拖曳的外框以供移動.", "edit-mode-exit-button": "離開編輯模式", "edit-mode-grid-snap-label": "貼齊格線", + "edit-mode-grid-snap-scale-label": "格線吸附比例", "edit-mode-label": "編輯模式", "enabled-description": "啟用或完全停用桌面小工具", "enabled-label": "啟用桌面小工具", @@ -1064,10 +1111,10 @@ "appearance-display-auto-hide": "自動隱藏", "appearance-display-description": "選擇 Dock 的行為", "appearance-display-exclusive": "獨占空間", + "appearance-dock-indicator-description": "當 Dock 隱藏時,顯示一個小指示器。", + "appearance-dock-indicator-label": "Dock 指示器", "appearance-floating-distance-description": "設定 Dock 與螢幕邊緣相隔的距離", "appearance-floating-distance-label": "Dock 懸浮間距", - "appearance-frame-indicator-description": "當 Dock 隱藏時,在邊框上顯示一個小指示器。", - "appearance-frame-indicator-label": "帶邊框的停靠欄指示器", "appearance-group-apps-description": "將同一應用程式的多個視窗分組到一個 Dock 項目中。", "appearance-group-apps-label": "分組相同應用程式", "appearance-group-click-action-cycle": "切換視窗", @@ -1088,6 +1135,12 @@ "appearance-icon-size-label": "Dock 大小", "appearance-inactive-indicators-description": "為所有應用程式顯示指示膠囊(而不僅是正在運作的程式)。", "appearance-inactive-indicators-label": "運作指示器", + "appearance-indicator-color-description": "選擇隱藏 Dock 指示器的顏色。", + "appearance-indicator-color-label": "指示器顏色", + "appearance-indicator-opacity-description": "調整隱藏 Dock 指示器的不透明度。", + "appearance-indicator-opacity-label": "指示器不透明度", + "appearance-indicator-thickness-description": "啟用較粗的隱藏 Dock 指示器(6px 而非 3px)。", + "appearance-indicator-thickness-label": "較粗的指示器", "appearance-launcher-position-description": "選擇啟動器圖示在停靠欄中的顯示位置。", "appearance-launcher-position-end": "結束", "appearance-launcher-position-label": "啟動器位置", @@ -1169,7 +1222,7 @@ }, "hooks": { "info-command-info-description": "• 指令將透過 shell (sh -lc) 執行
• 指令會在背景 (detached 狀態) 執行
• 測試按鈕會以當下填寫的內容執行", - "info-parameters-description": "• 桌布的 hook: $1 = 桌布路徑, $2 = 螢幕名稱
• 主題切換的 hook: $1 = true/false (深色模式啟用或否)
• 畫面鎖定/解鎖的 hook: 沒有參數
• 效能模式的 hook: 沒有參數
• 工作階段的 hook: $1 = 執行動作 (shutdown/reboot)", + "info-parameters-description": "• 桌布掛鉤: $1 = 桌布路徑, $2 = 螢幕名稱
• 主題切換掛鉤: $1 = true/false (Dark Mode狀態)
• 螢幕鎖定/解鎖掛鉤: $1 = lock/unlock (螢幕鎖定狀態)
• 效能模式掛鉤: 無參數
• 會話掛鉤: $1 = action (shutdown/reboot)", "info-parameters-label": "可用參數", "noctalia-started-description": "Noctalia 完成載入時要執行的指令。", "noctalia-started-label": "Noctalia 已啟動", @@ -1206,7 +1259,14 @@ "custom-description": "在一段時間不活動後執行 shell 命令。", "custom-entry-command": "命令", "custom-entry-delete": "刪除", + "custom-entry-edit": "編輯自訂命令", + "custom-entry-name": "名稱", + "custom-entry-name-placeholder": "例如:關燈", + "custom-entry-new": "新增自訂命令", "custom-entry-timeout": "閒置時間", + "custom-entry-timeout-format": "{count} 秒", + "custom-entry-timeout-format-plural": "{count} 秒", + "custom-entry-unnamed": "未命名命令", "custom-label": "自訂閒置指令", "enable-description": "在一段時間不活動後,自動關閉螢幕、鎖定或暫停。", "enable-label": "啟用閒置管理", @@ -1214,15 +1274,17 @@ "fade-duration-label": "淡入淡出時長", "lock-description": "鎖定畫面啟用前的閒置秒數。", "lock-label": "鎖定螢幕", + "resume-command-label": "恢復命令", "screen-off-description": "顯示器關閉前的不活動秒數。", "screen-off-label": "關閉螢幕", - "status-description": "Compositor回報的閒置時間。", + "status-description": "由混成器回報的閒置時間。", "status-label": "閒置時間", "suspend-description": "系統暫停前的閒置秒數。", "tab-behavior": "行為", "tab-custom": "自訂", - "timeouts-description": "設定為0以停用一個階段。Keep Awake啟用時,逾時會暫停。", + "timeouts-description": "設定為0以停用一個階段。保持喚醒啟用時,逾時會暫停。", "timeouts-label": "逾時", + "title": "閒置", "unavailable": "此 Compositor 不支援原生閒置監控。" }, "indicator": { @@ -1274,7 +1336,7 @@ "settings-show-icon-background-description": "在圖示後面顯示一個圓角矩形為背景", "settings-show-icon-background-label": "顯示圖示背景", "settings-sort-by-usage-description": "當啟用時, 最常使用的程式會出現在列表的上面", - "settings-sort-by-usage-label": "依最常使用排序", + "settings-sort-by-usage-label": "按最常使用排序", "settings-terminal-command-description": "打開終端機的指令, 例如: 'kitty -e'或 'gnome-terminal --'", "settings-terminal-command-label": "終端機指令", "settings-use-app2unit-description": "使用替代的啟動方式,以更好地管理應用程式程序並避免問題。", @@ -1334,6 +1396,8 @@ "clock-style-label": "時鐘樣式", "compact-lockscreen-description": "只顯示登入欄位及電源控制, 隱藏天氣及媒體小工具", "compact-lockscreen-label": "精簡鎖定畫面", + "enable-lockscreen-media-controls-description": "在鎖定畫面顯示互動式媒體播放控制項。", + "enable-lockscreen-media-controls-label": "鎖定畫面媒體控制項", "lock-on-suspend-description": "在暫停系統時自動鎖定畫面", "lock-on-suspend-label": "暫停時鎖定", "lock-screen-animations-description": "啟用或停用鎖定畫面動畫。", @@ -1557,6 +1621,13 @@ "show-keybinds-description": "在工作階段選項上顯示快捷鍵提示。", "show-keybinds-label": "顯示快捷鍵" }, + "system": { + "noctalia-performance-disable-desktop-widgets-description": "當 Noctalia 效能模式啟用時保持桌面小工具可見。", + "noctalia-performance-disable-desktop-widgets-label": "在效能模式下啟用桌面小工具", + "noctalia-performance-disable-wallpaper-description": "在啟用 Noctalia 效能模式時,保持桌面、概覽和鎖定畫面桌布可見。", + "noctalia-performance-disable-wallpaper-label": "在效能模式下啟用桌布渲染", + "title": "系統" + }, "system-monitor": { "critical-color-label": "危急顏色", "custom-highlight-colors-title-label": "自訂突出色", @@ -1573,8 +1644,8 @@ "polling-interval-label": "輪詢間隔", "polling-section-description": "設定該多久更新一次系統計量值", "polling-section-label": "輪詢間隔", - "threshold-critical": "危急門檻", - "threshold-warning": "警告門檻", + "threshold-critical": "嚴重", + "threshold-warning": "警告", "thresholds-section-description": "調整每個系統量測值的警告/危急門檻", "use-custom-highlight-colors-description": "當停用時, 將會使用主題預設的突出色", "use-custom-highlight-colors-label": "使用自訂的突出色", @@ -1589,6 +1660,8 @@ "animation-speed-label": "動畫速度", "animation-speed-reset": "重設動畫速度", "appearance-desc": "自訂視覺元素, 如提示框, 邊框及陰影", + "blur-behind-description": "使用合成器模糊協定,模糊欄位和面板後方的區域。", + "blur-behind-label": "背景模糊", "box-border-description": "在內容區塊的外圍顯示外框", "box-border-label": "頁面容器外框", "box-border-radius-description": "調整主要版面區塊(例如側邊欄、卡片與內容面板)的圓角弧度。", @@ -1608,6 +1681,8 @@ "panels-overlay-label": "維持工具列及面板在最上層", "scaling-description": "調整除了工具列以外的整體介面大小", "scaling-label": "介面比例", + "scrollbar-always-visible-description": "當內容可捲動時,保持捲軸始終可見,而不是僅在懸停時顯示。", + "scrollbar-always-visible-label": "永遠顯示捲軸", "settings-panel-header": "設定面板", "settings-panel-mode-description": "選擇介面設定出現所用的佈局 (可能需要重新打開)", "settings-panel-mode-label": "設定面板顯示", @@ -1619,7 +1694,9 @@ "shadows-label": "陰影", "title": "使用者介面", "tooltips-description": "在整個介面啟用或停用提示框", - "tooltips-label": "顯示提示框" + "tooltips-label": "顯示提示框", + "translucent-widgets-description": "使面板內的按鈕、分頁和其他小部件半透明。", + "translucent-widgets-label": "半透明小工具" }, "wallpaper": { "automation-change-mode-alphabetical": "照字母排序", @@ -1725,7 +1802,7 @@ }, "skip-setup": "跳過設定", "telemetry-wizard-done": "了解!", - "telemetry-wizard-note": "由你作主 — 你隨時可以在 \"設定\" 中啟用或停用此設定", + "telemetry-wizard-note": "盡在掌控——您隨時可在設定中啟用或停用此功能", "telemetry-wizard-subtitle": "我們新增了匿名分析功能以協助改善 Noctalia", "telemetry-wizard-title": "隱私權更新", "wallpaper": { @@ -1742,6 +1819,7 @@ "select-prompt": "在下面選擇一個桌布", "subheader": "用一個漂亮的背景營造氛圍" }, + "welcome": "歡迎", "welcome-note": "幾個基本的設定讓你簡單上手 — 所有的選項可以在設定裡調整", "welcome-subtitle": "讓你的桌面獨一無二", "welcome-title": "歡迎來到 Noctalia!" @@ -1761,6 +1839,7 @@ "widget-settings-title": "{widget} 設定" }, "system-monitor": { + "core-usage": "核心 {id} 使用率", "cpu-temp": "CPU 溫度", "cpu-usage": "CPU 使用率", "disk": "磁碟已用", @@ -1782,6 +1861,9 @@ }, "bluetooth": { "address-copied": "已複製位址至剪貼簿", + "auto-connect-disabled": "自動連線已停用", + "auto-connect-enabled": "自動連線已啟用", + "auto-connecting": "正在連接 {count} 個裝置...", "confirm-code": "在另一個裝置上確認配對碼: {value}", "connect-failed": "裝置連接失敗", "disconnect-failed": "裝置中斷連接失敗", @@ -1800,6 +1882,10 @@ "unavailable": "無法使用剪貼簿歷史", "unavailable-desc": "尚未安裝 cliphist 套件, 要使用剪貼簿歷史請安裝套件" }, + "custom-command-failed": { + "description": "指令失敗: {command}\\n結束代碼: {code}", + "title": "自訂指令失敗" + }, "do-not-disturb": { "disabled": "勿擾模式已停用", "disabled-desc": "顯示所有通知", @@ -1854,6 +1940,9 @@ }, "tooltips": { "add-widget": "新增小工具", + "auto-connect": "切換此裝置的自動連線", + "bluetooth-auto-connect-off": "自動連線已停用", + "bluetooth-auto-connect-on": "自動連線已啟用", "bluetooth-devices": "藍牙裝置", "brightness-at": "亮度: {brightness}%", "click-to-start-recording": "螢幕錄影 (開始錄製)", @@ -1951,9 +2040,14 @@ "resolution-label": "解析度", "resolution-mode-label": "選項", "solid-color-tooltip": "實色背景", + "sort-date-asc": "按最早排序", + "sort-date-desc": "按最新排序", + "sort-name-asc": "按名稱排序 (A-Z)", + "sort-name-desc": "按名稱排序 (Z-A)", + "sort-random": "隨機排序", "sorting-date-added": "新增日期", "sorting-favorites": "最愛", - "sorting-label": "依... 排序", + "sorting-label": "按... 排序", "sorting-relevance": "相關度", "sorting-toplist": "Toplist", "sorting-views": "觀看數", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 42bd1b144..638b66fb0 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -12,6 +12,7 @@ "widgetSpacing": 6, "contentPadding": 2, "fontScale": 1, + "enableExclusionZoneInset": true, "backgroundOpacity": 0.93, "useSeparateOpacity": false, "floating": false, @@ -69,6 +70,15 @@ } ] }, + "mouseWheelAction": "none", + "reverseScroll": false, + "mouseWheelWrap": true, + "middleClickAction": "none", + "middleClickFollowMouse": false, + "middleClickCommand": "", + "rightClickAction": "controlCenter", + "rightClickFollowMouse": true, + "rightClickCommand": "", "screenOverrides": [] }, "general": { @@ -88,7 +98,9 @@ "lockOnSuspend": true, "showSessionButtonsOnLockScreen": true, "showHibernateOnLockScreen": false, + "enableLockScreenMediaControls": false, "enableShadows": true, + "enableBlurBehind": true, "shadowDirection": "bottom_right", "shadowOffsetX": 2, "shadowOffsetY": 3, @@ -120,7 +132,8 @@ "Right" ], "keyEnter": [ - "Return" + "Return", + "Enter" ], "keyEscape": [ "Esc" @@ -137,8 +150,10 @@ "fontDefaultScale": 1, "fontFixedScale": 1, "tooltipsEnabled": true, + "scrollbarAlwaysVisible": true, "boxBorderEnabled": false, "panelBackgroundOpacity": 0.93, + "translucentWidgets": false, "panelsAttachedToBar": true, "settingsPanelMode": "attached", "settingsPanelSideBarCardStyle": false @@ -323,6 +338,10 @@ "criticalColor": "", "externalMonitor": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor" }, + "noctaliaPerformance": { + "disableWallpaper": true, + "disableDesktopWidgets": true + }, "dock": { "enabled": true, "position": "bottom", @@ -347,7 +366,10 @@ "deadOpacity": 0.6, "animationSpeed": 1, "sitOnFrame": false, - "showFrameIndicator": true + "showDockIndicator": false, + "indicatorThickness": 3, + "indicatorColor": "primary", + "indicatorOpacity": 0.6 }, "network": { "wifiEnabled": true, @@ -358,7 +380,8 @@ "wifiDetailsViewMode": "grid", "bluetoothDetailsViewMode": "grid", "bluetoothHideUnnamedDevices": false, - "disableDiscoverability": false + "disableDiscoverability": false, + "bluetoothAutoConnect": true }, "sessionMenu": { "enableCountdown": true, @@ -453,7 +476,7 @@ "audio": { "volumeStep": 5, "volumeOverdrive": false, - "cavaFrameRate": 30, + "spectrumFrameRate": 30, "visualizerType": "linear", "mprisBlacklist": [], "preferredPlayer": "", @@ -509,12 +532,19 @@ "lockTimeout": 660, "suspendTimeout": 1800, "fadeDuration": 5, + "screenOffCommand": "", + "lockCommand": "", + "suspendCommand": "", + "resumeScreenOffCommand": "", + "resumeLockCommand": "", + "resumeSuspendCommand": "", "customCommands": "[]" }, "desktopWidgets": { "enabled": false, "overviewEnabled": true, "gridSnap": false, + "gridSnapScale": false, "monitorWidgets": [] } } \ No newline at end of file diff --git a/Assets/settings-search-index.json b/Assets/settings-search-index.json index 6bb740906..057cfcda9 100644 --- a/Assets/settings-search-index.json +++ b/Assets/settings-search-index.json @@ -1,4 +1,13 @@ [ + { + "labelKey": "panels.about.changelog-on-startup", + "descriptionKey": "panels.about.changelog-on-startup-desc", + "widget": "NToggle", + "tab": 21, + "tabLabel": "panels.about.title", + "subTab": 0, + "subTabLabel": "common.info" + }, { "labelKey": "panels.about.telemetry-enabled", "descriptionKey": "panels.about.telemetry-desc", @@ -233,6 +242,15 @@ "subTab": 0, "subTabLabel": "common.appearance" }, + { + "labelKey": "panels.bar.appearance-capsule-color-label", + "descriptionKey": "panels.bar.appearance-capsule-color-description", + "widget": "NColorChoice", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, { "labelKey": "panels.bar.appearance-capsule-opacity-label", "descriptionKey": "panels.bar.appearance-capsule-opacity-description", @@ -242,6 +260,15 @@ "subTab": 0, "subTabLabel": "common.appearance" }, + { + "labelKey": "panels.bar.appearance-enable-exclusion-zone-inset-label", + "descriptionKey": "panels.bar.appearance-enable-exclusion-zone-inset-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, { "labelKey": "panels.bar.appearance-hide-on-overview-label", "descriptionKey": "panels.bar.appearance-hide-on-overview-description", @@ -287,19 +314,10 @@ "subTab": 0, "subTabLabel": "common.appearance" }, - { - "labelKey": "panels.bar.appearance-margins-label", - "descriptionKey": "panels.bar.appearance-margins-description", - "widget": "NLabel", - "tab": 4, - "tabLabel": "panels.bar.title", - "subTab": 0, - "subTabLabel": "common.appearance" - }, { "labelKey": "panels.bar.appearance-margins-vertical", - "descriptionKey": null, - "widget": "NValueSlider", + "descriptionKey": "panels.bar.appearance-margins-description", + "widget": "NSpinBox", "tab": 4, "tabLabel": "panels.bar.title", "subTab": 0, @@ -307,8 +325,8 @@ }, { "labelKey": "panels.bar.appearance-margins-horizontal", - "descriptionKey": null, - "widget": "NValueSlider", + "descriptionKey": "panels.bar.appearance-margins-description", + "widget": "NSpinBox", "tab": 4, "tabLabel": "panels.bar.title", "subTab": 0, @@ -341,13 +359,94 @@ "subTab": 0, "subTabLabel": "common.appearance" }, + { + "labelKey": "panels.bar.behavior-workspace-scroll-label", + "descriptionKey": "panels.bar.behavior-workspace-scroll-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.general.reverse-scrolling-label", + "descriptionKey": "panels.general.reverse-scrolling-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.bar.behavior-wheel-wrap-label", + "descriptionKey": "panels.bar.behavior-wheel-wrap-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.bar.behavior-middle-click-label", + "descriptionKey": "panels.bar.behavior-middle-click-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.bar.behavior-middle-click-command-label", + "descriptionKey": "panels.bar.behavior-middle-click-command-description", + "widget": "NTextInput", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.bar.behavior-middle-click-follow-mouse-label", + "descriptionKey": "panels.bar.behavior-middle-click-follow-mouse-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.bar.behavior-right-click-label", + "descriptionKey": "panels.bar.behavior-right-click-description", + "widget": "NComboBox", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.bar.behavior-right-click-command-label", + "descriptionKey": "panels.bar.behavior-right-click-command-description", + "widget": "NTextInput", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, + { + "labelKey": "panels.bar.behavior-right-click-follow-mouse-label", + "descriptionKey": "panels.bar.behavior-right-click-follow-mouse-description", + "widget": "NToggle", + "tab": 4, + "tabLabel": "panels.bar.title", + "subTab": 2, + "subTabLabel": "common.behavior" + }, { "labelKey": "panels.bar.monitor-override-settings", "descriptionKey": "panels.bar.monitor-override-settings-description", "widget": "NToggle", "tab": 4, "tabLabel": "panels.bar.title", - "subTab": 2, + "subTab": 3, "subTabLabel": "common.monitors" }, { @@ -449,6 +548,15 @@ "subTab": 1, "subTabLabel": "common.bluetooth" }, + { + "labelKey": "panels.connections.bluetooth-auto-connect-label", + "descriptionKey": "panels.connections.bluetooth-auto-connect-description", + "widget": "NToggle", + "tab": 16, + "tabLabel": "panels.connections.title", + "subTab": 1, + "subTabLabel": "common.bluetooth" + }, { "labelKey": "panels.connections.hide-unnamed-devices-label", "descriptionKey": "panels.connections.hide-unnamed-devices-description", @@ -664,14 +772,41 @@ "subTabLabel": "common.appearance" }, { - "labelKey": "panels.dock.appearance-frame-indicator-label", - "descriptionKey": "panels.dock.appearance-frame-indicator-description", + "labelKey": "panels.dock.appearance-dock-indicator-label", + "descriptionKey": "panels.dock.appearance-dock-indicator-description", "widget": "NToggle", "tab": 5, "tabLabel": "panels.dock.title", "subTab": 0, "subTabLabel": "common.appearance" }, + { + "labelKey": "panels.dock.appearance-indicator-thickness-label", + "descriptionKey": "panels.dock.appearance-indicator-thickness-description", + "widget": "NToggle", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-indicator-color-label", + "descriptionKey": "panels.dock.appearance-indicator-color-description", + "widget": "NColorChoice", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.dock.appearance-indicator-opacity-label", + "descriptionKey": "panels.dock.appearance-indicator-opacity-description", + "widget": "NValueSlider", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, { "labelKey": "panels.osd.background-opacity-label", "descriptionKey": "panels.dock.appearance-background-opacity-description", @@ -807,6 +942,15 @@ "subTab": 0, "subTabLabel": "common.appearance" }, + { + "labelKey": "common.select-icon-color", + "descriptionKey": null, + "widget": "NColorChoice", + "tab": 5, + "tabLabel": "panels.dock.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, { "labelKey": "panels.general.profile-picture-label", "descriptionKey": "panels.general.profile-picture-description", @@ -847,14 +991,6 @@ "tabLabel": "common.general", "subTab": null }, - { - "labelKey": "panels.general.reverse-scrolling-label", - "descriptionKey": "panels.general.reverse-scrolling-description", - "widget": "NToggle", - "tab": 0, - "tabLabel": "common.general", - "subTab": null - }, { "labelKey": "panels.general.keybinds-title", "descriptionKey": "panels.general.keybinds-description", @@ -958,7 +1094,7 @@ "descriptionKey": "panels.idle.enable-description", "widget": "NToggle", "tab": 13, - "tabLabel": "common.idle", + "tabLabel": "panels.idle.title", "subTab": 0, "subTabLabel": "panels.idle.tab-behavior" }, @@ -967,7 +1103,7 @@ "descriptionKey": "panels.idle.status-description", "widget": "NLabel", "tab": 13, - "tabLabel": "common.idle", + "tabLabel": "panels.idle.title", "subTab": 0, "subTabLabel": "panels.idle.tab-behavior" }, @@ -976,34 +1112,7 @@ "descriptionKey": "panels.idle.timeouts-description", "widget": "NLabel", "tab": 13, - "tabLabel": "common.idle", - "subTab": 0, - "subTabLabel": "panels.idle.tab-behavior" - }, - { - "labelKey": "panels.idle.screen-off-label", - "descriptionKey": "panels.idle.screen-off-description", - "widget": "NSpinBox", - "tab": 13, - "tabLabel": "common.idle", - "subTab": 0, - "subTabLabel": "panels.idle.tab-behavior" - }, - { - "labelKey": "panels.idle.lock-label", - "descriptionKey": "panels.idle.lock-description", - "widget": "NSpinBox", - "tab": 13, - "tabLabel": "common.idle", - "subTab": 0, - "subTabLabel": "panels.idle.tab-behavior" - }, - { - "labelKey": "common.suspend", - "descriptionKey": "panels.idle.suspend-description", - "widget": "NSpinBox", - "tab": 13, - "tabLabel": "common.idle", + "tabLabel": "panels.idle.title", "subTab": 0, "subTabLabel": "panels.idle.tab-behavior" }, @@ -1012,7 +1121,7 @@ "descriptionKey": "panels.idle.fade-duration-description", "widget": "NSpinBox", "tab": 13, - "tabLabel": "common.idle", + "tabLabel": "panels.idle.title", "subTab": 0, "subTabLabel": "panels.idle.tab-behavior" }, @@ -1021,25 +1130,7 @@ "descriptionKey": "panels.idle.custom-description", "widget": "NLabel", "tab": 13, - "tabLabel": "common.idle", - "subTab": 1, - "subTabLabel": "panels.idle.tab-custom" - }, - { - "labelKey": "panels.idle.custom-entry-timeout", - "descriptionKey": null, - "widget": "NSpinBox", - "tab": 13, - "tabLabel": "common.idle", - "subTab": 1, - "subTabLabel": "panels.idle.tab-custom" - }, - { - "labelKey": "panels.idle.custom-entry-command", - "descriptionKey": null, - "widget": "NTextInput", - "tab": 13, - "tabLabel": "common.idle", + "tabLabel": "panels.idle.title", "subTab": 1, "subTabLabel": "panels.idle.tab-custom" }, @@ -1277,6 +1368,15 @@ "subTab": 0, "subTabLabel": "common.appearance" }, + { + "labelKey": "panels.lock-screen.enable-lockscreen-media-controls-label", + "descriptionKey": "panels.lock-screen.enable-lockscreen-media-controls-description", + "widget": "NToggle", + "tab": 11, + "tabLabel": "panels.lock-screen.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, { "labelKey": "panels.lock-screen.lock-screen-animations-label", "descriptionKey": "panels.lock-screen.lock-screen-animations-description", @@ -1804,27 +1904,45 @@ "descriptionKey": "panels.system-monitor.enable-dgpu-monitoring-description", "widget": "NToggle", "tab": 18, - "tabLabel": "system-monitor.title", + "tabLabel": "panels.system.title", "subTab": 0, - "subTabLabel": "common.general" + "subTabLabel": "system-monitor.title" }, { "labelKey": "panels.system-monitor.use-custom-highlight-colors-label", "descriptionKey": "panels.system-monitor.use-custom-highlight-colors-description", "widget": "NToggle", "tab": 18, - "tabLabel": "system-monitor.title", + "tabLabel": "panels.system.title", "subTab": 0, - "subTabLabel": "common.general" + "subTabLabel": "system-monitor.title" }, { "labelKey": "panels.system-monitor.external-monitor-label", "descriptionKey": "panels.system-monitor.external-monitor-description", "widget": "NTextInput", "tab": 18, - "tabLabel": "system-monitor.title", + "tabLabel": "panels.system.title", "subTab": 0, - "subTabLabel": "common.general" + "subTabLabel": "system-monitor.title" + }, + { + "labelKey": "panels.system.noctalia-performance-disable-wallpaper-label", + "descriptionKey": "panels.system.noctalia-performance-disable-wallpaper-description", + "widget": "NToggle", + "tab": 18, + "tabLabel": "panels.system.title", + "subTab": 2, + "subTabLabel": "common.performance" + }, + { + "labelKey": "panels.system.noctalia-performance-disable-desktop-widgets-label", + "descriptionKey": "panels.system.noctalia-performance-disable-desktop-widgets-description", + "widget": "NToggle", + "tab": 18, + "tabLabel": "panels.system.title", + "subTab": 2, + "subTabLabel": "common.performance" }, { "labelKey": "panels.user-interface.tooltips-label", @@ -1844,6 +1962,15 @@ "subTab": 0, "subTabLabel": "common.appearance" }, + { + "labelKey": "panels.user-interface.scrollbar-always-visible-label", + "descriptionKey": "panels.user-interface.scrollbar-always-visible-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, { "labelKey": "panels.user-interface.shadows-label", "descriptionKey": "panels.user-interface.shadows-description", @@ -1853,6 +1980,24 @@ "subTab": 0, "subTabLabel": "common.appearance" }, + { + "labelKey": "panels.user-interface.blur-behind-label", + "descriptionKey": "panels.user-interface.blur-behind-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, + { + "labelKey": "panels.user-interface.translucent-widgets-label", + "descriptionKey": "panels.user-interface.translucent-widgets-description", + "widget": "NToggle", + "tab": 1, + "tabLabel": "panels.user-interface.title", + "subTab": 0, + "subTabLabel": "common.appearance" + }, { "labelKey": "panels.user-interface.shadows-direction-label", "descriptionKey": "panels.user-interface.shadows-direction-description", diff --git a/Assets/settings-widgets-default.json b/Assets/settings-widgets-default.json index 744c937e5..61a7239e6 100644 --- a/Assets/settings-widgets-default.json +++ b/Assets/settings-widgets-default.json @@ -125,10 +125,7 @@ "visualizerType": "linear", "textColor": "none", "compactMode": false, - "panelShowAlbumArt": true, - "panelShowVisualizer": true, - "compactShowAlbumArt": true, - "compactShowVisualizer": false + "panelShowAlbumArt": true }, "Microphone": { "displayMode": "onhover", @@ -159,6 +156,7 @@ "useMonospaceFont": true, "usePadding": false, "showCpuUsage": true, + "showCpuCores": false, "showCpuFreq": false, "showCpuTemp": true, "showGpuTemp": false, @@ -227,7 +225,8 @@ "occupiedColor": "secondary", "emptyColor": "secondary", "showBadge": true, - "pillSize": 0.6 + "pillSize": 0.6, + "fontWeight": "bold" }, "Volume": { "displayMode": "onhover", @@ -254,29 +253,41 @@ "desktop": { "Clock": { "showBackground": true, + "roundedCorners": true, "clockStyle": "digital", "clockColor": "none", "useCustomFont": false, + "customFont": "", "format": "HH:mm\\nd MMMM yyyy" }, "MediaPlayer": { "showBackground": true, + "roundedCorners": true, "visualizerType": "linear", "hideMode": "visible", "showButtons": true, "showAlbumArt": true, - "showVisualizer": true, - "roundedCorners": true + "showVisualizer": true }, "Weather": { - "showBackground": true + "showBackground": true, + "roundedCorners": true }, "SystemStat": { "showBackground": true, + "roundedCorners": true, "statType": "CPU", "diskPath": "/", - "roundedCorners": true, "layout": "bottom" + }, + "AudioVisualizer": { + "showBackground": true, + "roundedCorners": true, + "width": 320, + "height": 72, + "visualizerType": "linear", + "hideWhenIdle": false, + "colorName": "primary" } } } \ No newline at end of file diff --git a/Commons/Color.qml b/Commons/Color.qml index a70cb302b..19478e88a 100644 --- a/Commons/Color.qml +++ b/Commons/Color.qml @@ -324,6 +324,23 @@ Singleton { } } + // Smart alpha calculation: automatically makes light mode more transparent + function smartAlpha(baseColor, minAlpha = 0.4) { + if (!Settings.data.ui.translucentWidgets) + return baseColor; + + let alpha = Math.max(root.panelBackgroundOpacity, minAlpha); + + // Combine with the base color's existing alpha + let resultAlpha = Math.max(0, baseColor.a - (1.0 - alpha)); + return Qt.alpha(baseColor, resultAlpha); + } + + readonly property real panelBackgroundOpacity: { + let baseOpacity = Settings.data.ui.panelBackgroundOpacity; + return Settings.data.colorSchemes.darkMode ? baseOpacity : Math.pow(baseOpacity, 2); + } + readonly property var colorKeyModel: [ { "key": "none", diff --git a/Commons/Migrations/Migration54.qml b/Commons/Migrations/Migration54.qml new file mode 100644 index 000000000..367ab122b --- /dev/null +++ b/Commons/Migrations/Migration54.qml @@ -0,0 +1,28 @@ +import QtQuick + +QtObject { + id: root + + // Add numpad Enter as a second default keybind for keyEnter + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v54"); + + const keybinds = rawJson?.general?.keybinds; + if (!keybinds) + return true; + + const keyEnter = keybinds.keyEnter; + if (!keyEnter || !Array.isArray(keyEnter)) + return true; + + // Only add "Enter" if the first entry is "Return" and "Enter" isn't already present + if (keyEnter[0] === "Return" && !keyEnter.includes("Enter")) { + var updated = Array.from(keyEnter); + updated.splice(1, 0, "Enter"); + adapter.general.keybinds.keyEnter = updated; + logger.i("Settings", "Added 'Enter' (numpad) as second default keybind for keyEnter"); + } + + return true; + } +} diff --git a/Commons/Migrations/Migration55.qml b/Commons/Migrations/Migration55.qml new file mode 100644 index 000000000..0e0827692 --- /dev/null +++ b/Commons/Migrations/Migration55.qml @@ -0,0 +1,22 @@ +import QtQuick + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v55"); + + // Check if the old setting exists + if (rawJson.controlCenter && rawJson.controlCenter.openAtMouseOnBarRightClick !== undefined) { + if (!rawJson.bar) + rawJson.bar = {}; + + rawJson.bar.rightClickFollowMouse = rawJson.controlCenter.openAtMouseOnBarRightClick; + delete rawJson.controlCenter.openAtMouseOnBarRightClick; + + logger.i("Settings", "Successfully moved openAtMouseOnBarRightClick to bar.rightClickFollowMouse"); + } + + return true; + } +} diff --git a/Commons/Migrations/Migration56.qml b/Commons/Migrations/Migration56.qml new file mode 100644 index 000000000..1455d558e --- /dev/null +++ b/Commons/Migrations/Migration56.qml @@ -0,0 +1,20 @@ +import QtQuick +import Quickshell + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v56 (Color Scheme Migration)"); + + const scriptPath = Quickshell.shellDir + "/Scripts/python/src/theming/migrate-colorschemes.py"; + const configDir = Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/noctalia"; + + logger.i("Settings", `Running color scheme migration script: ${scriptPath} with configDir: ${configDir}`); + + // Run the migration script detached + Quickshell.execDetached(["python3", scriptPath, configDir]); + + return true; + } +} diff --git a/Commons/Migrations/Migration57.qml b/Commons/Migrations/Migration57.qml new file mode 100644 index 000000000..a8e3444bd --- /dev/null +++ b/Commons/Migrations/Migration57.qml @@ -0,0 +1,16 @@ +import QtQuick + +QtObject { + id: root + + function migrate(adapter, logger, rawJson) { + logger.i("Settings", "Migrating settings to v57 (cavaFrameRate -> spectrumFrameRate)"); + + if (rawJson && rawJson.audio && rawJson.audio.cavaFrameRate !== undefined) { + adapter.audio.spectrumFrameRate = rawJson.audio.cavaFrameRate; + logger.i("Settings", "Migrated cavaFrameRate:", rawJson.audio.cavaFrameRate, "-> spectrumFrameRate"); + } + + return true; + } +} diff --git a/Commons/Migrations/MigrationRegistry.qml b/Commons/Migrations/MigrationRegistry.qml index 459760148..51e546cef 100644 --- a/Commons/Migrations/MigrationRegistry.qml +++ b/Commons/Migrations/MigrationRegistry.qml @@ -25,7 +25,11 @@ QtObject { 48: migration48Component, 49: migration49Component, 50: migration50Component, - 53: migration53Component + 53: migration53Component, + 54: migration54Component, + 55: migration55Component, + 56: migration56Component, + 57: migration57Component }) // Migration components @@ -48,4 +52,8 @@ QtObject { property Component migration49Component: Migration49 {} property Component migration50Component: Migration50 {} property Component migration53Component: Migration53 {} + property Component migration54Component: Migration54 {} + property Component migration55Component: Migration55 {} + property Component migration56Component: Migration56 {} + property Component migration57Component: Migration57 {} } diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 69eacc6de..d6032e976 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -25,7 +25,7 @@ Singleton { - Default cache directory: ~/.cache/noctalia */ readonly property alias data: adapter // Used to access via Settings.data.xxx.yyy - readonly property int settingsVersion: 53 + readonly property int settingsVersion: 57 property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1" readonly property string shellName: "noctalia" readonly property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/" @@ -184,6 +184,7 @@ Singleton { property int widgetSpacing: 6 property int contentPadding: 2 property real fontScale: 1.0 + property bool enableExclusionZoneInset: true // Bar background opacity settings property real backgroundOpacity: 0.93 @@ -256,7 +257,15 @@ Singleton { } ] } - + property string mouseWheelAction: "none" + property bool reverseScroll: false + property bool mouseWheelWrap: true + property string middleClickAction: "none" + property bool middleClickFollowMouse: false + property string middleClickCommand: "" + property string rightClickAction: "controlCenter" + property bool rightClickFollowMouse: true + property string rightClickCommand: "" // Per-screen overrides for position and widgets // Format: [{ "name": "HDMI-1", "position": "left" }, { "name": "DP-1", "position": "bottom", "widgets": {...} }] property list screenOverrides: [] @@ -280,7 +289,9 @@ Singleton { property bool lockOnSuspend: true property bool showSessionButtonsOnLockScreen: true property bool showHibernateOnLockScreen: false + property bool enableLockScreenMediaControls: false property bool enableShadows: true + property bool enableBlurBehind: true property string shadowDirection: "bottom_right" property int shadowOffsetX: 2 property int shadowOffsetY: 3 @@ -303,7 +314,7 @@ Singleton { property list keyDown: ["Down"] property list keyLeft: ["Left"] property list keyRight: ["Right"] - property list keyEnter: ["Return"] + property list keyEnter: ["Return", "Enter"] property list keyEscape: ["Esc"] property list keyRemove: ["Del"] } @@ -317,8 +328,10 @@ Singleton { property real fontDefaultScale: 1.0 property real fontFixedScale: 1.0 property bool tooltipsEnabled: true + property bool scrollbarAlwaysVisible: true property bool boxBorderEnabled: false property real panelBackgroundOpacity: 0.93 + property bool translucentWidgets: false property bool panelsAttachedToBar: true property string settingsPanelMode: "attached" // "centered", "attached", "window" property bool settingsPanelSideBarCardStyle: false @@ -523,6 +536,12 @@ Singleton { property string externalMonitor: "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor" } + // performance + property JsonObject noctaliaPerformance: JsonObject { + property bool disableWallpaper: true + property bool disableDesktopWidgets: true + } + // dock property JsonObject dock: JsonObject { property bool enabled: true @@ -548,7 +567,10 @@ Singleton { property double deadOpacity: 0.6 property real animationSpeed: 1.0 // Speed multiplier for hide/show animations (0.1 = slowest, 2.0 = fastest) property bool sitOnFrame: false - property bool showFrameIndicator: true + property bool showDockIndicator: false + property int indicatorThickness: 3 + property string indicatorColor: "primary" + property real indicatorOpacity: 0.6 } // network @@ -562,6 +584,7 @@ Singleton { property string bluetoothDetailsViewMode: "grid" // "grid" or "list" property bool bluetoothHideUnnamedDevices: false property bool disableDiscoverability: false + property bool bluetoothAutoConnect: true } // session menu @@ -660,7 +683,7 @@ Singleton { property JsonObject audio: JsonObject { property int volumeStep: 5 property bool volumeOverdrive: false - property int cavaFrameRate: 30 + property int spectrumFrameRate: 30 property string visualizerType: "linear" property list mprisBlacklist: [] property string preferredPlayer: "" @@ -731,7 +754,13 @@ Singleton { property int lockTimeout: 660 // seconds, 0 = disabled property int suspendTimeout: 1800 // seconds, 0 = disabled property int fadeDuration: 5 // seconds of fade-to-black before action fires - property string customCommands: "[]" // JSON array of {timeout, command} + property string screenOffCommand: "" + property string lockCommand: "" + property string suspendCommand: "" + property string resumeScreenOffCommand: "" + property string resumeLockCommand: "" + property string resumeSuspendCommand: "" + property string customCommands: "[]" // JSON array of {timeout, command, resumeCommand} } // desktop widgets @@ -739,6 +768,7 @@ Singleton { property bool enabled: false property bool overviewEnabled: true property bool gridSnap: false + property bool gridSnapScale: false property list monitorWidgets: [] // Format: [{ "name": "DP-1", "widgets": [...] }, { "name": "HDMI-1", "widgets": [...] }] } diff --git a/Commons/ShellState.qml b/Commons/ShellState.qml index fd8f72706..f4d1c0c1f 100644 --- a/Commons/ShellState.qml +++ b/Commons/ShellState.qml @@ -109,6 +109,17 @@ Singleton { save(); } + // Migrate usage from one key to another, merging counts in a single save + function migrateLauncherUsage(fromKey, toKey) { + let counts = Object.assign({}, adapter.launcherUsage || {}); + const fromCount = typeof counts[fromKey] === 'number' && isFinite(counts[fromKey]) ? counts[fromKey] : 0; + const toCount = typeof counts[toKey] === 'number' && isFinite(counts[toKey]) ? counts[toKey] : 0; + counts[toKey] = toCount + fromCount; + delete counts[fromKey]; + adapter.launcherUsage = counts; + save(); + } + // Debounced save timer Timer { id: saveTimer diff --git a/Helpers/AdvancedMath.js b/Helpers/AdvancedMath.js index bde5d2002..d7bc36708 100644 --- a/Helpers/AdvancedMath.js +++ b/Helpers/AdvancedMath.js @@ -26,8 +26,18 @@ var constants = { // Safe evaluation function that handles advanced math function evaluate(expression) { try { - // Replace mathematical constants - var processed = expression + // Fixes decimal arithmetic + var cleanExpr = expression.replace(/\s+/g, '').toLowerCase(); + + // Allows numbers (including decimals), basic operators, and explicitly permitted math terms only + var safeRegex = /^(\d*\.?\d+|[+\-*/()^%,]|sin|cos|tan|asin|acos|atan|atan2|sinh|cosh|tanh|asinh|acosh|atanh|log|ln|exp|pow|sqrt|cbrt|abs|floor|ceil|round|trunc|min|max|random|pi|e|sind|cosd|tand)+$/; + + if (!safeRegex.test(cleanExpr)) { + throw new Error("Invalid characters or unauthorized functions in expression"); + } + + // Replace mathematical constants (Original Structure) + var processed = cleanExpr .replace(/\bpi\b/gi, Math.PI) .replace(/\be\b/gi, Math.E); @@ -41,7 +51,7 @@ function evaluate(expression) { .replace(/\bacos\s*\(/g, 'Math.acos(') .replace(/\batan\s*\(/g, 'Math.atan(') .replace(/\batan2\s*\(/g, 'Math.atan2(') - + // Hyperbolic functions .replace(/\bsinh\s*\(/g, 'Math.sinh(') .replace(/\bcosh\s*\(/g, 'Math.cosh(') @@ -49,28 +59,28 @@ function evaluate(expression) { .replace(/\basinh\s*\(/g, 'Math.asinh(') .replace(/\bacosh\s*\(/g, 'Math.acosh(') .replace(/\batanh\s*\(/g, 'Math.atanh(') - + // Logarithmic and exponential functions .replace(/\blog\s*\(/g, 'Math.log10(') .replace(/\bln\s*\(/g, 'Math.log(') .replace(/\bexp\s*\(/g, 'Math.exp(') .replace(/\bpow\s*\(/g, 'Math.pow(') - + // Root functions .replace(/\bsqrt\s*\(/g, 'Math.sqrt(') .replace(/\bcbrt\s*\(/g, 'Math.cbrt(') - + // Rounding and absolute .replace(/\babs\s*\(/g, 'Math.abs(') .replace(/\bfloor\s*\(/g, 'Math.floor(') .replace(/\bceil\s*\(/g, 'Math.ceil(') .replace(/\bround\s*\(/g, 'Math.round(') .replace(/\btrunc\s*\(/g, 'Math.trunc(') - + // Min/Max .replace(/\bmin\s*\(/g, 'Math.min(') .replace(/\bmax\s*\(/g, 'Math.max(') - + // Random .replace(/\brandom\s*\(\s*\)/g, 'Math.random()'); @@ -83,14 +93,10 @@ function evaluate(expression) { // Handle ^ for exponentiation: convert 2^3 to Math.pow(2,3) processed = processed.replace(/([\d.]+|\))\^([\d.]+|\([^)]*\))/g, 'Math.pow($1,$2)'); - // Sanitize expression (only allow safe characters) - if (!/^[0-9+\-*/().\s\w,]+$/.test(processed)) { - throw new Error("Invalid characters in expression"); - } + // Replacing eval() with a scoped function constructor + // This is safe because the strict whitelist guarantees only math reaches this point + var result = new Function('return ' + processed)(); - // Evaluate the processed expression - var result = eval(processed); - if (!isFinite(result) || isNaN(result)) { throw new Error("Invalid result"); } @@ -106,12 +112,12 @@ function formatResult(result) { if (Number.isInteger(result)) { return result.toString(); } - + // Handle very large or very small numbers if (Math.abs(result) >= 1e15 || (Math.abs(result) < 1e-6 && result !== 0)) { return result.toExponential(6); } - + // Normal decimal formatting return parseFloat(result.toFixed(10)).toString(); } @@ -120,35 +126,35 @@ function formatResult(result) { function getAvailableFunctions() { return [ // Basic arithmetic: +, -, *, /, %, ^, () - + // Trigonometric functions "sin(x), cos(x), tan(x) - trigonometric functions (radians)", "sind(x), cosd(x), tand(x) - trigonometric functions (degrees)", "asin(x), acos(x), atan(x) - inverse trigonometric", "atan2(y, x) - two-argument arctangent", - + // Hyperbolic functions "sinh(x), cosh(x), tanh(x) - hyperbolic functions", "asinh(x), acosh(x), atanh(x) - inverse hyperbolic", - + // Logarithmic and exponential "log(x) - base 10 logarithm", "ln(x) - natural logarithm", "exp(x) - e^x", "pow(x, y) - x^y", - + // Root functions "sqrt(x) - square root", "cbrt(x) - cube root", - + // Rounding and absolute "abs(x) - absolute value", "floor(x), ceil(x), round(x), trunc(x)", - + // Min/Max/Random "min(a, b, ...), max(a, b, ...)", "random() - random number 0-1", - + // Constants "pi, e - mathematical constants" ]; diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 999b4265e..ea74e5b70 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -3,6 +3,7 @@ import Quickshell import Quickshell.Wayland import qs.Commons import qs.Services.Compositor +import qs.Services.Power import qs.Services.UI Variants { @@ -13,7 +14,7 @@ Variants { required property ShellScreen modelData - active: modelData && Settings.data.wallpaper.enabled + active: modelData && Settings.data.wallpaper.enabled && (!PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableWallpaper) sourceComponent: PanelWindow { id: root diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index 24090afb9..4ed59208f 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -8,7 +8,7 @@ import qs.Services.Power import qs.Services.UI Loader { - active: CompositorService.isNiri && Settings.data.wallpaper.enabled && Settings.data.wallpaper.overviewEnabled + active: CompositorService.isNiri && Settings.data.wallpaper.enabled && Settings.data.wallpaper.overviewEnabled && (!PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableWallpaper) sourceComponent: Variants { model: Quickshell.screens diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 964d195ae..142625890 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -7,6 +7,8 @@ import Quickshell.Wayland import qs.Commons import qs.Modules.Bar.Extras import qs.Modules.Notification +import qs.Modules.Panels.Settings +import qs.Services.Compositor import qs.Services.UI import qs.Widgets @@ -127,18 +129,29 @@ Item { target: BarService function onWidgetsRevisionChanged() { Logger.d("Bar", "onWidgetsRevisionChanged, revision:", BarService.widgetsRevision, "screen:", root.screen?.name); - var widgets = Settings.getBarWidgetsForScreen(root.screen?.name); - if (widgets) { - root.syncWidgetModel(root.leftWidgetsModel, widgets.left); - root.syncWidgetModel(root.centerWidgetsModel, widgets.center); - root.syncWidgetModel(root.rightWidgetsModel, widgets.right); - } + Qt.callLater(root._syncFromRevision); } } - // Initialize models + function _syncFromRevision() { + var widgets = Settings.getBarWidgetsForScreen(screen?.name); + if (widgets) { + syncWidgetModel(leftWidgetsModel, widgets.left); + syncWidgetModel(centerWidgetsModel, widgets.center); + syncWidgetModel(rightWidgetsModel, widgets.right); + } + } + + // Initialize models — deferred to next event-loop tick via Qt.callLater to avoid + // re-entrant incubation: Component.onCompleted fires during QQmlObjectCreator::finalize, + // and ListModel.append synchronously creates Repeater delegates whose own finalization + // can corrupt the V4 heap (SIGSEGV in QV4::Object::insertMember). Component.onCompleted: { Logger.d("Bar", "Bar Component.onCompleted for screen:", screen?.name); + Qt.callLater(root._initModels); + } + + function _initModels() { var widgets = Settings.getBarWidgetsForScreen(screen?.name); if (widgets) { syncWidgetModel(leftWidgetsModel, widgets.left); @@ -180,6 +193,14 @@ Item { Item { id: bar + // Wheel scroll handling (empty bar area) + property int barWheelAccumulatedDelta: 0 + property bool barWheelCooldown: false + readonly property string barWheelAction: { + return Settings.data.bar.mouseWheelAction || "none"; + } + readonly property string barRightClickAction: Settings.data.bar.rightClickAction || "controlCenter" + // Position and size the bar content based on orientation x: (root.barPosition === "right") ? (parent.width - root.barHeight) : 0 y: (root.barPosition === "bottom") ? (parent.height - root.barHeight) : 0 @@ -263,47 +284,190 @@ Item { return -1; } + function isPointOverWidget(xPos, yPos) { + var widgets = BarService.getAllWidgetInstances(null, screen.name); + for (var i = 0; i < widgets.length; i++) { + var widget = widgets[i]; + if (!widget || !widget.visible || widget.widgetId === "Spacer") { + continue; + } + var localPos = mapToItem(widget, xPos, yPos); + + if (root.barIsVertical) { + if (localPos.y >= -Style.marginS && localPos.y <= widget.height + Style.marginS) { + return true; + } + } else { + if (localPos.x >= -Style.marginS && localPos.x <= widget.width + Style.marginS) { + return true; + } + } + } + return false; + } + + function switchWorkspaceByOffset(offset) { + if (!root.screen || CompositorService.workspaces.count === 0) + return; + + var screenName = root.screen.name.toLowerCase(); + var candidates = []; + for (var i = 0; i < CompositorService.workspaces.count; i++) { + var ws = CompositorService.workspaces.get(i); + var matchesScreen = CompositorService.globalWorkspaces || (ws.output && ws.output.toLowerCase() === screenName); + if (matchesScreen) + candidates.push(ws); + } + + if (candidates.length <= 1) + return; + + var current = -1; + for (var j = 0; j < candidates.length; j++) { + if (candidates[j].isFocused) { + current = j; + break; + } + } + if (current < 0) + current = 0; + + var next = current + offset; + if (Settings.data.bar.mouseWheelWrap) { + next = next % candidates.length; + if (next < 0) + next = candidates.length - 1; + } else { + if (next < 0 || next >= candidates.length) + return; + } + + if (next === current) + return; + CompositorService.switchToWorkspace(candidates[next]); + } + + function handleEmptyBarClick(action, followMouse, command, mouse) { + if (action === "none") + return; + if (action === "controlCenter") { + var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen); + controlCenterPanel?.toggle(null, followMouse ? mapToItem(null, mouse.x, mouse.y) : "ControlCenter"); + mouse.accepted = true; + } else if (action === "settings") { + var settingsPanel = PanelService.getPanel("settingsPanel", screen); + settingsPanel?.toggle(null, followMouse ? mapToItem(null, mouse.x, mouse.y) : null); + mouse.accepted = true; + } else if (action === "launcherPanel") { + var launcherPanel = PanelService.getPanel("launcherPanel", screen); + launcherPanel?.toggle(null, followMouse ? mapToItem(null, mouse.x, mouse.y) : null); + mouse.accepted = true; + } else if (action === "command") { + runCustomCommand(command); + mouse.accepted = true; + } + } + + function runCustomCommand(command) { + if (!command || command.trim() === "") + return; + + const processString = "import QtQuick; import Quickshell.Io; Process { command: [\"sh\", \"-lc\", \"\"] }"; + + try { + const processObj = Qt.createQmlObject(processString, root, "BarCommandProcess_" + Date.now()); + processObj.command = ["sh", "-lc", command]; + + processObj.exited.connect(function (exitCode) { + if (exitCode !== 0) { + ToastService.showError(I18n.tr("toast.custom-command-failed.title"), I18n.tr("toast.custom-command-failed.description", { + command: command, + code: exitCode + })); + } + processObj.destroy(); + }); + + processObj.running = true; + } catch (e) { + Logger.e("Bar", "Failed to start custom command:", e); + ToastService.showError(I18n.tr("toast.custom-command-failed.title"), I18n.tr("toast.custom-command-failed.description", { + command: command, + code: "start_error" + })); + } + } + MouseArea { anchors.fill: parent - acceptedButtons: Qt.RightButton + acceptedButtons: Qt.RightButton | Qt.MiddleButton + enabled: bar.barRightClickAction !== "none" || Settings.data.bar.middleClickAction !== "none" hoverEnabled: false preventStealing: true onClicked: mouse => { if (mouse.button === Qt.RightButton) { - // Check if click is over any widget - var widgets = BarService.getAllWidgetInstances(null, screen.name); - for (var i = 0; i < widgets.length; i++) { - var widget = widgets[i]; - if (!widget || !widget.visible || widget.widgetId === "Spacer") { - continue; - } - // Map click position to widget's coordinate space - var localPos = mapToItem(widget, mouse.x, mouse.y); - - if (root.barIsVertical) { - if (localPos.y >= -Style.marginS && localPos.y <= widget.height + Style.marginS) { - return; - } - } else { - if (localPos.x >= -Style.marginS && localPos.x <= widget.width + Style.marginS) { - return; - } - } - } - // Click is on empty bar background - open control center - var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen); - - // Map click position to screen-relative coordinates - // We need to map from bar coordinates to screen coordinates - var screenRelativePos = mapToItem(null, mouse.x, mouse.y); - - // Pass click position directly - controlCenterPanel?.toggle(null, screenRelativePos); - mouse.accepted = true; + if (bar.isPointOverWidget(mouse.x, mouse.y)) + return; + bar.handleEmptyBarClick(bar.barRightClickAction, Settings.data.bar.rightClickFollowMouse, Settings.data.bar.rightClickCommand, mouse); + return; + } + if (mouse.button === Qt.MiddleButton) { + if (bar.isPointOverWidget(mouse.x, mouse.y)) + return; + bar.handleEmptyBarClick(Settings.data.bar.middleClickAction || "none", Settings.data.bar.middleClickFollowMouse, Settings.data.bar.middleClickCommand, mouse); + return; } } } + // Debounce timer for wheel interactions + Timer { + id: barWheelDebounce + interval: 150 + repeat: false + onTriggered: { + bar.barWheelCooldown = false; + bar.barWheelAccumulatedDelta = 0; + } + } + + // Scroll on empty bar area to switch workspaces + WheelHandler { + id: barWheelHandler + target: bar + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + enabled: bar.barWheelAction !== "none" + + onWheel: function (event) { + if (bar.barWheelCooldown) + return; + if (bar.isPointOverWidget(event.x, event.y)) + return; + + var dy = event.angleDelta.y; + var dx = event.angleDelta.x; + var useDy = Math.abs(dy) >= Math.abs(dx); + var delta = useDy ? dy : dx; + + bar.barWheelAccumulatedDelta += delta; + var step = 120; + if (Math.abs(bar.barWheelAccumulatedDelta) >= step) { + var direction = bar.barWheelAccumulatedDelta > 0 ? -1 : 1; + if (Settings.data.bar.reverseScroll) + direction *= -1; + if (bar.barWheelAction === "workspace") { + bar.switchWorkspaceByOffset(direction); + } else if (bar.barWheelAction === "content") { + CompositorService.scrollWorkspaceContent(direction); + } + bar.barWheelCooldown = true; + barWheelDebounce.restart(); + bar.barWheelAccumulatedDelta = 0; + event.accepted = true; + } + } + } + Loader { anchors.fill: parent sourceComponent: root.barIsVertical ? verticalBarComponent : horizontalBarComponent diff --git a/Modules/Bar/Extras/WorkspacePill.qml b/Modules/Bar/Extras/WorkspacePill.qml index a68ee389e..149879e16 100644 --- a/Modules/Bar/Extras/WorkspacePill.qml +++ b/Modules/Bar/Extras/WorkspacePill.qml @@ -14,6 +14,7 @@ Item { required property real capsuleHeight required property real barHeight required property string labelMode + required property int fontWeight required property int characterCount required property real textRatio required property bool showLabelsOnlyWhenOccupied @@ -119,7 +120,7 @@ Item { pointSize: (isVertical ? pillContainer.pillWidth : pillContainer.pillHeight) * textRatio applyUiScale: false font.capitalization: Font.AllUppercase - font.weight: Style.fontWeightBold + font.weight: fontWeight horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.Wrap diff --git a/Modules/Bar/Widgets/AudioVisualizer.qml b/Modules/Bar/Widgets/AudioVisualizer.qml index c01be7adc..4edd7a28e 100644 --- a/Modules/Bar/Widgets/AudioVisualizer.qml +++ b/Modules/Bar/Widgets/AudioVisualizer.qml @@ -45,20 +45,20 @@ Item { readonly property bool shouldShow: (currentVisualizerType !== "" && currentVisualizerType !== "none") && (!hideWhenIdle || MediaService.isPlaying) - // Register/unregister with CavaService based on visibility - readonly property string cavaComponentId: "bar:audiovisualizer:" + root.screen.name + ":" + root.section + ":" + root.sectionWidgetIndex + // Register/unregister with SpectrumService based on visibility + readonly property string spectrumComponentId: "bar:audiovisualizer:" + root.screen.name + ":" + root.section + ":" + root.sectionWidgetIndex onShouldShowChanged: { if (root.shouldShow) { - CavaService.registerComponent(root.cavaComponentId); + SpectrumService.registerComponent(root.spectrumComponentId); } else { - CavaService.unregisterComponent(root.cavaComponentId); + SpectrumService.unregisterComponent(root.spectrumComponentId); } } Component.onDestruction: { if (root.shouldShow) { - CavaService.unregisterComponent(root.cavaComponentId); + SpectrumService.unregisterComponent(root.spectrumComponentId); } } @@ -177,7 +177,7 @@ Item { id: linearComponent NLinearSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: root.fillColor showMinimumSignal: true vertical: root.isVerticalBar @@ -189,7 +189,7 @@ Item { id: mirroredComponent NMirroredSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: root.fillColor showMinimumSignal: true vertical: root.isVerticalBar @@ -200,7 +200,7 @@ Item { id: waveComponent NWaveSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: root.fillColor showMinimumSignal: true vertical: root.isVerticalBar diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index e9ee2537c..865de3e57 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -80,7 +80,7 @@ Item { } let rateText = BatteryService.getRateText(selectedDevice); - if (rateText) { + if (!isPluggedIn && rateText) { const colonIdx = rateText.indexOf(":"); if (colonIdx >= 0) { rows.push([rateText.substring(0, colonIdx).trim(), rateText.substring(colonIdx + 1).trim()]); @@ -173,7 +173,7 @@ Item { id: nBattery visible: root.useGraphicMode anchors.centerIn: parent - baseSize: Style.barFontSize + baseSize: (Style.getBarHeightForScreen(root.screenName) / root.capsuleHeight) * Style.fontSizeXXS showPercentageText: root.displayMode !== "graphic-clean" vertical: root.isBarVertical percentage: root.percent @@ -194,20 +194,21 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton cursorShape: Qt.PointingHandCursor onEntered: { - if (root.tooltipContent) { + if (!getBatteryPanel()?.isPanelOpen && root.tooltipContent) { TooltipService.show(root, root.tooltipContent, BarService.getTooltipDirection(root.screen?.name)); + tooltipRefreshTimer.start(); } - tooltipRefreshTimer.start(); } onExited: { tooltipRefreshTimer.stop(); TooltipService.hide(); } onClicked: mouse => { + TooltipService.hide(); if (mouse.button === Qt.RightButton) { PanelService.showContextMenu(contextMenu, nBattery, screen); } else { - openBatteryPanel(); + toggleBatteryPanel(); } } } @@ -238,22 +239,25 @@ Item { forceClose: root.displayMode === "icon-only" || !root.isReady customBackgroundColor: root.isCharging ? Color.mPrimary : ((root.isLowBattery || root.isCriticalBattery) ? Color.mError : "transparent") customTextIconColor: root.isCharging ? Color.mOnPrimary : ((root.isLowBattery || root.isCriticalBattery) ? Color.mOnError : "transparent") - tooltipText: root.tooltipContent - - onClicked: openBatteryPanel() + tooltipText: !getBatteryPanel()?.isPanelOpen ? root.tooltipContent : "" + onClicked: toggleBatteryPanel() onRightClicked: PanelService.showContextMenu(contextMenu, pill, screen) } // ==================== SHARED ==================== - function openBatteryPanel() { + function getBatteryPanel() { var panel = PanelService.getPanel("batteryPanel", screen); if (panel) { panel.panelID = { showPowerProfiles: widgetSettings.showPowerProfiles !== undefined ? widgetSettings.showPowerProfiles : widgetMetadata.showPowerProfiles, showNoctaliaPerformance: widgetSettings.showNoctaliaPerformance !== undefined ? widgetSettings.showNoctaliaPerformance : widgetMetadata.showNoctaliaPerformance }; - panel.toggle(root); } + return panel; + } + + function toggleBatteryPanel() { + getBatteryPanel()?.toggle(root); } } diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 117be87ec..bb59c595b 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -110,6 +110,9 @@ Item { PanelService.showContextMenu(contextMenu, pill, screen); } tooltipText: { + if (PanelService.getPanel("bluetoothPanel", screen)?.isPanelOpen) { + return ""; + } if (pill.text !== "") { return pill.text; } diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index b998f85b8..541317583 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -46,11 +46,16 @@ Item { implicitWidth: pill.width implicitHeight: pill.height - // Track the brightness monitor reactively; explicitly update on screen/monitors changes - property var brightnessMonitor: null - - function updateMonitor() { - brightnessMonitor = BrightnessService.getMonitorForScreen(screen) || null; + // Track the brightness monitor reactively via declarative binding so it + // updates atomically when monitors change, avoiding a transient undefined + // state that occurs when Monitor QtObjects are destroyed before the + // imperative updateMonitor() call would run. + property var brightnessMonitor: { + var _ = BrightnessService.monitors; // reactive dependency + var __ = BrightnessService.ddcMonitors; // reactive dependency + if (!screen) + return null; + return BrightnessService.getMonitorForScreen(screen) ?? null; } function getControllableMonitorCount() { @@ -63,18 +68,6 @@ Item { return count; } - onScreenChanged: updateMonitor() - - Connections { - target: BrightnessService - function onMonitorsChanged() { - root.updateMonitor(); - } - function onDdcMonitorsChanged() { - root.updateMonitor(); - } - } - visible: brightnessMonitor !== null opacity: brightnessMonitor !== null ? 1.0 : 0.0 @@ -163,7 +156,8 @@ Item { forceClose: displayMode === "alwaysHide" tooltipText: { var monitor = brightnessMonitor; - if (!monitor || !monitor.brightnessControlAvailable || isNaN(monitor.brightness)) + var panel = PanelService.getPanel("brightnessPanel", screen); + if (panel?.isPanelOpen || !monitor || !monitor.brightnessControlAvailable || isNaN(monitor.brightness)) return ""; return I18n.tr("tooltips.brightness-at", { "brightness": Math.round(monitor.brightness * 100) diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml index 58040608c..d74bc80f7 100644 --- a/Modules/Bar/Widgets/Clock.qml +++ b/Modules/Bar/Widgets/Clock.qml @@ -105,6 +105,9 @@ Item { color: textColor wrapMode: Text.WordWrap Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + features: ({ + "tnum": 1 + }) } } } @@ -129,6 +132,9 @@ Item { color: textColor wrapMode: Text.WordWrap Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + features: ({ + "tnum": 1 + }) } } } diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index ee57aae44..f50511d02 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -49,7 +49,13 @@ NIconButton { // If we have a custom path and not using distro logo, use the theme icon. // If using distro logo, don't use theme icon. icon: (customIconPath === "" && !useDistroLogo) ? customIcon : "" - tooltipText: I18n.tr("tooltips.open-control-center") + tooltipText: { + if (PanelService.getPanel("controlCenterPanel", screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.open-control-center"); + } + } tooltipDirection: BarService.getTooltipDirection(screen?.name) baseSize: Style.getCapsuleHeightForScreen(screen?.name) applyUiScale: false diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index af026f6ac..c3c8bfba1 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -221,13 +221,22 @@ Item { if (showExecTooltip && hasExec) { if (leftClickExec !== "") { lines.push(I18n.tr("bar.custom-button.left-click-label") + `: ${leftClickExec}`); + } else if (!leftClickUpdateText) { + lines.push(I18n.tr("bar.custom-button.left-click-label") + ": " + I18n.tr("actions.widget-settings")); } + if (rightClickExec !== "") { lines.push(I18n.tr("bar.custom-button.right-click-label") + `: ${rightClickExec}`); + } else if (!rightClickUpdateText) { + lines.push(I18n.tr("bar.custom-button.right-click-label") + ": " + I18n.tr("actions.widget-settings")); } + if (middleClickExec !== "") { lines.push(I18n.tr("bar.custom-button.middle-click-label") + `: ${middleClickExec}`); + } else if (!middleClickUpdateText) { + lines.push(I18n.tr("bar.custom-button.middle-click-label") + ": " + I18n.tr("actions.widget-settings")); } + if (wheelMode === "unified" && wheelExec !== "") { lines.push(I18n.tr("bar.custom-button.wheel-label") + `: ${wheelExec}`); } else if (wheelMode === "separate") { @@ -502,7 +511,6 @@ Item { Logger.i("CustomButton", `Executing command: ${leftClickExec}`); } else if (!leftClickUpdateText) { BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); - //SettingsPanelService.openToTab(SettingsPanel.Tab.Bar, 1, screen); } if (!textStream && leftClickUpdateText) { runTextCommand(); @@ -513,6 +521,8 @@ Item { if (rightClickExec) { Quickshell.execDetached(["sh", "-lc", rightClickExec]); Logger.i("CustomButton", `Executing command: ${rightClickExec}`); + } else if (!rightClickUpdateText) { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); } if (!textStream && rightClickUpdateText) { runTextCommand(); @@ -523,6 +533,8 @@ Item { if (middleClickExec) { Quickshell.execDetached(["sh", "-lc", middleClickExec]); Logger.i("CustomButton", `Executing command: ${middleClickExec}`); + } else if (!middleClickUpdateText) { + BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); } if (!textStream && middleClickUpdateText) { runTextCommand(); diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 48fdf195f..a54b1fc63 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -39,17 +39,17 @@ Item { readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) // Widget settings - readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : "hidden" - readonly property bool hideWhenIdle: (widgetSettings.hideWhenIdle !== undefined) ? widgetSettings.hideWhenIdle : (widgetMetadata.hideWhenIdle !== undefined ? widgetMetadata.hideWhenIdle : false) - readonly property bool showAlbumArt: (widgetSettings.showAlbumArt !== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt - readonly property bool showArtistFirst: (widgetSettings.showArtistFirst !== undefined) ? widgetSettings.showArtistFirst : widgetMetadata.showArtistFirst - readonly property bool showVisualizer: (widgetSettings.showVisualizer !== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer - readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType !== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType - readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode - readonly property bool showProgressRing: (widgetSettings.showProgressRing !== undefined) ? widgetSettings.showProgressRing : widgetMetadata.showProgressRing - readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth - readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0) - readonly property string textColorKey: (widgetSettings.textColor !== undefined) ? widgetSettings.textColor : widgetMetadata.textColor + readonly property string hideMode: widgetSettings.hideMode !== undefined ? widgetSettings.hideMode : widgetMetadata.hideMode + readonly property bool hideWhenIdle: widgetSettings.hideWhenIdle !== undefined ? widgetSettings.hideWhenIdle : widgetMetadata.hideWhenIdle + readonly property bool showAlbumArt: widgetSettings.showAlbumArt !== undefined ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt + readonly property bool showArtistFirst: widgetSettings.showArtistFirst !== undefined ? widgetSettings.showArtistFirst : widgetMetadata.showArtistFirst + readonly property bool showVisualizer: widgetSettings.showVisualizer !== undefined ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer + readonly property string visualizerType: widgetSettings.visualizerType !== undefined ? widgetSettings.visualizerType : widgetMetadata.visualizerType + readonly property string scrollingMode: widgetSettings.scrollingMode !== undefined ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode + readonly property bool showProgressRing: widgetSettings.showProgressRing !== undefined ? widgetSettings.showProgressRing : widgetMetadata.showProgressRing + readonly property bool useFixedWidth: widgetSettings.useFixedWidth !== undefined ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth + readonly property real maxWidth: widgetSettings.maxWidth !== undefined ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0) + readonly property string textColorKey: widgetSettings.textColor !== undefined ? widgetSettings.textColor : widgetMetadata.textColor readonly property color textColor: Color.resolveColorKey(textColorKey) // Dimensions @@ -73,37 +73,31 @@ Item { return showArtistFirst ? (artist ? `${artist} - ${track}` : track) : (artist ? `${track} - ${artist}` : track); } - // CavaService registration for visualizer - readonly property string cavaComponentId: "bar:mediamini:" + root.screen?.name + ":" + root.section + ":" + root.sectionWidgetIndex - readonly property bool needsCava: root.showVisualizer && root.visualizerType !== "" && root.visualizerType !== "none" + // SpectrumService registration for visualizer + readonly property string spectrumComponentId: "bar:mediamini:" + root.screen?.name + ":" + root.section + ":" + root.sectionWidgetIndex + readonly property bool needsSpectrum: root.showVisualizer && root.visualizerType !== "" && root.visualizerType !== "none" && !root.isHidden Layout.preferredHeight: isVertical ? -1 : Style.getBarHeightForScreen(screenName) Layout.preferredWidth: isVertical ? Style.getBarHeightForScreen(screenName) : -1 Layout.fillHeight: false Layout.fillWidth: false - onNeedsCavaChanged: { - if (root.needsCava) { - CavaService.registerComponent(root.cavaComponentId); + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); } else { - CavaService.unregisterComponent(root.cavaComponentId); + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + } + + Component.onCompleted: { + if (root.needsSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); } } Component.onDestruction: { - if (root.needsCava) { - CavaService.unregisterComponent(root.cavaComponentId); - } - } - - readonly property string tooltipText: { - var text = title; - var controls = []; - controls.push("Left click to open player."); - controls.push("Right click for options."); - if (MediaService.canGoPrevious) - controls.push("Middle click for previous."); - return controls.length ? `${text}\n\n${controls.join("\n")}` : text; + SpectrumService.unregisterComponent(root.spectrumComponentId); } // Layout @@ -395,22 +389,25 @@ Item { hoverEnabled: true cursorShape: Qt.PointingHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton | Qt.ForwardButton | Qt.BackButton onClicked: mouse => { + TooltipService.hide(); if (mouse.button === Qt.LeftButton) { PanelService.getPanel("mediaPlayerPanel", screen)?.toggle(container); } else if (mouse.button === Qt.RightButton) { - TooltipService.hide(); PanelService.showContextMenu(contextMenu, container, screen); } else if (mouse.button === Qt.MiddleButton && hasPlayer) { MediaService.playPause(); - TooltipService.hide(); + } else if (mouse.button === Qt.ForwardButton && hasPlayer) { + MediaService.next(); + } else if (mouse.button === Qt.BackButton && hasPlayer) { + MediaService.previous(); } } onEntered: { - if (isVertical || scrollingMode === "never") { + if ((isVertical || scrollingMode === "never") && !PanelService.getPanel("mediaPlayerPanel", screen)?.isPanelOpen) { TooltipService.show(root, title, BarService.getTooltipDirection(root.screen?.name)); } } @@ -423,7 +420,7 @@ Item { NLinearSpectrum { width: parent.width - Style.marginS height: 20 - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 barPosition: root.barPosition @@ -435,7 +432,7 @@ Item { NMirroredSpectrum { width: parent.width - Style.marginS height: parent.height - Style.marginS - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 } @@ -446,7 +443,7 @@ Item { NWaveSpectrum { width: parent.width - Style.marginS height: parent.height - Style.marginS - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 } diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index 4d4b8929e..7a8b60619 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -143,13 +143,21 @@ Item { suffix: "%" forceOpen: displayMode === "alwaysShow" forceClose: displayMode === "alwaysHide" - tooltipText: I18n.tr("tooltips.microphone-volume-at", { - "volume": (() => { - const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; - const displayVolume = Math.min(maxVolume, AudioService.inputVolume); - return Math.round(displayVolume * 100); - })() - }) + tooltipText: { + if (PanelService.getPanel("audioPanel", screen)?.isPanelOpen) { + return ""; + } else { + const nick = AudioService.source?.nickname ?? ""; + const volumeText = I18n.tr("tooltips.microphone-volume-at", { + "volume": (() => { + const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + const displayVolume = Math.min(maxVolume, AudioService.inputVolume); + return Math.round(displayVolume * 100); + })() + }); + return nick ? volumeText + "\n" + nick : volumeText; + } + } onWheel: function (delta) { // As soon as we start scrolling to adjust volume, hide the tooltip diff --git a/Modules/Bar/Widgets/Network.qml b/Modules/Bar/Widgets/Network.qml index e6a85c377..1fc143f7e 100644 --- a/Modules/Bar/Widgets/Network.qml +++ b/Modules/Bar/Widgets/Network.qml @@ -131,6 +131,9 @@ Item { PanelService.showContextMenu(contextMenu, pill, screen); } tooltipText: { + if (PanelService.getPanel("networkPanel", screen)?.isPanelOpen) { + return ""; + } try { if (NetworkService.ethernetConnected) { const d = NetworkService.activeEthernetDetails || ({}); diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index dab692dde..e191e37c3 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -59,7 +59,13 @@ NIconButton { applyUiScale: false customRadius: Style.radiusL icon: NotificationService.doNotDisturb ? "bell-off" : "bell" - tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.open-notification-history-enable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd") + tooltipText: { + if (PanelService.getPanel("notificationHistoryPanel", screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.open-notification-history-enable-dnd"); + } + } tooltipDirection: BarService.getTooltipDirection(screen?.name) colorBg: Style.capsuleColor colorFg: Color.resolveColorKey(iconColorKey) diff --git a/Modules/Bar/Widgets/SessionMenu.qml b/Modules/Bar/Widgets/SessionMenu.qml index f474fe654..92359ec7e 100644 --- a/Modules/Bar/Widgets/SessionMenu.qml +++ b/Modules/Bar/Widgets/SessionMenu.qml @@ -37,7 +37,12 @@ NIconButton { applyUiScale: false customRadius: Style.radiusL icon: "power" - tooltipText: I18n.tr("tooltips.session-menu") + tooltipText: { + if (PanelService.getPanel("sessionMenuPanel", screen)?.isPanelOpen) + return ""; + else + return I18n.tr("tooltips.session-menu"); + } tooltipDirection: BarService.getTooltipDirection(screenName) colorBg: Style.capsuleColor colorFg: Color.resolveColorKey(iconColorKey) diff --git a/Modules/Bar/Widgets/Settings.qml b/Modules/Bar/Widgets/Settings.qml index 6cd771800..733bd06ac 100644 --- a/Modules/Bar/Widgets/Settings.qml +++ b/Modules/Bar/Widgets/Settings.qml @@ -34,7 +34,13 @@ NIconButton { readonly property color iconColor: Color.resolveColorKey(valueIconColor) icon: "settings" - tooltipText: !PanelService.getPanel("settingsPanel", screen)?.isPanelOpen ? I18n.tr("tooltips.open-settings") : "" + tooltipText: { + if (PanelService.getPanel("settingsPanel", screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.open-settings"); + } + } tooltipDirection: BarService.getTooltipDirection(screen?.name) baseSize: Style.getCapsuleHeightForScreen(screen?.name) applyUiScale: false diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index 778c1f743..fcf137015 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -47,6 +47,7 @@ Item { readonly property bool usePadding: !compactMode && !isVertical && useMonospaceFont && ((widgetSettings.usePadding !== undefined) ? widgetSettings.usePadding : widgetMetadata.usePadding) readonly property bool showCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage + readonly property bool showCpuCores: (widgetSettings.showCpuCores !== undefined) ? widgetSettings.showCpuCores : widgetMetadata.showCpuCores readonly property bool showCpuFreq: (widgetSettings.showCpuFreq !== undefined) ? widgetSettings.showCpuFreq : widgetMetadata.showCpuFreq readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp readonly property bool showGpuTemp: (widgetSettings.showGpuTemp !== undefined) ? widgetSettings.showGpuTemp : widgetMetadata.showGpuTemp @@ -94,6 +95,11 @@ Item { // CPU rows.push([I18n.tr("system-monitor.cpu-usage"), `${Math.round(SystemStatService.cpuUsage)}% (${SystemStatService.cpuFreq.replace(/[^0-9.]/g, "")} GHz)`]); + if (showCpuCores) { + SystemStatService.coresUsage.forEach((usage, core) => rows.push([" " + I18n.tr("system-monitor.core-usage", { + "id": core + }), `${Math.round(usage)}%`])); + } if (SystemStatService.cpuTemp > 0) { rows.push([I18n.tr("system-monitor.cpu-temp"), `${Math.round(SystemStatService.cpuTemp)}°C`]); @@ -110,11 +116,11 @@ Item { } // Memory - rows.push([I18n.tr("common.memory"), `${Math.round(SystemStatService.memPercent)}% (${SystemStatService.formatGigabytes(SystemStatService.memGb).replace(/[^0-9.]/g, "") + " GB"})`]); + rows.push([I18n.tr("common.memory"), `${Math.round(SystemStatService.memPercent)}% (${(SystemStatService.memGb).toFixed(1)} GiB)`]); // Swap (if available) if (SystemStatService.swapTotalGb > 0) { - rows.push([I18n.tr("bar.system-monitor.swap-usage-label"), `${Math.round(SystemStatService.swapPercent)}% (${SystemStatService.formatGigabytes(SystemStatService.swapGb).replace(/[^0-9.]/g, "") + " GB"})`]); + rows.push([I18n.tr("bar.system-monitor.swap-usage-label"), `${Math.round(SystemStatService.swapPercent)}% (${(SystemStatService.swapGb).toFixed(1)} GiB)`]); } // Network @@ -152,6 +158,11 @@ Item { id: contextMenu model: [ + { + "label": I18n.tr("system-monitor.title"), + "action": "sysmon-settings", + "icon": "settings" + }, { "label": I18n.tr("actions.widget-settings"), "action": "widget-settings", @@ -163,7 +174,9 @@ Item { contextMenu.close(); PanelService.closeContextMenu(screen); - if (action === "widget-settings") { + if (action === "sysmon-settings") { + SettingsPanelService.openToTab(SettingsPanel.Tab.System, 0, screen); + } else if (action === "widget-settings") { BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); } } @@ -184,25 +197,12 @@ Item { Component { id: miniGaugeComponent - Rectangle { - id: miniGauge - property real ratio: 0 // 0..1 - property color statColor: Color.mPrimary // Color based on warning/critical state - + NLinearGauge { + ratio: 0 + orientation: Qt.Vertical + fillColor: Color.mPrimary width: miniGaugeWidth height: iconSize - radius: width / 2 - color: Color.mOutline - - // Fill that grows from bottom - Rectangle { - property real fillHeight: parent.height * Math.min(1, Math.max(0, miniGauge.ratio)) - width: parent.width - height: fillHeight - radius: parent.radius - color: miniGauge.statColor - anchors.bottom: parent.bottom - } } } @@ -213,7 +213,7 @@ Item { rows: isVertical ? -1 : 1 columns: isVertical ? 1 : -1 rowSpacing: isVertical ? (compactMode ? Style.marginL : Style.marginXL) : 0 - columnSpacing: isVertical ? 0 : (Style.marginM) + columnSpacing: isVertical ? 0 : Style.marginM // CPU Usage Component Item { @@ -228,10 +228,12 @@ Item { GridLayout { id: cpuUsageContent anchors.centerIn: parent - flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight - rows: (isVertical && !compactMode) ? 2 : 1 - columns: (isVertical && !compactMode) ? 1 : 2 - rowSpacing: Style.marginXXS + + property bool verticalDisplay: isVertical && (!compactMode || showCpuCores) + flow: verticalDisplay ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: verticalDisplay ? -1 : 1 + columns: verticalDisplay ? 1 : -1 + rowSpacing: compactMode ? 3 : Style.marginXS columnSpacing: compactMode ? 3 : Style.marginXS Item { @@ -266,10 +268,10 @@ Item { Layout.column: isVertical ? 0 : 1 } - // Compact mode + // Compact mode general cpu Loader { - active: compactMode - visible: compactMode + active: compactMode && !showCpuCores + visible: compactMode && !showCpuCores sourceComponent: miniGaugeComponent Layout.alignment: Qt.AlignCenter Layout.row: 0 @@ -277,7 +279,21 @@ Item { onLoaded: { item.ratio = Qt.binding(() => SystemStatService.cpuUsage / 100); - item.statColor = Qt.binding(() => SystemStatService.cpuColor); + item.fillColor = Qt.binding(() => SystemStatService.cpuColor); + } + } + + // Compact mode for cores + Repeater { + model: (compactMode && showCpuCores) ? SystemStatService.coresUsage : [] + + delegate: NLinearGauge { + required property var modelData + width: isVertical ? iconSize : miniGaugeWidth + height: isVertical ? miniGaugeWidth : iconSize + orientation: isVertical ? Qt.Horizontal : Qt.Vertical + ratio: modelData / 100 + fillColor: SystemStatService.getCoreUsageColor(modelData) } } } @@ -345,7 +361,6 @@ Item { onLoaded: { item.ratio = Qt.binding(() => SystemStatService.cpuFreqRatio); - item.statColor = Qt.binding(() => Color.mPrimary); } } } @@ -413,7 +428,7 @@ Item { onLoaded: { item.ratio = Qt.binding(() => SystemStatService.cpuTemp / 100); - item.statColor = Qt.binding(() => SystemStatService.tempColor); + item.fillColor = Qt.binding(() => SystemStatService.tempColor); } } } @@ -481,7 +496,7 @@ Item { onLoaded: { item.ratio = Qt.binding(() => SystemStatService.gpuTemp / 100); - item.statColor = Qt.binding(() => SystemStatService.gpuColor); + item.fillColor = Qt.binding(() => SystemStatService.gpuColor); } } } @@ -549,7 +564,6 @@ Item { onLoaded: { item.ratio = Qt.binding(() => Math.min(1, SystemStatService.loadAvg1 / SystemStatService.nproc)); - item.statColor = Qt.binding(() => Color.mPrimary); } } } @@ -620,7 +634,7 @@ Item { onLoaded: { item.ratio = Qt.binding(() => SystemStatService.memPercent / 100); - item.statColor = Qt.binding(() => SystemStatService.memColor); + item.fillColor = Qt.binding(() => SystemStatService.memColor); } } } @@ -692,7 +706,7 @@ Item { onLoaded: { item.ratio = Qt.binding(() => SystemStatService.swapPercent / 100); - item.statColor = Qt.binding(() => SystemStatService.swapColor); + item.fillColor = Qt.binding(() => SystemStatService.swapColor); } } } @@ -896,7 +910,7 @@ Item { onLoaded: { item.ratio = Qt.binding(() => (showDiskAvailable ? SystemStatService.diskAvailPercents[diskPath] : SystemStatService.diskPercents[diskPath] ?? 0) / 100); - item.statColor = Qt.binding(() => SystemStatService.getDiskColor(diskPath, showDiskAvailable)); + item.fillColor = Qt.binding(() => SystemStatService.getDiskColor(diskPath, showDiskAvailable)); } } } @@ -924,8 +938,10 @@ Item { } } onEntered: { - TooltipService.show(root, buildTooltipContent(), BarService.getTooltipDirection(root.screen?.name)); - tooltipRefreshTimer.start(); + if (!PanelService.getPanel("systemStatsPanel", screen).isPanelOpen) { + TooltipService.show(root, buildTooltipContent(), BarService.getTooltipDirection(root.screen?.name)); + tooltipRefreshTimer.start(); + } } onExited: { tooltipRefreshTimer.stop(); diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index afdd42bd5..e0a0153b4 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -203,7 +203,38 @@ Item { if (!appId || !pinnedApps || pinnedApps.length === 0) return false; const normalizedId = normalizeAppId(appId); - return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId); + // Direct match + if (pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId)) + return true; + // Resolve via desktop entry lookup (handles StartupWMClass != .desktop filename) + const resolved = resolveToDesktopEntryId(appId); + if (resolved !== appId) { + const normalizedResolved = normalizeAppId(resolved); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedResolved); + } + return false; + } + + // Desktop entry ID resolution cache (cleared when DesktopEntries change) + property var _desktopEntryIdCache: ({}) + + // Resolve a toplevel appId to its canonical .desktop entry ID via heuristic lookup. + function resolveToDesktopEntryId(appId) { + if (!appId) + return appId; + if (_desktopEntryIdCache.hasOwnProperty(appId)) + return _desktopEntryIdCache[appId]; + try { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.id) { + _desktopEntryIdCache[appId] = entry.id; + return entry.id; + } + } + } catch (e) {} + _desktopEntryIdCache[appId] = appId; + return appId; } // Helper function to get app name from desktop entry @@ -272,7 +303,14 @@ Item { return false; const pinnedApps = Settings.data.dock.pinnedApps || []; const normalizedId = normalizeAppId(appId); - return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId); + if (pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId)) + return true; + const resolved = resolveToDesktopEntryId(appId); + if (resolved !== appId) { + const normalizedResolved = normalizeAppId(resolved); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedResolved); + } + return false; } // Helper function to toggle app pin/unpin @@ -331,6 +369,10 @@ Item { "title": w.title || getAppNameFromDesktopEntry(w.appId) }); processedAppIds.add(normalizeAppId(w.appId)); + // Also track the resolved desktop entry ID so pinned app matching works + const resolvedId = resolveToDesktopEntryId(w.appId); + if (resolvedId !== w.appId) + processedAppIds.add(normalizeAppId(resolvedId)); } } } catch (e) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 1d0c0037c..d0126472c 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -349,7 +349,13 @@ Item { visible: root.drawerEnabled && dropdownItems.length > 0 && BarService.getPillDirection(root) width: isVertical ? barHeight : capsuleHeight height: isVertical ? capsuleHeight : barHeight - tooltipText: I18n.tr("tooltips.open-tray-dropdown") + tooltipText: { + if (PanelService.getPanel("trayDrawerPanel", root.screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.open-tray-dropdown"); + } + } tooltipDirection: BarService.getTooltipDirection(root.screen?.name) baseSize: capsuleHeight applyUiScale: false diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index c7cef0d08..753cfff9e 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -136,13 +136,21 @@ Item { suffix: "%" forceOpen: displayMode === "alwaysShow" forceClose: displayMode === "alwaysHide" - tooltipText: I18n.tr("tooltips.volume-at", { - "volume": (() => { - const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; - const displayVolume = Math.min(maxVolume, AudioService.volume); - return Math.round(displayVolume * 100); - })() - }) + tooltipText: { + if (PanelService.getPanel("audioPanel", screen)?.isPanelOpen) { + return ""; + } else { + const nick = AudioService.sink?.nickname ?? ""; + const volumeText = I18n.tr("tooltips.volume-at", { + "volume": (() => { + const maxVolume = Settings.data.audio.volumeOverdrive ? 1.5 : 1.0; + const displayVolume = Math.min(maxVolume, AudioService.volume); + return Math.round(displayVolume * 100); + })() + }); + return nick ? volumeText + "\n" + nick : volumeText; + } + } onWheel: function (delta) { // Hide tooltip as soon as the user starts scrolling to adjust volume diff --git a/Modules/Bar/Widgets/WallpaperSelector.qml b/Modules/Bar/Widgets/WallpaperSelector.qml index e017fbb76..545fcbf6b 100644 --- a/Modules/Bar/Widgets/WallpaperSelector.qml +++ b/Modules/Bar/Widgets/WallpaperSelector.qml @@ -37,7 +37,13 @@ NIconButton { applyUiScale: false customRadius: Style.radiusL icon: "wallpaper-selector" - tooltipText: I18n.tr("tooltips.wallpaper-selector") + tooltipText: { + if (PanelService.getPanel("wallpaperPanel", screen)?.isPanelOpen) { + return ""; + } else { + return I18n.tr("tooltips.wallpaper-selector"); + } + } tooltipDirection: BarService.getTooltipDirection(screen?.name) colorBg: Style.capsuleColor colorFg: Color.resolveColorKey(iconColorKey) diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index 2e1511360..68d27434a 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -44,6 +44,19 @@ Item { readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode readonly property bool hasLabel: (labelMode !== "none") + readonly property string fontWeight: { + var fontWeightSetting = (widgetSettings.fontWeight !== undefined) ? widgetSettings.fontWeight : widgetMetadata.fontWeight; + + if (fontWeightSetting === "regular") + return Style.fontWeightRegular; + if (fontWeightSetting === "medium") + return Style.fontWeightMedium; + if (fontWeightSetting === "semibold") + return Style.fontWeightSemiBold; + if (fontWeightSetting === "bold") + return Style.fontWeightBold; + return Style.fontWeightBold; + } readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied readonly property bool followFocusedScreen: (widgetSettings.followFocusedScreen !== undefined) ? widgetSettings.followFocusedScreen : widgetMetadata.followFocusedScreen readonly property int characterCount: isVertical ? 2 : ((widgetSettings.characterCount !== undefined) ? widgetSettings.characterCount : widgetMetadata.characterCount) @@ -181,7 +194,7 @@ Item { } function switchByOffset(offset) { - if (localWorkspaces.count === 0) + if (localWorkspaces.count <= 1) return; var current = getFocusedLocalIndex(); if (current < 0) @@ -189,6 +202,8 @@ Item { var next = (current + offset) % localWorkspaces.count; if (next < 0) next = localWorkspaces.count - 1; + if (next === current) + return; const ws = localWorkspaces.get(next); if (ws && ws.idx !== undefined) CompositorService.switchToWorkspace(ws); @@ -230,18 +245,20 @@ Item { Settings.data.dock.pinnedApps = pinnedApps; } - Component.onCompleted: { - refreshWorkspaces(); - } + // Deferred to next event-loop tick via Qt.callLater to avoid re-entrant incubation: + // Calling localWorkspaces.append() synchronously there causes the inner Repeater to + // create WorkspacePill delegates mid-finalization, corrupting the V4 heap + // (SIGSEGV in QV4::Object::insertMember). + Component.onCompleted: Qt.callLater(refreshWorkspaces) Component.onDestruction: { root.isDestroying = true; } - onScreenChanged: refreshWorkspaces() - onScreenNameChanged: refreshWorkspaces() - onHideUnoccupiedChanged: refreshWorkspaces() - onShowApplicationsChanged: refreshWorkspaces() + onScreenChanged: Qt.callLater(refreshWorkspaces) + onScreenNameChanged: Qt.callLater(refreshWorkspaces) + onHideUnoccupiedChanged: Qt.callLater(refreshWorkspaces) + onShowApplicationsChanged: Qt.callLater(refreshWorkspaces) Connections { target: CompositorService @@ -553,6 +570,7 @@ Item { capsuleHeight: root.capsuleHeight barHeight: root.barHeight labelMode: root.labelMode + fontWeight: root.fontWeight characterCount: root.characterCount textRatio: root.textRatio showLabelsOnlyWhenOccupied: root.showLabelsOnlyWhenOccupied @@ -587,6 +605,7 @@ Item { capsuleHeight: root.capsuleHeight barHeight: root.barHeight labelMode: root.labelMode + fontWeight: root.fontWeight characterCount: root.characterCount textRatio: root.textRatio showLabelsOnlyWhenOccupied: root.showLabelsOnlyWhenOccupied @@ -659,8 +678,8 @@ Item { MouseArea { anchors.fill: parent hoverEnabled: true - enabled: !groupedContainer.hasWindows - cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + enabled: true + cursorShape: Qt.PointingHandCursor acceptedButtons: Qt.LeftButton | Qt.RightButton preventStealing: true onPressed: mouse => { @@ -851,7 +870,7 @@ Item { family: Settings.data.ui.fontFixed font { pointSize: barFontSize * 0.75 - weight: Style.fontWeightBold + weight: fontWeight capitalization: Font.AllUppercase } applyUiScale: false diff --git a/Modules/Cards/CalendarHeaderCard.qml b/Modules/Cards/CalendarHeaderCard.qml index 45a181bb8..cd07cf3b5 100644 --- a/Modules/Cards/CalendarHeaderCard.qml +++ b/Modules/Cards/CalendarHeaderCard.qml @@ -104,7 +104,7 @@ Rectangle { } NText { - text: root.weatherReady && !Settings.data.location.hideWeatherTimezone ? ` (${LocationService.data.weather.timezone_abbreviation})` : "" + text: root.weatherReady && !Settings.data.location.hideWeatherTimezone ? `${Settings.data.location.hideWeatherCityName ? "" : " "}(${LocationService.data.weather.timezone_abbreviation})` : "" pointSize: Style.fontSizeXS color: Qt.alpha(Color.mOnPrimary, 0.7) } diff --git a/Modules/Cards/MediaCard.qml b/Modules/Cards/MediaCard.qml index b9f12f036..e042d3374 100644 --- a/Modules/Cards/MediaCard.qml +++ b/Modules/Cards/MediaCard.qml @@ -15,26 +15,26 @@ NBox { // Track whether we have an active media player readonly property bool hasActivePlayer: MediaService.currentPlayer && MediaService.canPlay - // CavaService registration for visualizer - readonly property bool needsCava: Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none" + // SpectrumService registration for visualizer + readonly property bool needsSpectrum: Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none" - onNeedsCavaChanged: { - if (root.needsCava) { - CavaService.registerComponent("mediacard"); + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent("mediacard"); } else { - CavaService.unregisterComponent("mediacard"); + SpectrumService.unregisterComponent("mediacard"); } } Component.onCompleted: { - if (root.needsCava) { - CavaService.registerComponent("mediacard"); + if (root.needsSpectrum) { + SpectrumService.registerComponent("mediacard"); } updateCachedWallpaper(); } Component.onDestruction: { - CavaService.unregisterComponent("mediacard"); + SpectrumService.unregisterComponent("mediacard"); } property string wallpaper: WallpaperService.getWallpaper(screen.name) @@ -157,7 +157,7 @@ NBox { id: linearComponent NLinearSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.8 } @@ -167,7 +167,7 @@ NBox { id: mirroredComponent NMirroredSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.8 } @@ -177,7 +177,7 @@ NBox { id: waveComponent NWaveSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.8 } @@ -268,7 +268,7 @@ NBox { // No media player detected - centered disc icon NIcon { anchors.centerIn: parent - visible: !root.hasActivePlayer && CavaService.isIdle + visible: !root.hasActivePlayer && SpectrumService.isIdle icon: "disc" pointSize: Style.fontSizeXXXL * 3 color: Color.mOnSurfaceVariant diff --git a/Modules/DesktopWidgets/DesktopWidgets.qml b/Modules/DesktopWidgets/DesktopWidgets.qml index 462aba209..e440a9ba4 100644 --- a/Modules/DesktopWidgets/DesktopWidgets.qml +++ b/Modules/DesktopWidgets/DesktopWidgets.qml @@ -51,7 +51,7 @@ Variants { // Only create PanelWindow if enabled AND (screen has widgets OR in edit mode) // During compositor overview, show widgets only when overviewEnabled is true. - active: modelData && Settings.data.desktopWidgets.enabled && (screenWidgets.length > 0 || DesktopWidgetRegistry.editMode) && (!CompositorService.overviewActive || Settings.data.desktopWidgets.overviewEnabled) && !PowerProfileService.noctaliaPerformanceMode && !PanelService.lockScreen?.active + active: modelData && Settings.data.desktopWidgets.enabled && (screenWidgets.length > 0 || DesktopWidgetRegistry.editMode) && (!CompositorService.overviewActive || Settings.data.desktopWidgets.overviewEnabled) && (!PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableDesktopWidgets) && !PanelService.lockScreen?.active sourceComponent: PanelWindow { id: window @@ -441,6 +441,15 @@ Variants { } } + NIconButton { + icon: "grid-3x3" + visible: Settings.data.desktopWidgets.gridSnap + tooltipText: I18n.tr("panels.desktop-widgets.edit-mode-grid-snap-scale-label") + colorBg: Settings.data.desktopWidgets.gridSnapScale ? Color.mPrimary : Color.mSurfaceVariant + colorFg: Settings.data.desktopWidgets.gridSnapScale ? Color.mOnPrimary : Color.mPrimary + onClicked: Settings.data.desktopWidgets.gridSnapScale = !Settings.data.desktopWidgets.gridSnapScale + } + NIconButton { icon: "grid-4x4" tooltipText: I18n.tr("panels.desktop-widgets.edit-mode-grid-snap-label") diff --git a/Modules/DesktopWidgets/DraggableDesktopWidget.qml b/Modules/DesktopWidgets/DraggableDesktopWidget.qml index bd9057f97..c3c544cd8 100644 --- a/Modules/DesktopWidgets/DraggableDesktopWidget.qml +++ b/Modules/DesktopWidgets/DraggableDesktopWidget.qml @@ -21,8 +21,10 @@ Item { readonly property bool isDragging: internal.isDragging readonly property bool isScaling: internal.isScaling - property bool showBackground: (widgetData && widgetData.showBackground !== undefined) ? widgetData.showBackground : true - property bool roundedCorners: (widgetData && widgetData.roundedCorners !== undefined) ? widgetData.roundedCorners : true + // All Desktop widgets have these settings, but fallback just in case + readonly property var _metadata: widgetData?.id ? DesktopWidgetRegistry.widgetMetadata[widgetData.id] : null + property bool showBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : (_metadata?.showBackground ?? true) + property bool roundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : (_metadata?.roundedCorners ?? true) property real widgetScale: 1.0 property real minScale: 0.5 @@ -113,6 +115,35 @@ Item { return Math.round(coord / root.gridSize) * root.gridSize; } + function snapScaleToGrid(scale) { + if (!Settings.data.desktopWidgets.gridSnap || !Settings.data.desktopWidgets.gridSnapScale) { + return scale; + } + + // Get widget's base width + var initialWidth = internal.initialWidth; + var initialScale = internal.initialScale; + if (initialWidth <= 0 || initialScale <= 0) { + return scale; + } + + // Since initialWidth = baseWidth * initialScale + var baseWidth = initialWidth / initialScale; + + // Snap the resulting width with the scale + var resultingWidth = baseWidth * scale; + var snappedWidth = root.snapToGrid(resultingWidth); + + // Check that the snappedWidth isn't smaller than one grid size + if (snappedWidth < root.gridSize) { + snappedWidth = root.gridSize; + } + + // Return the ratio of the snappedWidth and the baseWidth, which is the new snapped scale + var snappedScale = snappedWidth / baseWidth; + return Math.max(minScale, Math.min(maxScale, snappedScale)); + } + function updateWidgetData(properties) { if (widgetIndex < 0 || !screen || !screen.name) { return; @@ -258,14 +289,14 @@ Item { color: DesktopWidgetRegistry.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : "transparent" border.color: (DesktopWidgetRegistry.editMode || internal.isDragging) ? (internal.isDragging ? Color.mOutline : Color.mPrimary) : "transparent" border.width: DesktopWidgetRegistry.editMode ? 3 : 0 - radius: Math.round(Style.radiusL * root.widgetScale) + radius: Math.min(Math.round(Style.radiusL * root.widgetScale), Style.radiusL, width / 2, height / 2) z: -1 } Rectangle { id: container anchors.fill: parent - radius: root.roundedCorners ? Math.round(Style.radiusL * root.widgetScale) : 0 + radius: root.roundedCorners ? Math.min(Math.round(Style.radiusL * root.widgetScale), Style.radiusL, width / 2, height / 2) : 0 color: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity) border { width: 1 @@ -301,7 +332,7 @@ Item { if (DesktopWidgetRegistry.isPluginWidget(widgetId)) { var pluginId = widgetId.replace("plugin:", ""); var manifest = PluginRegistry.getPluginManifest(pluginId); - hasSettings = manifest && manifest.entryPoints && manifest.entryPoints.settings; + hasSettings = manifest && manifest.entryPoints && (manifest.entryPoints.settings || manifest.entryPoints.desktopWidgetSettings); } else { hasSettings = DesktopWidgetRegistry.widgetSettingsMap[widgetId] !== undefined; } @@ -552,6 +583,8 @@ Item { internal.isScaling = true; internal.initialScale = root.widgetScale; internal.lastScale = root.widgetScale; + internal.initialWidth = root.width; + internal.initialHeight = root.height; } onPositionChanged: mouse => { @@ -569,6 +602,8 @@ Item { var scaleDelta = diagonalDelta / sensitivity; var newScale = Math.max(root.minScale, Math.min(root.maxScale, internal.initialScale + scaleDelta)); + newScale = root.snapScaleToGrid(newScale); + if (!isNaN(newScale) && newScale > 0) { root.widgetScale = newScale; internal.lastScale = newScale; @@ -583,6 +618,7 @@ Item { }); internal.isScaling = false; internal.operationType = ""; + root.widgetScale = root.snapScaleToGrid(root.widgetScale); internal.lastScale = root.widgetScale; } } diff --git a/Modules/DesktopWidgets/Widgets/DesktopAudioVisualizer.qml b/Modules/DesktopWidgets/Widgets/DesktopAudioVisualizer.qml new file mode 100644 index 000000000..24464cace --- /dev/null +++ b/Modules/DesktopWidgets/Widgets/DesktopAudioVisualizer.qml @@ -0,0 +1,116 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import qs.Commons +import qs.Modules.DesktopWidgets +import qs.Services.Media +import qs.Services.UI +import qs.Widgets +import qs.Widgets.AudioSpectrum + +DraggableDesktopWidget { + id: root + + defaultY: 280 + + readonly property var widgetMetadata: DesktopWidgetRegistry.widgetMetadata["AudioVisualizer"] + + readonly property int visualizerWidth: (widgetData && widgetData.width !== undefined) ? widgetData.width : (widgetMetadata?.width ?? 320) + readonly property int visualizerHeight: (widgetData && widgetData.height !== undefined) ? widgetData.height : (widgetMetadata?.height ?? 72) + readonly property string visualizerType: (widgetData && widgetData.visualizerType !== undefined) ? widgetData.visualizerType : (widgetMetadata?.visualizerType ?? "linear") + readonly property bool hideWhenIdle: (widgetData && widgetData.hideWhenIdle !== undefined) ? widgetData.hideWhenIdle : (widgetMetadata?.hideWhenIdle ?? false) + readonly property string colorName: (widgetData && widgetData.colorName !== undefined) ? widgetData.colorName : (widgetMetadata?.colorName ?? "primary") + + readonly property color fillColor: Color.resolveColorKey(colorName) + + readonly property bool shouldShow: visualizerType !== "" && visualizerType !== "none" && (!hideWhenIdle || MediaService.isPlaying) + readonly property bool isHidden: !shouldShow + readonly property bool shouldRegisterSpectrum: shouldShow + + // Keep widget visible in edit mode so users can move/configure it + visible: !root.isHidden || DesktopWidgetRegistry.editMode + + readonly property string spectrumComponentId: "desktop:audiovisualizer:" + (root.screen ? root.screen.name : "unknown") + ":" + root.widgetIndex + + onShouldRegisterSpectrumChanged: { + if (root.shouldRegisterSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); + } else { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + } + + Component.onCompleted: { + if (root.shouldRegisterSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); + } + } + + Component.onDestruction: { + SpectrumService.unregisterComponent(root.spectrumComponentId); + } + + implicitWidth: Math.round(visualizerWidth * widgetScale) + implicitHeight: Math.round(visualizerHeight * widgetScale) + width: implicitWidth + height: implicitHeight + + Rectangle { + id: visualizerMask + anchors.fill: parent + color: "transparent" + radius: root.roundedCorners ? Math.min(Math.round(Style.radiusL * root.widgetScale), Style.radiusL, width / 2, height / 2) : 0 + clip: true + + Loader { + id: visualizerLoader + anchors.fill: parent + anchors.margins: root.showBackground ? Math.round(Style.marginXS * root.widgetScale) : 0 + active: root.shouldShow + asynchronous: true + + sourceComponent: { + switch (root.visualizerType) { + case "linear": + return linearComponent; + case "mirrored": + return mirroredComponent; + case "wave": + return waveComponent; + default: + return null; + } + } + } + } + + Component { + id: linearComponent + NLinearSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: root.fillColor + showMinimumSignal: true + } + } + + Component { + id: mirroredComponent + NMirroredSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: root.fillColor + showMinimumSignal: true + } + } + + Component { + id: waveComponent + NWaveSpectrum { + anchors.fill: parent + values: SpectrumService.values + fillColor: root.fillColor + showMinimumSignal: true + } + } +} diff --git a/Modules/DesktopWidgets/Widgets/DesktopClock.qml b/Modules/DesktopWidgets/Widgets/DesktopClock.qml index bf159f82c..e72d4f01a 100644 --- a/Modules/DesktopWidgets/Widgets/DesktopClock.qml +++ b/Modules/DesktopWidgets/Widgets/DesktopClock.qml @@ -14,12 +14,12 @@ DraggableDesktopWidget { readonly property color clockTextColor: Color.resolveColorKey(clockColor) readonly property real fontSize: Math.round(Style.fontSizeXXXL * 2.5 * widgetScale) - readonly property real widgetOpacity: (widgetData && widgetData.opacity !== undefined) ? widgetData.opacity : 1.0 - readonly property string clockStyle: (widgetData && widgetData.clockStyle !== undefined) ? widgetData.clockStyle : (widgetMetadata.clockStyle !== undefined ? widgetMetadata.clockStyle : "digital") - readonly property string clockColor: (widgetData && widgetData.clockColor !== undefined) ? widgetData.clockColor : (widgetMetadata.clockColor !== undefined ? widgetMetadata.clockColor : "none") - readonly property bool useCustomFont: (widgetData && widgetData.useCustomFont !== undefined) ? widgetData.useCustomFont : (widgetMetadata.useCustomFont !== undefined ? widgetMetadata.useCustomFont : false) - readonly property string customFont: (widgetData && widgetData.customFont !== undefined) ? widgetData.customFont : "" - readonly property string format: (widgetData && widgetData.format !== undefined) ? widgetData.format : (widgetMetadata.format !== undefined ? widgetMetadata.format : "HH:mm\\nd MMMM yyyy") + readonly property real widgetOpacity: widgetData.opacity !== undefined ? widgetData.opacity : 1.0 + readonly property string clockStyle: widgetData.clockStyle !== undefined ? widgetData.clockStyle : widgetMetadata.clockStyle + readonly property string clockColor: widgetData.clockColor !== undefined ? widgetData.clockColor : widgetMetadata.clockColor + readonly property bool useCustomFont: widgetData.useCustomFont !== undefined ? widgetData.useCustomFont : widgetMetadata.useCustomFont + readonly property string customFont: widgetData.customFont !== undefined ? widgetData.customFont : widgetMetadata.customFont + readonly property string format: widgetData.format !== undefined ? widgetData.format : widgetMetadata.format readonly property real contentPadding: Math.round((clockStyle === "minimal" ? Style.marginL : Style.marginXL) * widgetScale) implicitWidth: contentLoader.item ? Math.round((contentLoader.item.implicitWidth || contentLoader.item.width || 0) + contentPadding * 2) : 0 diff --git a/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml b/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml index 406d4c900..143ab7cc4 100644 --- a/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml +++ b/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml @@ -32,25 +32,26 @@ DraggableDesktopWidget { readonly property bool isHidden: (shouldHideIdle || shouldHideEmpty) && !DesktopWidgetRegistry.editMode visible: !isHidden - // CavaService registration for visualizer - readonly property string cavaComponentId: "desktopmediaplayer:" + (root.screen ? root.screen.name : "unknown") + // SpectrumService registration for visualizer + readonly property string spectrumComponentId: "desktopmediaplayer:" + (root.screen ? root.screen.name : "unknown") + readonly property bool needsSpectrum: root.shouldShowVisualizer && !root.isHidden - onShouldShowVisualizerChanged: { - if (root.shouldShowVisualizer) { - CavaService.registerComponent(root.cavaComponentId); + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); } else { - CavaService.unregisterComponent(root.cavaComponentId); + SpectrumService.unregisterComponent(root.spectrumComponentId); } } Component.onCompleted: { - if (root.shouldShowVisualizer) { - CavaService.registerComponent(root.cavaComponentId); + if (root.needsSpectrum) { + SpectrumService.registerComponent(root.spectrumComponentId); } } Component.onDestruction: { - CavaService.unregisterComponent(root.cavaComponentId); + SpectrumService.unregisterComponent(root.spectrumComponentId); } readonly property bool showPrev: hasPlayer && MediaService.canGoPrevious @@ -81,7 +82,7 @@ DraggableDesktopWidget { anchors.bottomMargin: 0 z: 0 clip: true - active: shouldShowVisualizer + active: needsSpectrum layer.enabled: true layer.smooth: true layer.effect: MultiEffect { @@ -113,7 +114,7 @@ DraggableDesktopWidget { id: linearComponent NLinearSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.5 } @@ -123,7 +124,7 @@ DraggableDesktopWidget { id: mirroredComponent NMirroredSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.5 } @@ -133,7 +134,7 @@ DraggableDesktopWidget { id: waveComponent NWaveSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.5 } diff --git a/Modules/DesktopWidgets/Widgets/DesktopSystemStat.qml b/Modules/DesktopWidgets/Widgets/DesktopSystemStat.qml index 52293c5be..b02bad06d 100644 --- a/Modules/DesktopWidgets/Widgets/DesktopSystemStat.qml +++ b/Modules/DesktopWidgets/Widgets/DesktopSystemStat.qml @@ -198,7 +198,9 @@ DraggableDesktopWidget { color2: root.color2 fill: true updateInterval: root.graphUpdateInterval + strokeWidth: 1.5 * Style.uiScaleRatio * root.widgetScale animateScale: root.statType === "Network" + antialiasing: 0.5 * root.widgetScale } } diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index f265c72b3..0dc725ff4 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -67,6 +67,7 @@ Loader { target: DesktopEntries.applications function onValuesChanged() { root.iconRevision++; + root._desktopEntryIdCache = {}; updateDockApps(); } } @@ -80,8 +81,10 @@ Loader { readonly property int showDelay: 100 readonly property int hideAnimationDuration: Math.max(0, Math.round(Style.animationFast / (Settings.data.dock.animationSpeed || 1.0))) readonly property int showAnimationDuration: Math.max(0, Math.round(Style.animationFast / (Settings.data.dock.animationSpeed || 1.0))) - readonly property int peekHeight: 1 - readonly property int indicatorThickness: 3 + readonly property int peekThickness: 1 + readonly property int indicatorThickness: Settings.data.dock.indicatorThickness || 3 + readonly property string indicatorColorKey: Settings.data.dock.indicatorColor || "primary" + readonly property real indicatorOpacity: Settings.data.dock.indicatorOpacity !== undefined ? Settings.data.dock.indicatorOpacity : 0.6 readonly property int iconSize: Math.round(12 + 24 * (Settings.data.dock.size ?? 1)) readonly property int floatingMargin: Settings.data.dock.floatingRatio * Style.marginL readonly property int maxWidth: modelData ? modelData.width * 0.8 : 1000 @@ -100,10 +103,18 @@ Loader { readonly property real barMarginH: Settings.data.bar.floating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0 readonly property real barMarginV: Settings.data.bar.floating ? Math.ceil(Settings.data.bar.marginVertical) : 0 readonly property int barHeight: Style.getBarHeightForScreen(modelData?.name) + readonly property bool staticPanelOpen: { + if (!isStaticMode) + return false; + var panel = getStaticDockPanel(); + if (panel && panel.isPanelOpen !== undefined) + return panel.isPanelOpen; + return false; + } readonly property int peekEdgeLength: { const edgeSize = isVertical ? Math.round(modelData?.height || maxHeight) : Math.round(modelData?.width || maxWidth); - const minLength = Math.max(1, Math.round(edgeSize * ((isStaticMode && Settings.data.dock.showFrameIndicator && Settings.data.bar.barType === "framed" && hasBar) ? 0.1 : 0.25))); - return Math.max(minLength, frameIndicatorLength); + const minLength = Math.max(1, Math.round(edgeSize * (Settings.data.dock.showDockIndicator ? 0.1 : 0.25))); + return Math.max(minLength, dockIndicatorLength); } readonly property int peekCenterOffsetX: { if (isVertical) @@ -139,16 +150,14 @@ Loader { } return Math.max(0, Math.round((edgeSize - peekEdgeLength) / 2)); } - readonly property bool showFrameIndicator: { - if (!isStaticMode || !Settings.data.dock.showFrameIndicator || Settings.data.bar.barType !== "framed" || !hasBar) + readonly property bool showDockIndicator: { + if (!Settings.data.dock.showDockIndicator || (!autoHide && !isStaticMode) || !hidden) return false; - var panel = getStaticDockPanel(); - if (panel && panel.isPanelOpen !== undefined) - return !panel.isPanelOpen; - return hidden; + return !staticPanelOpen; } readonly property int dockItemCount: dockApps.length + (Settings.data.dock.showLauncherIcon ? 1 : 0) - readonly property int frameIndicatorLength: { + readonly property bool indicatorVisible: showDockIndicator && dockIndicatorLength > 0 + readonly property int dockIndicatorLength: { if (dockItemCount <= 0) return 0; const spacing = Style.marginS; @@ -313,7 +322,40 @@ Loader { if (!appId || !pinnedApps || pinnedApps.length === 0) return false; const normalizedId = normalizeAppId(appId); - return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId); + // Direct match + if (pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId)) + return true; + // Resolve via desktop entry lookup (handles StartupWMClass != .desktop filename) + const resolved = resolveToDesktopEntryId(appId); + if (resolved !== appId) { + const normalizedResolved = normalizeAppId(resolved); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedResolved); + } + return false; + } + + // Desktop entry ID resolution cache (cleared when DesktopEntries change) + property var _desktopEntryIdCache: ({}) + + // Resolve a toplevel appId to its canonical .desktop entry ID via heuristic lookup. + // This handles cases where the Wayland appId (e.g. "zen" from StartupWMClass) + // differs from the .desktop filename (e.g. "zen-browser-bin"). + function resolveToDesktopEntryId(appId) { + if (!appId) + return appId; + if (_desktopEntryIdCache.hasOwnProperty(appId)) + return _desktopEntryIdCache[appId]; + try { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.id) { + _desktopEntryIdCache[appId] = entry.id; + return entry.id; + } + } + } catch (e) {} + _desktopEntryIdCache[appId] = appId; + return appId; } // Helper function to get app name from desktop entry @@ -489,7 +531,17 @@ Loader { function pushPinned() { pinnedApps.forEach(pinnedAppId => { // Find all running instances of this pinned app using robust matching - const matchingToplevels = runningApps.filter(app => app && normalizeAppId(app.appId) === normalizeAppId(pinnedAppId)); + // Also resolve toplevel appId via desktop entry lookup to handle + // StartupWMClass != .desktop filename (e.g. zen -> zen-browser-bin) + const normalizedPinned = normalizeAppId(pinnedAppId); + const matchingToplevels = runningApps.filter(app => { + if (!app) + return false; + if (normalizeAppId(app.appId) === normalizedPinned) + return true; + const resolved = resolveToDesktopEntryId(app.appId); + return resolved !== app.appId && normalizeAppId(resolved) === normalizedPinned; + }); if (matchingToplevels.length > 0) { // Add all running instances as pinned-running @@ -617,13 +669,7 @@ Loader { interval: showDelay onTriggered: { if (autoHide) { - if (isStaticMode) { - if (dockItemCount <= 0) - return; - const panel = getStaticDockPanel(); - if (panel && !panel.isPanelOpen) - panel.open(); - } else { + if (!isStaticMode) { dockLoaded = true; // Load dock immediately } hidden = false; // Then trigger show animation @@ -648,7 +694,7 @@ Loader { // PEEK WINDOW Loader { - active: (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name)) && (autoHide || isStaticMode) + active: (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name)) sourceComponent: PanelWindow { id: peekWindow @@ -662,32 +708,15 @@ Loader { focusable: false color: "transparent" - // When bar is at same edge, position peek window past the bar so it receives mouse events - margins.top: isVertical ? peekCenterOffsetY : (dockPosition === "top" && barAtSameEdge && !showFrameIndicator ? (barHeight + (Settings.data.bar.floating ? Settings.data.bar.marginVertical : 0)) : 0) - margins.bottom: dockPosition === "bottom" && barAtSameEdge && !showFrameIndicator ? (barHeight + (Settings.data.bar.floating ? Settings.data.bar.marginVertical : 0)) : 0 - margins.left: !isVertical ? peekCenterOffsetX : (dockPosition === "left" && barAtSameEdge && !showFrameIndicator ? (barHeight + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal : 0)) : 0) - margins.right: dockPosition === "right" && barAtSameEdge && !showFrameIndicator ? (barHeight + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal : 0)) : 0 + margins.top: peekCenterOffsetY + margins.left: peekCenterOffsetX WlrLayershell.namespace: "noctalia-dock-peek-" + (screen?.name || "unknown") + WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.exclusionMode: ExclusionMode.Ignore // Larger peek area when bar is at same edge, normal 1px otherwise - implicitHeight: isVertical ? peekEdgeLength : ((showFrameIndicator || barAtSameEdge) ? indicatorThickness : peekHeight) - implicitWidth: isVertical ? ((showFrameIndicator || barAtSameEdge) ? indicatorThickness : peekHeight) : peekEdgeLength - - Rectangle { - anchors.fill: parent - radius: indicatorThickness - color: Qt.alpha(Color.mPrimary, 0.6) - opacity: showFrameIndicator && frameIndicatorLength > 0 ? 1 : 0 - visible: opacity > 0 - - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - easing.type: Easing.InOutQuad - } - } - } + implicitHeight: isVertical ? peekEdgeLength : peekThickness + implicitWidth: isVertical ? peekThickness : peekEdgeLength MouseArea { id: peekArea @@ -696,7 +725,7 @@ Loader { onEntered: { peekHovered = true; - if (isStaticMode && !autoHide) { + if (isStaticMode) { if (dockItemCount <= 0) return; const panel = getStaticDockPanel(); @@ -704,10 +733,6 @@ Loader { panel.open(); return; } - if (isStaticMode) { - showTimer.start(); - return; - } if (hidden) { showTimer.start(); } @@ -724,6 +749,85 @@ Loader { } } + // DOCK INDICATOR WINDOW + Loader { + active: (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name)) + + sourceComponent: PanelWindow { + id: dockIndicatorWindow + + screen: modelData + // Dynamic anchors based on dock position + anchors.top: dockPosition === "top" || isVertical + anchors.bottom: dockPosition === "bottom" + anchors.left: dockPosition === "left" || !isVertical + anchors.right: dockPosition === "right" + focusable: false + color: "transparent" + + property real targetIndicatorOffsetX: peekCenterOffsetX + property real targetIndicatorOffsetY: peekCenterOffsetY + property real animatedIndicatorOffsetX: targetIndicatorOffsetX + property real animatedIndicatorOffsetY: targetIndicatorOffsetY + + onTargetIndicatorOffsetXChanged: animatedIndicatorOffsetX = targetIndicatorOffsetX + onTargetIndicatorOffsetYChanged: animatedIndicatorOffsetY = targetIndicatorOffsetY + + Behavior on animatedIndicatorOffsetX { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + + Behavior on animatedIndicatorOffsetY { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + + margins.top: animatedIndicatorOffsetY + margins.left: animatedIndicatorOffsetX + + WlrLayershell.namespace: "noctalia-dock-indicator-" + (screen?.name || "unknown") + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + implicitHeight: isVertical ? peekEdgeLength : indicatorThickness + implicitWidth: isVertical ? indicatorThickness : peekEdgeLength + + Behavior on implicitWidth { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + Behavior on implicitHeight { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + + Rectangle { + id: indicatorRect + anchors.fill: parent + radius: indicatorThickness + color: Qt.alpha(Color.resolveColorKey(indicatorColorKey), indicatorOpacity) + opacity: indicatorVisible ? 1 : 0 + visible: opacity > 0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.InOutQuad + } + } + } + } + } + // Force dock reload when position changes to fix anchor/layout issues // Force dock reload when position/mode changes to fix anchor/layout issues property bool _reloading: false @@ -759,6 +863,19 @@ Loader { WlrLayershell.namespace: "noctalia-dock-" + (screen?.name || "unknown") WlrLayershell.exclusionMode: exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore + // Blur behind dock (User Interface → Blur behind) + BackgroundEffect.blurRegion: Settings.data.general.enableBlurBehind ? dockBlurRegion : null + Region { + id: dockBlurRegion + Region { + x: Math.round(dockContainerWrapper.mapFromItem(dockContent.dockContainer, 0, 0).x) + y: Math.round(dockContainerWrapper.mapFromItem(dockContent.dockContainer, 0, 0).y) + width: Math.round(dockContent.dockContainer.width) + height: Math.round(dockContent.dockContainer.height) + radius: Style.radiusL + } + } + implicitWidth: dockContainerWrapper.width implicitHeight: dockContainerWrapper.height @@ -792,7 +909,7 @@ Loader { readonly property int extraRight: (!isVertical && !exclusive && barOnRight) ? barHeight : 0 // Add +2 buffer for fractional scaling issues - width: dockContent.dockContainer.width + extraLeft + extraRight + (root.isVertical ? 2 : Style.margin2XL * 6) + width: dockContent.dockContainer.width + extraLeft + extraRight + 2 height: dockContent.dockContainer.height + extraTop + extraBottom + 2 anchors.horizontalCenter: isVertical ? undefined : parent.horizontalCenter diff --git a/Modules/Dock/DockContent.qml b/Modules/Dock/DockContent.qml index 4006204b7..137ae6879 100644 --- a/Modules/Dock/DockContent.qml +++ b/Modules/Dock/DockContent.qml @@ -19,7 +19,7 @@ Item { required property int extraRight property alias dockContainer: dockContainer readonly property bool isStaticMode: Settings.data.dock.dockType === "static" - readonly property string tooltipDirection: dockRoot.dockPosition === "top" ? "bottom" : "top" + readonly property string tooltipDirection: dockRoot.dockPosition === "left" ? "right" : (dockRoot.dockPosition === "right" ? "left" : (dockRoot.dockPosition === "top" ? "bottom" : "top")) Rectangle { id: dockContainer diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index 7f18d6309..97389d88a 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -505,7 +505,7 @@ PopupWindow { if (appId) { root.toggleAppPin(appId); } - closeAndReset(); + Qt.callLater(() => closeAndReset()); } function handleClose(targetToplevel) { @@ -582,6 +582,7 @@ PopupWindow { Rectangle { anchors.fill: parent + anchors.margins: border.width color: Color.mSurfaceVariant radius: Style.radiusS border.color: Color.mOutline diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index b3a95cd1d..ad02d2428 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -17,13 +17,13 @@ Loader { active: false // Track if the visualizer should be shown (lockscreen active + media playing + non-compact mode) - readonly property bool needsCava: root.active && !Settings.data.general.compactLockScreen && Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none" + readonly property bool needsSpectrum: root.active && !Settings.data.general.compactLockScreen && Settings.data.audio.visualizerType !== "" && Settings.data.audio.visualizerType !== "none" onActiveChanged: { - if (root.active && root.needsCava) { - CavaService.registerComponent("lockscreen"); + if (root.active && root.needsSpectrum) { + SpectrumService.registerComponent("lockscreen"); } else { - CavaService.unregisterComponent("lockscreen"); + SpectrumService.unregisterComponent("lockscreen"); } if (root.active) { @@ -33,11 +33,11 @@ Loader { } } - onNeedsCavaChanged: { - if (root.needsCava) { - CavaService.registerComponent("lockscreen"); + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent("lockscreen"); } else { - CavaService.unregisterComponent("lockscreen"); + SpectrumService.unregisterComponent("lockscreen"); } } @@ -47,7 +47,7 @@ Loader { } Component.onDestruction: { - CavaService.unregisterComponent("lockscreen"); + SpectrumService.unregisterComponent("lockscreen"); LockKeysService.unregisterComponent("lockscreen"); } @@ -125,8 +125,10 @@ Loader { anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton - onPositionChanged: { - if (passwordInput) { + onEntered: { + // Avoid repeatedly forcing focus on every mouse move. + // This can churn text-input surface state during monitor/suspend transitions. + if (passwordInput && !passwordInput.activeFocus) { passwordInput.forceActiveFocus(); } } diff --git a/Modules/LockScreen/LockScreenBackground.qml b/Modules/LockScreen/LockScreenBackground.qml index 8494a34f8..378b2b3a4 100644 --- a/Modules/LockScreen/LockScreenBackground.qml +++ b/Modules/LockScreen/LockScreenBackground.qml @@ -111,7 +111,7 @@ Item { Image { id: lockBgImage - visible: source !== "" && Settings.data.wallpaper.enabled && !Settings.data.wallpaper.useSolidColor + visible: source !== "" && Settings.data.wallpaper.enabled && !Settings.data.wallpaper.useSolidColor && (!PowerProfileService.noctaliaPerformanceMode || !Settings.data.noctaliaPerformance.disableWallpaper) anchors.fill: parent fillMode: Image.PreserveAspectCrop source: resolvedWallpaperPath @@ -142,19 +142,19 @@ Item { gradient: Gradient { GradientStop { position: 0.0 - color: Qt.alpha(Color.mShadow, 0.8) - } - GradientStop { - position: 0.3 color: Qt.alpha(Color.mShadow, 0.4) } + GradientStop { + position: 0.3 + color: Qt.alpha(Color.mShadow, 0.2) + } GradientStop { position: 0.7 - color: Qt.alpha(Color.mShadow, 0.5) + color: Qt.alpha(Color.mShadow, 0.25) } GradientStop { position: 1.0 - color: Qt.alpha(Color.mShadow, 0.9) + color: Qt.alpha(Color.mShadow, 0.5) } } } diff --git a/Modules/LockScreen/LockScreenHeader.qml b/Modules/LockScreen/LockScreenHeader.qml index b1b169aca..d75b37c6c 100644 --- a/Modules/LockScreen/LockScreenHeader.qml +++ b/Modules/LockScreen/LockScreenHeader.qml @@ -125,6 +125,7 @@ Rectangle { "es": "dddd, d 'de' MMMM", "fr": "dddd d MMMM", "hu": "dddd, MMMM d.", + "it": "dddd d MMMM", "ja": "yyyy年M月d日 dddd", "ko": "yyyy년 M월 d일 dddd", "ku": "dddd, dê MMMM", diff --git a/Modules/LockScreen/LockScreenPanel.qml b/Modules/LockScreen/LockScreenPanel.qml index 35801fddc..e62fe89b7 100644 --- a/Modules/LockScreen/LockScreenPanel.qml +++ b/Modules/LockScreen/LockScreenPanel.qml @@ -88,6 +88,9 @@ Item { case "reboot": CompositorService.reboot(); break; + case "userspaceReboot": + CompositorService.userspaceReboot(); + break; case "shutdown": CompositorService.shutdown(); break; @@ -265,7 +268,7 @@ Item { z: 0 sourceComponent: NLinearSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 } @@ -278,7 +281,7 @@ Item { z: 0 sourceComponent: NMirroredSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 } @@ -291,7 +294,7 @@ Item { z: 0 sourceComponent: NWaveSpectrum { anchors.fill: parent - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 } @@ -342,6 +345,124 @@ Item { elide: Text.ElideRight } } + + // Media controls (when enabled) + RowLayout { + spacing: Style.marginXS + visible: Settings.data.general.enableLockScreenMediaControls + Layout.alignment: Qt.AlignHCenter + + Rectangle { + width: 28 + height: 28 + radius: Math.min(Style.radiusL, width / 2) + color: prevButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mOnSurface, 0.1) + visible: MediaService.canGoPrevious + + NIcon { + anchors.centerIn: parent + icon: "media-prev" + pointSize: Style.fontSizeM + color: prevButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurface + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + id: prevButtonArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: MediaService.canGoPrevious ? MediaService.previous() : {} + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + Rectangle { + width: 32 + height: 32 + radius: Math.min(Style.radiusL, width / 2) + color: playPauseButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mOnSurface, 0.15) + visible: MediaService.canPlay || MediaService.canPause + + NIcon { + anchors.centerIn: parent + icon: MediaService.isPlaying ? "media-pause" : "media-play" + pointSize: Style.fontSizeL + color: playPauseButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurface + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + id: playPauseButtonArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {} + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + Rectangle { + width: 28 + height: 28 + radius: Math.min(Style.radiusL, width / 2) + color: nextButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mOnSurface, 0.1) + visible: MediaService.canGoNext + + NIcon { + anchors.centerIn: parent + icon: "media-next" + pointSize: Style.fontSizeM + color: nextButtonArea.containsMouse ? Color.mOnPrimary : Color.mOnSurface + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + MouseArea { + id: nextButtonArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: MediaService.canGoNext ? MediaService.next() : {} + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + } } } diff --git a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml index 53132d432..9665b1860 100644 --- a/Modules/MainScreen/Backgrounds/AllBackgrounds.qml +++ b/Modules/MainScreen/Backgrounds/AllBackgrounds.qml @@ -38,12 +38,13 @@ Item { // Enable layer caching to prevent continuous re-rendering layer.enabled: true - opacity: Settings.data.ui.panelBackgroundOpacity + opacity: Color.panelBackgroundOpacity Shape { id: unifiedBackgroundsShape anchors.fill: parent preferredRendererType: Shape.CurveRenderer + asynchronous: true enabled: false Component.onCompleted: { @@ -105,12 +106,13 @@ Item { anchors.fill: parent layer.enabled: true - opacity: Settings.data.ui.panelBackgroundOpacity + opacity: Color.panelBackgroundOpacity Shape { id: panelBackgroundsShape anchors.fill: parent preferredRendererType: Shape.CurveRenderer + asynchronous: true enabled: false /** @@ -159,6 +161,7 @@ Item { id: barBackgroundShape anchors.fill: parent preferredRendererType: Shape.CurveRenderer + asynchronous: true enabled: false BarBackground { diff --git a/Modules/MainScreen/Backgrounds/BarBackground.qml b/Modules/MainScreen/Backgrounds/BarBackground.qml index 890f1cd9d..18f5e2538 100644 --- a/Modules/MainScreen/Backgrounds/BarBackground.qml +++ b/Modules/MainScreen/Backgrounds/BarBackground.qml @@ -77,13 +77,18 @@ ShapePath { readonly property bool shouldFlatten: bar ? ShapeCornerHelper.shouldFlatten(barWidth, barHeight, radius) : false readonly property real effectiveRadius: shouldFlatten ? (bar ? ShapeCornerHelper.getFlattenedRadius(Math.min(barWidth, barHeight), radius) : 0) : radius + // Minimum safe arc radius — prevents zero-displacement zero-radius PathArcs + // that crash qTriangulate in CurveRenderer. 0.01px is sub-pixel and invisible. + readonly property real _minR: 0.01 + // Helper function for getting corner radius based on state function getCornerRadius(cornerState) { - // State -1 = no radius (flat corner) + // State -1 = flat corner — use minimum safe radius instead of 0 + // to prevent degenerate PathArc (zero displacement + zero radius) if (cornerState === -1) - return 0; - // All other states use effectiveRadius - return effectiveRadius; + return _minR; + // All other states use effectiveRadius (clamped to safe minimum) + return Math.max(_minR, effectiveRadius); } // Per-corner multipliers and radii based on bar's corner states (handle null bar) @@ -103,13 +108,18 @@ ShapePath { readonly property real blMultY: bar ? ShapeCornerHelper.getMultY(bar.bottomLeftCornerState) : 1 readonly property real blRadius: bar ? getCornerRadius(bar.bottomLeftCornerState) : 0 - // Extend bar background beyond screen edges where both adjacent corners are flat, - // to prevent CurveRenderer antialiasing artifacts on screen-flush edges + // True when the bar path has valid, non-degenerate geometry to render. + // Mirrors PanelBackground.isRenderable — prevents CurveRenderer crash on zero-area paths. + readonly property bool isRenderable: bar !== null && shouldShow && (isFramed ? (screenWidth > 0 && screenHeight > 0) : (barWidth > 0 && barHeight > 0)) + + // Edge overshoot: extend bar background beyond screen edges where both adjacent + // corners are flat (state -1) to prevent CurveRenderer antialiasing artifacts. + // Uses corner state checks instead of radius === 0 since flat corners now have _minR. readonly property real screenEdgeOvershoot: 2 - readonly property real topEdgeOvs: (!isFramed && shouldShow && tlRadius === 0 && trRadius === 0 && barMappedPos.y <= 0) ? -screenEdgeOvershoot : 0 - readonly property real bottomEdgeOvs: (!isFramed && shouldShow && blRadius === 0 && brRadius === 0 && (barMappedPos.y + barHeight) >= screenHeight) ? screenEdgeOvershoot : 0 - readonly property real leftEdgeOvs: (!isFramed && shouldShow && tlRadius === 0 && blRadius === 0 && barMappedPos.x <= 0) ? -screenEdgeOvershoot : 0 - readonly property real rightEdgeOvs: (!isFramed && shouldShow && trRadius === 0 && brRadius === 0 && (barMappedPos.x + barWidth) >= screenWidth) ? screenEdgeOvershoot : 0 + readonly property real topEdgeOvs: (!isFramed && shouldShow && bar && bar.topLeftCornerState === -1 && bar.topRightCornerState === -1 && barMappedPos.y <= 0) ? -screenEdgeOvershoot : 0 + readonly property real bottomEdgeOvs: (!isFramed && shouldShow && bar && bar.bottomLeftCornerState === -1 && bar.bottomRightCornerState === -1 && (barMappedPos.y + barHeight) >= screenHeight) ? screenEdgeOvershoot : 0 + readonly property real leftEdgeOvs: (!isFramed && shouldShow && bar && bar.topLeftCornerState === -1 && bar.bottomLeftCornerState === -1 && barMappedPos.x <= 0) ? -screenEdgeOvershoot : 0 + readonly property real rightEdgeOvs: (!isFramed && shouldShow && bar && bar.topRightCornerState === -1 && bar.bottomRightCornerState === -1 && (barMappedPos.x + barWidth) >= screenWidth) ? screenEdgeOvershoot : 0 // Auto-hide opacity factor for background fade property real opacityFactor: (bar && bar.isHidden) ? 0 : 1 @@ -124,160 +134,150 @@ ShapePath { // ShapePath configuration strokeWidth: -1 // No stroke, fill only - fillColor: Qt.rgba(backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a * opacityFactor) + fillColor: isRenderable ? Qt.rgba(backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a * opacityFactor) : "transparent" fillRule: isFramed ? ShapePath.OddEvenFill : ShapePath.WindingFill - // Starting position - // In framed mode, we start at (0,0) to draw the screen rectangle first - startX: isFramed ? 0 : (barMappedPos.x + leftEdgeOvs + tlRadius * tlMultX) - startY: isFramed ? 0 : (barMappedPos.y + topEdgeOvs) + // Starting position — falls back to off-screen when not renderable so that + // all subsequent path elements form a valid non-degenerate off-screen square. + // Each edge is split between PathLine and PathArc so no arc has zero displacement, + // preventing CurveRenderer triangulation crashes on degenerate arcs. + // For framed mode the outer path is a full-screen rectangle; _minR offsets at each + // corner prevent zero-displacement zero-radius arcs that crash qTriangulate. + startX: isRenderable ? (isFramed ? _minR : (barMappedPos.x + leftEdgeOvs + tlRadius * tlMultX)) : -0.75 + startY: isRenderable ? (isFramed ? 0 : (barMappedPos.y + topEdgeOvs)) : -1 // ========== PATH DEFINITION ========== // 1. Main Bar / Outer Screen Rectangle + // When !isRenderable all elements use fallback coordinates forming a valid 1×1 + // off-screen square with non-degenerate arcs so CurveRenderer never receives + // a zero-area, bare-moveto, or zero-displacement arc path. PathLine { - x: { - if (!root.shouldShow) - return 0; - if (root.isFramed) - return root.screenWidth; - return root.barMappedPos.x + root.barWidth + root.rightEdgeOvs - root.trRadius * root.trMultX; - } - y: root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs) - } - - // Bar top-right corner (only if not framed) - PathArc { - x: root.isFramed ? (root.shouldShow ? root.screenWidth : 0) : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs) - y: root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs + root.trRadius * root.trMultY) - radiusX: root.isFramed ? 0 : root.trRadius - radiusY: root.isFramed ? 0 : root.trRadius - direction: ShapeCornerHelper.getArcDirection(root.trMultX, root.trMultY) - } - - PathLine { - x: root.isFramed ? (root.shouldShow ? root.screenWidth : 0) : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs) - y: { - if (!root.shouldShow) - return 0; - if (root.isFramed) - return root.screenHeight; - return root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs - root.brRadius * root.brMultY; - } - } - - // Bar bottom-right corner (only if not framed) - PathArc { - x: root.isFramed ? (root.shouldShow ? root.screenWidth : 0) : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs - root.brRadius * root.brMultX) - y: root.isFramed ? (root.shouldShow ? root.screenHeight : 0) : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs) - radiusX: root.isFramed ? 0 : root.brRadius - radiusY: root.isFramed ? 0 : root.brRadius - direction: ShapeCornerHelper.getArcDirection(root.brMultX, root.brMultY) - } - - PathLine { - x: { - if (!root.shouldShow) - return 0; - if (root.isFramed) - return 0; - return root.barMappedPos.x + root.leftEdgeOvs + root.blRadius * root.blMultX; - } - y: root.isFramed ? (root.shouldShow ? root.screenHeight : 0) : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs) - } - - // Bar bottom-left corner (only if not framed) - PathArc { - x: root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs) - y: root.isFramed ? (root.shouldShow ? root.screenHeight : 0) : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs - root.blRadius * root.blMultY) - radiusX: root.isFramed ? 0 : root.blRadius - radiusY: root.isFramed ? 0 : root.blRadius - direction: ShapeCornerHelper.getArcDirection(root.blMultX, root.blMultY) - } - - PathLine { - x: root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs) - y: { - if (!root.shouldShow) - return 0; - if (root.isFramed) - return 0; - return root.barMappedPos.y + root.topEdgeOvs + root.tlRadius * root.tlMultY; - } - } - - // Bar top-left corner (only if not framed, back to start) - PathArc { - x: root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs + root.tlRadius * root.tlMultX) - y: root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs) - radiusX: root.isFramed ? 0 : root.tlRadius - radiusY: root.isFramed ? 0 : root.tlRadius - direction: ShapeCornerHelper.getArcDirection(root.tlMultX, root.tlMultY) - } - - // 2. Inner Hole for Framed Mode (Clockwise) - PathMove { - x: (root.isFramed && root.shouldShow) ? (root.holeX + root.frameRadius) : root.startX - y: (root.isFramed && root.shouldShow) ? root.holeY : root.startY - } - - // Top edge - PathLine { - x: (root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth - root.frameRadius) : ((root.isFramed && root.shouldShow) ? (root.holeX + root.frameRadius) : root.startX) - y: (root.isFramed && root.shouldShow) ? root.holeY : ((root.isFramed && root.shouldShow) ? root.holeY : root.startY) + x: root.isRenderable ? (root.isFramed ? (root.screenWidth - root._minR) : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs - root.trRadius * root.trMultX)) : 0 + y: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs)) : -1 } // Top-right corner PathArc { - x: (root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth) : ((root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth - root.frameRadius) : root.startX) - y: (root.isFramed && root.shouldShow) ? (root.holeY + root.frameRadius) : ((root.isFramed && root.shouldShow) ? root.holeY : root.startY) - radiusX: (root.isFramed && root.shouldShow) ? root.frameRadius : 0 - radiusY: (root.isFramed && root.shouldShow) ? root.frameRadius : 0 + x: root.isRenderable ? (root.isFramed ? root.screenWidth : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs)) : 0 + y: root.isRenderable ? (root.isFramed ? root._minR : (root.barMappedPos.y + root.topEdgeOvs + root.trRadius * root.trMultY)) : -0.75 + radiusX: root.isRenderable ? (root.isFramed ? root._minR : root.trRadius) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root._minR : root.trRadius) : 0 + direction: ShapeCornerHelper.getArcDirection(root.trMultX, root.trMultY) + } + + PathLine { + x: root.isRenderable ? (root.isFramed ? root.screenWidth : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs)) : 0 + y: root.isRenderable ? (root.isFramed ? (root.screenHeight - root._minR) : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs - root.brRadius * root.brMultY)) : 0 + } + + // Bottom-right corner + PathArc { + x: root.isRenderable ? (root.isFramed ? (root.screenWidth - root._minR) : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs - root.brRadius * root.brMultX)) : -0.25 + y: root.isRenderable ? (root.isFramed ? root.screenHeight : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs)) : 0 + radiusX: root.isRenderable ? (root.isFramed ? root._minR : root.brRadius) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root._minR : root.brRadius) : 0 + direction: ShapeCornerHelper.getArcDirection(root.brMultX, root.brMultY) + } + + PathLine { + x: root.isRenderable ? (root.isFramed ? root._minR : (root.barMappedPos.x + root.leftEdgeOvs + root.blRadius * root.blMultX)) : -1 + y: root.isRenderable ? (root.isFramed ? root.screenHeight : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs)) : 0 + } + + // Bottom-left corner + PathArc { + x: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs)) : -1 + y: root.isRenderable ? (root.isFramed ? (root.screenHeight - root._minR) : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs - root.blRadius * root.blMultY)) : -0.25 + radiusX: root.isRenderable ? (root.isFramed ? root._minR : root.blRadius) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root._minR : root.blRadius) : 0 + direction: ShapeCornerHelper.getArcDirection(root.blMultX, root.blMultY) + } + + PathLine { + x: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs)) : -1 + y: root.isRenderable ? (root.isFramed ? root._minR : (root.barMappedPos.y + root.topEdgeOvs + root.tlRadius * root.tlMultY)) : -1 + } + + // Top-left corner (back to start) + PathArc { + x: root.isRenderable ? (root.isFramed ? root._minR : (root.barMappedPos.x + root.leftEdgeOvs + root.tlRadius * root.tlMultX)) : -0.75 + y: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs)) : -1 + radiusX: root.isRenderable ? (root.isFramed ? root._minR : root.tlRadius) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root._minR : root.tlRadius) : 0 + direction: ShapeCornerHelper.getArcDirection(root.tlMultX, root.tlMultY) + } + + // 2. Inner Hole for Framed Mode (Clockwise) + // When !isFramed, draws a tiny 1x1 rectangle inside the bar as a non-degenerate WindingFill + // no-op to prevent a zero-area degenerate subpath crashing qTriangulate. + // Note: an exact duplicate of the outer path cannot be used here because Qt's CurveRenderer + // has issues with exactly coincident subpaths, causing the fill to not render on some systems. + // When !isRenderable, falls back to a valid 1×1 off-screen square at (-3,-3)→(-2,-2). + readonly property real _nhX: barMappedPos.x + barWidth / 2 + readonly property real _nhY: barMappedPos.y + barHeight / 2 + PathMove { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.frameRadius) : (root._nhX + 0.25)) : -2.75 + y: root.isRenderable ? (root.isFramed ? root.holeY : root._nhY) : -3 + } + + // Top edge + PathLine { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth - root.frameRadius) : (root._nhX + 1)) : -2 + y: root.isRenderable ? (root.isFramed ? root.holeY : root._nhY) : -3 + } + + // Top-right corner + PathArc { + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth) : (root._nhX + 1)) : -2 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.frameRadius) : (root._nhY + 0.25)) : -2.75 + radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 direction: PathArc.Clockwise } // Right edge PathLine { - x: (root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth) : root.startX - y: (root.isFramed && root.shouldShow) ? (root.holeY + root.holeHeight - root.frameRadius) : root.startY + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth) : (root._nhX + 1)) : -2 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight - root.frameRadius) : (root._nhY + 1)) : -2 } // Bottom-right corner PathArc { - x: (root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth - root.frameRadius) : root.startX - y: (root.isFramed && root.shouldShow) ? (root.holeY + root.holeHeight) : root.startY - radiusX: (root.isFramed && root.shouldShow) ? root.frameRadius : 0 - radiusY: (root.isFramed && root.shouldShow) ? root.frameRadius : 0 + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth - root.frameRadius) : (root._nhX + 0.75)) : -2.25 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight) : (root._nhY + 1)) : -2 + radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 direction: PathArc.Clockwise } // Bottom edge PathLine { - x: (root.isFramed && root.shouldShow) ? (root.holeX + root.frameRadius) : root.startX - y: (root.isFramed && root.shouldShow) ? (root.holeY + root.holeHeight) : root.startY + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.frameRadius) : root._nhX) : -3 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight) : (root._nhY + 1)) : -2 } // Bottom-left corner PathArc { - x: (root.isFramed && root.shouldShow) ? root.holeX : root.startX - y: (root.isFramed && root.shouldShow) ? (root.holeY + root.holeHeight - root.frameRadius) : root.startY - radiusX: (root.isFramed && root.shouldShow) ? root.frameRadius : 0 - radiusY: (root.isFramed && root.shouldShow) ? root.frameRadius : 0 + x: root.isRenderable ? (root.isFramed ? root.holeX : root._nhX) : -3 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight - root.frameRadius) : (root._nhY + 0.75)) : -2.25 + radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 direction: PathArc.Clockwise } // Left edge PathLine { - x: (root.isFramed && root.shouldShow) ? root.holeX : root.startX - y: (root.isFramed && root.shouldShow) ? (root.holeY + root.frameRadius) : root.startY + x: root.isRenderable ? (root.isFramed ? root.holeX : root._nhX) : -3 + y: root.isRenderable ? (root.isFramed ? (root.holeY + root.frameRadius) : root._nhY) : -3 } // Top-left corner (back to start) PathArc { - x: (root.isFramed && root.shouldShow) ? (root.holeX + root.frameRadius) : root.startX - y: (root.isFramed && root.shouldShow) ? root.holeY : root.startY - radiusX: (root.isFramed && root.shouldShow) ? root.frameRadius : 0 - radiusY: (root.isFramed && root.shouldShow) ? root.frameRadius : 0 + x: root.isRenderable ? (root.isFramed ? (root.holeX + root.frameRadius) : (root._nhX + 0.25)) : -2.75 + y: root.isRenderable ? (root.isFramed ? root.holeY : root._nhY) : -3 + radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 + radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0 direction: PathArc.Clockwise } } diff --git a/Modules/MainScreen/Backgrounds/PanelBackground.qml b/Modules/MainScreen/Backgrounds/PanelBackground.qml index 4ced652d0..5842b10bd 100644 --- a/Modules/MainScreen/Backgrounds/PanelBackground.qml +++ b/Modules/MainScreen/Backgrounds/PanelBackground.qml @@ -53,18 +53,24 @@ ShapePath { readonly property real panelY: panelBg ? panelBg.y : 0 readonly property real panelWidth: panelBg ? panelBg.width : 0 readonly property real panelHeight: panelBg ? panelBg.height : 0 + readonly property bool isRenderable: assignedPanel && panelBg && panelWidth > 0 && panelHeight > 0 // Flatten corners if panel is too small readonly property bool shouldFlatten: panelBg ? ShapeCornerHelper.shouldFlatten(panelWidth, panelHeight, radius) : false readonly property real effectiveRadius: shouldFlatten ? ShapeCornerHelper.getFlattenedRadius(Math.min(panelWidth, panelHeight), radius) : radius + // Minimum safe arc radius — prevents zero-displacement zero-radius PathArcs + // that crash qTriangulate in CurveRenderer. 0.01px is sub-pixel and invisible. + readonly property real _minR: 0.01 + // Helper function for getting corner radius based on state function getCornerRadius(cornerState) { - // State -1 = no radius (flat corner) + // State -1 = flat corner — use minimum safe radius instead of 0 + // to prevent degenerate PathArc (zero displacement + zero radius) if (cornerState === -1) - return 0; - // All other states use effectiveRadius - return effectiveRadius; + return _minR; + // All other states use effectiveRadius (clamped to safe minimum) + return Math.max(_minR, effectiveRadius); } // Per-corner multipliers and radii based on panelBg's corner states @@ -87,11 +93,13 @@ ShapePath { // ShapePath configuration strokeWidth: -1 // No stroke, fill only - // Starting position (top-left corner, after the arc) - startX: panelX + tlRadius * tlMultX - startY: panelY + // Start point - use tiny off-screen non-degenerate fallback when not renderable. + // Fallback forms a 1×1 off-screen square where each edge is split between a PathLine + // and a PathArc, ensuring no arc has zero displacement (which can crash qTriangulate). + startX: isRenderable ? (panelX + tlRadius * tlMultX) : -0.75 + startY: isRenderable ? panelY : -1 - fillColor: effectiveBackgroundColor + fillColor: isRenderable ? effectiveBackgroundColor : "transparent" // ========== PATH DEFINITION ========== // Draws a rectangle with potentially inverted corners @@ -99,61 +107,61 @@ ShapePath { // Top edge (moving right) PathLine { - relativeX: root.panelWidth - root.tlRadius * root.tlMultX - root.trRadius * root.trMultX + relativeX: root.isRenderable ? (root.panelWidth - root.tlRadius * root.tlMultX - root.trRadius * root.trMultX) : 0.75 relativeY: 0 } // Top-right corner arc PathArc { - relativeX: root.trRadius * root.trMultX - relativeY: root.trRadius * root.trMultY - radiusX: root.trRadius - radiusY: root.trRadius + relativeX: root.isRenderable ? (root.trRadius * root.trMultX) : 0 + relativeY: root.isRenderable ? (root.trRadius * root.trMultY) : 0.25 + radiusX: root.isRenderable ? root.trRadius : 0 + radiusY: root.isRenderable ? root.trRadius : 0 direction: ShapeCornerHelper.getArcDirection(root.trMultX, root.trMultY) } // Right edge (moving down) PathLine { relativeX: 0 - relativeY: root.panelHeight - root.trRadius * root.trMultY - root.brRadius * root.brMultY + relativeY: root.isRenderable ? (root.panelHeight - root.trRadius * root.trMultY - root.brRadius * root.brMultY) : 0.75 } // Bottom-right corner arc PathArc { - relativeX: -root.brRadius * root.brMultX - relativeY: root.brRadius * root.brMultY - radiusX: root.brRadius - radiusY: root.brRadius + relativeX: root.isRenderable ? (-root.brRadius * root.brMultX) : -0.25 + relativeY: root.isRenderable ? (root.brRadius * root.brMultY) : 0 + radiusX: root.isRenderable ? root.brRadius : 0 + radiusY: root.isRenderable ? root.brRadius : 0 direction: ShapeCornerHelper.getArcDirection(root.brMultX, root.brMultY) } // Bottom edge (moving left) PathLine { - relativeX: -(root.panelWidth - root.brRadius * root.brMultX - root.blRadius * root.blMultX) + relativeX: root.isRenderable ? (-(root.panelWidth - root.brRadius * root.brMultX - root.blRadius * root.blMultX)) : -0.75 relativeY: 0 } // Bottom-left corner arc PathArc { - relativeX: -root.blRadius * root.blMultX - relativeY: -root.blRadius * root.blMultY - radiusX: root.blRadius - radiusY: root.blRadius + relativeX: root.isRenderable ? (-root.blRadius * root.blMultX) : 0 + relativeY: root.isRenderable ? (-root.blRadius * root.blMultY) : -0.25 + radiusX: root.isRenderable ? root.blRadius : 0 + radiusY: root.isRenderable ? root.blRadius : 0 direction: ShapeCornerHelper.getArcDirection(root.blMultX, root.blMultY) } // Left edge (moving up) - closes the path back to start PathLine { relativeX: 0 - relativeY: -(root.panelHeight - root.blRadius * root.blMultY - root.tlRadius * root.tlMultY) + relativeY: root.isRenderable ? (-(root.panelHeight - root.blRadius * root.blMultY - root.tlRadius * root.tlMultY)) : -0.75 } // Top-left corner arc (back to start) PathArc { - relativeX: root.tlRadius * root.tlMultX - relativeY: -root.tlRadius * root.tlMultY - radiusX: root.tlRadius - radiusY: root.tlRadius + relativeX: root.isRenderable ? (root.tlRadius * root.tlMultX) : 0.25 + relativeY: root.isRenderable ? (-root.tlRadius * root.tlMultY) : 0 + radiusX: root.isRenderable ? root.tlRadius : 0 + radiusY: root.isRenderable ? root.tlRadius : 0 direction: ShapeCornerHelper.getArcDirection(root.tlMultX, root.tlMultY) } } diff --git a/Modules/MainScreen/BarExclusionZone.qml b/Modules/MainScreen/BarExclusionZone.qml index 44bab77bc..e9696e62e 100644 --- a/Modules/MainScreen/BarExclusionZone.qml +++ b/Modules/MainScreen/BarExclusionZone.qml @@ -23,8 +23,13 @@ PanelWindow { readonly property bool barFloating: Settings.data.bar.floating || false readonly property real barMarginH: (barFloating && edge === Settings.getBarPositionForScreen(screen?.name)) ? Math.ceil(Settings.data.bar.marginHorizontal) : 0 readonly property real barMarginV: (barFloating && edge === Settings.getBarPositionForScreen(screen?.name)) ? Math.ceil(Settings.data.bar.marginVertical) : 0 - // Reduce exclusion zone by 1 physical pixel so app windows blend flush against the bar edge - readonly property real bleedInset: 1.0 / (CompositorService.getDisplayScale(screen?.name) || 1.0) + // Allow users to enable a 1-physical-pixel inset for the exclusion zone so window borders can bleed under the bar + readonly property real bleedOffset: Settings.data.bar.enableExclusionZoneInset ? 1.0 : 0.0 + readonly property real bleedInset: { + const info = CompositorService.displayScales[screen?.name]; + const scale = (info && info.scale) ? info.scale : 1.0; + return bleedOffset / scale; + } // Invisible - just reserves space color: "transparent" diff --git a/Modules/MainScreen/MainScreen.qml b/Modules/MainScreen/MainScreen.qml index 685839c32..f51ad1157 100644 --- a/Modules/MainScreen/MainScreen.qml +++ b/Modules/MainScreen/MainScreen.qml @@ -203,13 +203,15 @@ PanelWindow { } // Blur behind the bar and open panels — attached to PanelWindow (required by BackgroundEffect API) - BackgroundEffect.blurRegion: Region { + BackgroundEffect.blurRegion: Settings.data.general.enableBlurBehind ? blurRegion : null + Region { + id: blurRegion // ── Non-framed bar (simple/floating): single rectangle with bar corner states ── Region { - x: (!barPlaceholder.isFramed && root.barShouldShow) ? barPlaceholder.x : 0 - y: (!barPlaceholder.isFramed && root.barShouldShow) ? barPlaceholder.y : 0 - width: (!barPlaceholder.isFramed && root.barShouldShow) ? barPlaceholder.width : 0 - height: (!barPlaceholder.isFramed && root.barShouldShow) ? barPlaceholder.height : 0 + x: (!barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? barPlaceholder.x : 0 + y: (!barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? barPlaceholder.y : 0 + width: (!barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? barPlaceholder.width : 0 + height: (!barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? barPlaceholder.height : 0 radius: Style.radiusL topLeftCorner: barPlaceholder.topLeftCornerState topRightCorner: barPlaceholder.topRightCornerState @@ -221,8 +223,8 @@ PanelWindow { Region { x: 0 y: 0 - width: (barPlaceholder.isFramed && root.barShouldShow) ? root.width : 0 - height: (barPlaceholder.isFramed && root.barShouldShow) ? root.height : 0 + width: (barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? root.width : 0 + height: (barPlaceholder.isFramed && root.barShouldShow && !barPlaceholder.isHidden) ? root.height : 0 Region { intersection: Intersection.Subtract @@ -460,14 +462,14 @@ PanelWindow { // Use screen dimensions directly x: { if (barPosition === "right") - return screen.width - barHeight - barMarginH; + return (screen?.width ?? 0) - barHeight - barMarginH; if (isFramed && !barIsVertical) return frameThickness; return barMarginH; } y: { if (barPosition === "bottom") - return screen.height - barHeight - barMarginV; + return (screen?.height ?? 0) - barHeight - barMarginV; if (isFramed && barIsVertical) return frameThickness; return barMarginV; @@ -477,16 +479,16 @@ PanelWindow { return barHeight; } if (isFramed) - return screen.width - frameThickness * 2; - return screen.width - barMarginH * 2; + return (screen?.width ?? 0) - frameThickness * 2; + return (screen?.width ?? 0) - barMarginH * 2; } height: { if (!barIsVertical) { return barHeight; } if (isFramed) - return screen.height - frameThickness * 2; - return screen.height - barMarginV * 2; + return (screen?.height ?? 0) - frameThickness * 2; + return (screen?.height ?? 0) - barMarginV * 2; } // Corner states (same as Bar.qml) diff --git a/Modules/MainScreen/ScreenCorners.qml b/Modules/MainScreen/ScreenCorners.qml index b2b9374ae..4387ecfa1 100644 --- a/Modules/MainScreen/ScreenCorners.qml +++ b/Modules/MainScreen/ScreenCorners.qml @@ -25,7 +25,9 @@ Item { anchors.fill: parent preferredRendererType: Shape.CurveRenderer + asynchronous: true enabled: false // Disable mouse input + visible: cornersPath.cornerRadius > 0 && width > 0 && height > 0 ShapePath { id: cornersPath diff --git a/Modules/MainScreen/SmartPanel.qml b/Modules/MainScreen/SmartPanel.qml index 36036a168..aff4af32b 100644 --- a/Modules/MainScreen/SmartPanel.qml +++ b/Modules/MainScreen/SmartPanel.qml @@ -72,6 +72,8 @@ Item { property bool cachedAnimateFromBottom: false property bool cachedAnimateFromLeft: false property bool cachedAnimateFromRight: false + + readonly property bool animationsDisabled: Settings.data.general.animationDisabled property bool cachedShouldAnimateWidth: false property bool cachedShouldAnimateHeight: false @@ -161,11 +163,41 @@ Item { // Reset to default - fixes panel being stuck in one position root.useButtonPosition = false; + // Calculate the bar window's position on screen based on bar settings + // The BarContentWindow uses anchors + margins, so we need to compute its origin + var barWindowX = 0; + var barWindowY = 0; + var screenWidth = root.screen?.width || 0; + var screenHeight = root.screen?.height || 0; + + if (root.barPosition === "right") { + barWindowX = screenWidth - root.barMarginH - root.barHeight; + } else if (root.barPosition === "left") { + barWindowX = root.barMarginH; + } else if (root.isFramed) { + barWindowX = root.frameThickness; + } else { + // Horizontal floating bars: BarContentWindow has margins.left = barMarginH + barWindowX = root.barMarginH; + } + + if (root.barPosition === "bottom") { + barWindowY = screenHeight - root.barMarginV - root.barHeight; + } else if (root.barPosition === "top") { + barWindowY = root.barMarginV; + } else if (root.isFramed) { + barWindowY = root.frameThickness; + } else { + // Vertical floating bars: BarContentWindow has margins.top = barMarginV + barWindowY = root.barMarginV; + } + if (!buttonItem && buttonName) { // Check if buttonName is actually a point object (click coordinates) if (typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined) { root.buttonItem = null; - root.buttonPosition = buttonName; + // Click coordinates are in BarContentWindow-local space, offset to screen space + root.buttonPosition = Qt.point(barWindowX + buttonName.x, barWindowY + buttonName.y); root.buttonWidth = 0; root.buttonHeight = 0; root.useButtonPosition = true; @@ -179,34 +211,9 @@ Item { if (buttonItem && typeof buttonItem.mapToItem === "function") { try { root.buttonItem = buttonItem; - // Map button position within its window + // Map button position within its window (BarContentWindow-local coordinates) var buttonLocal = buttonItem.mapToItem(null, 0, 0); - // Calculate the bar window's position on screen based on bar settings - // The BarContentWindow uses anchors, so we need to compute its position - var barWindowX = 0; - var barWindowY = 0; - var screenWidth = root.screen?.width || 0; - var screenHeight = root.screen?.height || 0; - - if (root.barPosition === "right") { - barWindowX = screenWidth - root.barMarginH - root.barHeight; - } else if (root.barPosition === "left") { - barWindowX = root.barMarginH; - } else if (root.isFramed) { - barWindowX = root.frameThickness; - } - // For top/bottom bars, barWindowX stays 0 (full width window) unless framed - - if (root.barPosition === "bottom") { - barWindowY = screenHeight - root.barMarginV - root.barHeight; - } else if (root.barPosition === "top") { - barWindowY = root.barMarginV; - } else if (root.isFramed) { - barWindowY = root.frameThickness; - } - // For left/right bars, barWindowY stays 0 (full height window) unless framed - root.buttonPosition = Qt.point(barWindowX + buttonLocal.x, barWindowY + buttonLocal.y); root.buttonWidth = buttonItem.width; root.buttonHeight = buttonItem.height; @@ -1312,7 +1319,13 @@ Item { // Make panel visible, now only the intended dimension will animate root.isPanelVisible = true; - opacityTrigger.start(); + + if (root.animationsDisabled) { + // Skip delay when animations are disabled + root.sizeAnimationComplete = true; + } else { + opacityTrigger.start(); + } // Start open watchdog timer root.openWatchdogActive = true; diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 9fbd1ad9d..0c56c7fe1 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -11,8 +11,15 @@ import qs.Widgets // Simple notification popup - displays multiple notifications Variants { - // If no notification display activated in settings, then show them all - model: Quickshell.screens.filter(screen => (Settings.data.notifications.monitors.includes(screen.name) || (Settings.data.notifications.monitors.length === 0))) + + model: { + const screens = Quickshell.screens.filter(screen => Settings.data.notifications.monitors.includes(screen.name)); + // Empty list can mean two things : + // - No (visible) notification display activated in settings + // - One or more (not visible) displays are activated but unplugged + // In both cases we fallback to show notification on all screens + return screens.length === 0 ? Quickshell.screens : screens; + } delegate: Loader { id: root @@ -21,8 +28,13 @@ Variants { property ListModel notificationModel: NotificationService.activeList - // Always create window (but with 0x0 dimensions when no notifications) - active: notificationModel.count > 0 || delayTimer.running + // Deferred activation to prevent re-entrant QML incubation crash. + // Direct binding to notificationModel.count would activate the Loader + // synchronously during ListModel.insert(), causing nested incubation + // (Loader + inner Repeater both processing the model) which crashes + // the V4 engine in QV4::Object::insertMember. + property bool shouldBeActive: false + active: shouldBeActive || delayTimer.running // Keep loader active briefly after last notification to allow animations to complete Timer { @@ -31,10 +43,23 @@ Variants { repeat: false } + // Deferred activation timer - activates Loader on next event loop iteration + Timer { + id: activationTimer + interval: 0 + repeat: false + onTriggered: root.shouldBeActive = true + } + Connections { target: notificationModel function onCountChanged() { - if (notificationModel.count === 0 && root.active) { + if (notificationModel.count > 0) { + if (!root.shouldBeActive) { + activationTimer.restart(); + } + } else if (root.shouldBeActive) { + root.shouldBeActive = false; delayTimer.restart(); } } diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index e7c27b36e..e701c72e8 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -35,6 +35,7 @@ Variants { property int currentOSDType: -1 // OSD.Type enum value, -1 means none property bool startupComplete: false property real currentBrightness: 0 + property bool suppressInputOSD: false // Lock Key States property string lastLockKeyChanged: "" // "caps", "num", "scroll", or "" @@ -258,11 +259,15 @@ Variants { } function onInputVolumeChanged() { + if (suppressInputOSD) + return; if (AudioService.hasInput) showOSD(OSD.Type.InputVolume); } function onInputMutedChanged() { + if (suppressInputOSD) + return; if (!AudioService.hasInput) return; if (AudioService.consumeInputOSDSuppression()) @@ -272,6 +277,8 @@ Variants { // Refresh OSD when device changes to ensure correct volume is displayed function onSinkChanged() { + suppressInputOSD = true; + inputSuppressionTimer.restart(); // If volume OSD is currently showing, refresh it to show new device's volume if (root.currentOSDType === OSD.Type.Volume) { Qt.callLater(() => { @@ -344,6 +351,14 @@ Variants { } } + // Timer to reset the input volume OSD suppression + Timer { + id: inputSuppressionTimer + interval: 300 + repeat: false + onTriggered: root.suppressInputOSD = false + } + Component.onDestruction: { LockKeysService.unregisterComponent("osd:" + (modelData?.name || "unknown")); if (typeof BrightnessService !== "undefined" && BrightnessService.monitors) { diff --git a/Modules/Panels/Bluetooth/BluetoothPanel.qml b/Modules/Panels/Bluetooth/BluetoothPanel.qml index c416829e6..92464d7b7 100644 --- a/Modules/Panels/Bluetooth/BluetoothPanel.qml +++ b/Modules/Panels/Bluetooth/BluetoothPanel.qml @@ -59,6 +59,14 @@ SmartPanel { baseSize: Style.baseWidgetSize * 0.65 } + NIconButton { + icon: Settings.data.network.bluetoothAutoConnect ? "bluetooth-connected" : "bluetooth" + tooltipText: Settings.data.network.bluetoothAutoConnect ? I18n.tr("tooltips.bluetooth-auto-connect-on") : I18n.tr("tooltips.bluetooth-auto-connect-off") + colorFg: Settings.data.network.bluetoothAutoConnect ? Color.mPrimary : Color.mOnSurfaceVariant + baseSize: Style.baseWidgetSize * 0.8 + onClicked: Settings.data.network.bluetoothAutoConnect = !Settings.data.network.bluetoothAutoConnect + } + NIconButton { icon: "settings" tooltipText: I18n.tr("tooltips.open-settings") diff --git a/Modules/Panels/Brightness/BrightnessPanel.qml b/Modules/Panels/Brightness/BrightnessPanel.qml index f0fdd99d0..4093345bc 100644 --- a/Modules/Panels/Brightness/BrightnessPanel.qml +++ b/Modules/Panels/Brightness/BrightnessPanel.qml @@ -218,6 +218,10 @@ SmartPanel { Layout.preferredHeight: outputColumn.implicitHeight + Style.margin2M property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData) + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } ColumnLayout { id: outputColumn @@ -231,7 +235,6 @@ SmartPanel { label: modelData.name || "Unknown" labelColor: Color.mPrimary description: { - const compositorScale = CompositorService.getDisplayScale(modelData.name); I18n.tr("system.monitor-description", { "model": modelData.model, "width": modelData.width * compositorScale, diff --git a/Modules/Panels/Dock/StaticDockPanel.qml b/Modules/Panels/Dock/StaticDockPanel.qml index 302a9c2aa..ea08ac1be 100644 --- a/Modules/Panels/Dock/StaticDockPanel.qml +++ b/Modules/Panels/Dock/StaticDockPanel.qml @@ -56,11 +56,25 @@ SmartPanel { } } + onAnyAppHoveredChanged: { + if (anyAppHovered) { + hoverCloseTimer.stop(); + } else if (!panelHovered && !menuHovered && !dockHovered && !isDockHovered) { + hoverCloseTimer.restart(); + } + } + onClosed: { hoverCloseTimer.stop(); isDockHovered = false; } + onOpened: { + if (!panelHovered && !menuHovered) { + hoverCloseTimer.restart(); + } + } + panelAnchorTop: dockPosition === "top" panelAnchorBottom: dockPosition === "bottom" panelAnchorLeft: dockPosition === "left" @@ -196,7 +210,38 @@ SmartPanel { if (!appId || !pinnedApps || pinnedApps.length === 0) return false; const normalizedId = normalizeAppId(appId); - return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId); + // Direct match + if (pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId)) + return true; + // Resolve via desktop entry lookup (handles StartupWMClass != .desktop filename) + const resolved = resolveToDesktopEntryId(appId); + if (resolved !== appId) { + const normalizedResolved = normalizeAppId(resolved); + return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedResolved); + } + return false; + } + + // Desktop entry ID resolution cache (cleared when DesktopEntries change) + property var _desktopEntryIdCache: ({}) + + // Resolve a toplevel appId to its canonical .desktop entry ID via heuristic lookup. + function resolveToDesktopEntryId(appId) { + if (!appId) + return appId; + if (_desktopEntryIdCache.hasOwnProperty(appId)) + return _desktopEntryIdCache[appId]; + try { + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) { + const entry = DesktopEntries.heuristicLookup(appId); + if (entry && entry.id) { + _desktopEntryIdCache[appId] = entry.id; + return entry.id; + } + } + } catch (e) {} + _desktopEntryIdCache[appId] = appId; + return appId; } // Helper function to get app name from desktop entry @@ -372,7 +417,17 @@ SmartPanel { function pushPinned() { pinnedApps.forEach(pinnedAppId => { // Find all running instances of this pinned app using robust matching - const matchingToplevels = runningApps.filter(app => app && normalizeAppId(app.appId) === normalizeAppId(pinnedAppId)); + // Also resolve toplevel appId via desktop entry lookup to handle + // StartupWMClass != .desktop filename (e.g. zen -> zen-browser-bin) + const normalizedPinned = normalizeAppId(pinnedAppId); + const matchingToplevels = runningApps.filter(app => { + if (!app) + return false; + if (normalizeAppId(app.appId) === normalizedPinned) + return true; + const resolved = resolveToDesktopEntryId(app.appId); + return resolved !== app.appId && normalizeAppId(resolved) === normalizedPinned; + }); if (matchingToplevels.length > 0) { // Add all running instances as pinned-running @@ -473,6 +528,7 @@ SmartPanel { target: DesktopEntries.applications function onValuesChanged() { root.iconRevision++; + root._desktopEntryIdCache = {}; } } @@ -492,7 +548,7 @@ SmartPanel { id: hoverCloseTimer interval: hideDelay onTriggered: { - if (root.menuHovered || (root.currentContextMenu && root.currentContextMenu.visible)) { + if (root.dockHovered || root.isDockHovered || root.anyAppHovered || root.menuHovered || (root.currentContextMenu && root.currentContextMenu.visible)) { restart(); return; } @@ -509,20 +565,27 @@ SmartPanel { property real contentPreferredWidth: Math.round(dockContainerWrapper.width) - (isVertical ? frameThickness : 0) property real contentPreferredHeight: Math.round(dockContainerWrapper.height) - (!isVertical ? frameThickness : 0) - // Detect hover over panel content (including DockContent) - HoverHandler { - id: dockHoverHandler - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - onHoveredChanged: { - root.panelHovered = hovered; - if (hovered) { - root.isDockHovered = true; - hoverCloseTimer.stop(); - } else { - if (root.menuHovered || (root.currentContextMenu && root.currentContextMenu.visible)) { + Item { + id: hoverArea + anchors.fill: dockContainerWrapper + anchors.margins: -frameThickness + + // Detect hover over dock area including frame thickness + HoverHandler { + id: dockHoverArea + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onHoveredChanged: { + root.panelHovered = hovered; + if (hovered) { + root.isDockHovered = true; hoverCloseTimer.stop(); } else { - hoverCloseTimer.restart(); + root.isDockHovered = false; + if (root.menuHovered || (root.currentContextMenu && root.currentContextMenu.visible)) { + hoverCloseTimer.stop(); + } else { + hoverCloseTimer.restart(); + } } } } diff --git a/Modules/Panels/Launcher/Helpers/LauncherNavigation.js b/Modules/Panels/Launcher/Helpers/LauncherNavigation.js new file mode 100644 index 000000000..00a1c5e95 --- /dev/null +++ b/Modules/Panels/Launcher/Helpers/LauncherNavigation.js @@ -0,0 +1,134 @@ +function selectNext(selectedIndex, resultsLength) { + if (resultsLength > 0 && selectedIndex < resultsLength - 1) + return selectedIndex + 1; + return selectedIndex; +} + +function selectPrevious(selectedIndex, resultsLength) { + if (resultsLength > 0 && selectedIndex > 0) + return selectedIndex - 1; + return selectedIndex; +} + +function selectNextWrapped(selectedIndex, resultsLength, allowWrap) { + if (resultsLength > 0) { + if (allowWrap) + return (selectedIndex + 1) % resultsLength; + return selectNext(selectedIndex, resultsLength); + } + return selectedIndex; +} + +function selectPreviousWrapped(selectedIndex, resultsLength, allowWrap) { + if (resultsLength > 0) { + if (allowWrap) + return (((selectedIndex - 1) % resultsLength) + resultsLength) % resultsLength; + return selectPrevious(selectedIndex, resultsLength); + } + return selectedIndex; +} + +function selectFirst() { + return 0; +} + +function selectLast(resultsLength) { + return resultsLength > 0 ? resultsLength - 1 : 0; +} + +function selectNextPage(selectedIndex, resultsLength, entryHeight) { + if (resultsLength > 0) { + var page = Math.max(1, Math.floor(600 / entryHeight)); + return Math.min(selectedIndex + page, resultsLength - 1); + } + return selectedIndex; +} + +function selectPreviousPage(selectedIndex, resultsLength, entryHeight) { + if (resultsLength > 0) { + var page = Math.max(1, Math.floor(600 / entryHeight)); + return Math.max(selectedIndex - page, 0); + } + return selectedIndex; +} + +function selectPreviousRow(selectedIndex, resultsLength, gridColumns) { + if (resultsLength <= 0 || gridColumns <= 0) + return selectedIndex; + + var currentRow = Math.floor(selectedIndex / gridColumns); + var currentCol = selectedIndex % gridColumns; + + if (currentRow > 0) { + var targetRow = currentRow - 1; + var itemsInTargetRow = Math.min(gridColumns, resultsLength - targetRow * gridColumns); + if (currentCol < itemsInTargetRow) + return targetRow * gridColumns + currentCol; + return targetRow * gridColumns + itemsInTargetRow - 1; + } + + // Wrap to last row, same column + var totalRows = Math.ceil(resultsLength / gridColumns); + var lastRow = totalRows - 1; + var itemsInLastRow = Math.min(gridColumns, resultsLength - lastRow * gridColumns); + if (currentCol < itemsInLastRow) + return lastRow * gridColumns + currentCol; + return resultsLength - 1; +} + +function selectNextRow(selectedIndex, resultsLength, gridColumns) { + if (resultsLength <= 0 || gridColumns <= 0) + return selectedIndex; + + var currentRow = Math.floor(selectedIndex / gridColumns); + var currentCol = selectedIndex % gridColumns; + var totalRows = Math.ceil(resultsLength / gridColumns); + + if (currentRow < totalRows - 1) { + var targetRow = currentRow + 1; + var targetIndex = targetRow * gridColumns + currentCol; + if (targetIndex < resultsLength) + return targetIndex; + var itemsInTargetRow = resultsLength - targetRow * gridColumns; + if (itemsInTargetRow > 0) + return targetRow * gridColumns + itemsInTargetRow - 1; + return Math.min(currentCol, resultsLength - 1); + } + + // Wrap to first row, same column + return Math.min(currentCol, resultsLength - 1); +} + +function selectPreviousColumn(selectedIndex, resultsLength, gridColumns) { + if (resultsLength <= 0) + return selectedIndex; + + var currentRow = Math.floor(selectedIndex / gridColumns); + var currentCol = selectedIndex % gridColumns; + + if (currentCol > 0) + return currentRow * gridColumns + (currentCol - 1); + if (currentRow > 0) + return (currentRow - 1) * gridColumns + (gridColumns - 1); + + var totalRows = Math.ceil(resultsLength / gridColumns); + var lastRowIndex = (totalRows - 1) * gridColumns + (gridColumns - 1); + return Math.min(lastRowIndex, resultsLength - 1); +} + +function selectNextColumn(selectedIndex, resultsLength, gridColumns) { + if (resultsLength <= 0) + return selectedIndex; + + var currentRow = Math.floor(selectedIndex / gridColumns); + var currentCol = selectedIndex % gridColumns; + var itemsInCurrentRow = Math.min(gridColumns, resultsLength - currentRow * gridColumns); + + if (currentCol < itemsInCurrentRow - 1) + return currentRow * gridColumns + (currentCol + 1); + + var totalRows = Math.ceil(resultsLength / gridColumns); + if (currentRow < totalRows - 1) + return (currentRow + 1) * gridColumns; + return 0; +} diff --git a/Modules/Panels/Launcher/Launcher.qml b/Modules/Panels/Launcher/Launcher.qml index 2e593deea..4518a6bc0 100644 --- a/Modules/Panels/Launcher/Launcher.qml +++ b/Modules/Panels/Launcher/Launcher.qml @@ -67,12 +67,12 @@ SmartPanel { return Settings.data.appLauncher.position; } } - panelAnchorHorizontalCenter: panelPosition === "center" || panelPosition.endsWith("_center") - panelAnchorVerticalCenter: panelPosition === "center" - panelAnchorLeft: panelPosition !== "center" && panelPosition.endsWith("_left") - panelAnchorRight: panelPosition !== "center" && panelPosition.endsWith("_right") - panelAnchorBottom: panelPosition.startsWith("bottom_") - panelAnchorTop: panelPosition.startsWith("top_") + panelAnchorHorizontalCenter: !root.useButtonPosition && (panelPosition === "center" || panelPosition.endsWith("_center")) + panelAnchorVerticalCenter: !root.useButtonPosition && panelPosition === "center" + panelAnchorLeft: !root.useButtonPosition && panelPosition !== "center" && panelPosition.endsWith("_left") + panelAnchorRight: !root.useButtonPosition && panelPosition !== "center" && panelPosition.endsWith("_right") + panelAnchorBottom: !root.useButtonPosition && panelPosition.startsWith("bottom_") + panelAnchorTop: !root.useButtonPosition && panelPosition.startsWith("top_") panelContent: Rectangle { id: ui @@ -89,11 +89,20 @@ SmartPanel { } // Preview Panel (external) - uses provider's preview component + NDropShadow { + source: previewBox + anchors.fill: previewBox + autoPaddingEnabled: true + visible: previewBox.visible + z: previewBox.z - 1 + } + NBox { id: previewBox visible: root.previewActive width: root.previewPanelWidth height: Math.round(400 * Style.uiScaleRatio) + forceOpaque: true // no blur for now x: root.panelAnchorRight ? -(root.previewPanelWidth + Style.marginM) : ui.width + Style.marginM y: { var view = launcherCore.resultsView; @@ -151,20 +160,15 @@ SmartPanel { } // Core launcher (state, providers, UI) - NBox { + LauncherCore { + id: launcherCore anchors.fill: parent - anchors.margins: Style.marginL - - LauncherCore { - id: launcherCore - anchors.fill: parent - screen: root.screen - isOpen: root.isPanelOpen - onRequestClose: root.close() - // Defer so the signal emission completes before SmartPanel - // sets isPanelOpen=false and the contentLoader destroys us. - onRequestCloseImmediately: Qt.callLater(root.closeImmediately) - } + screen: root.screen + isOpen: root.isPanelOpen + onRequestClose: root.close() + // Defer so the signal emission completes before SmartPanel + // sets isPanelOpen=false and the contentLoader destroys us. + onRequestCloseImmediately: Qt.callLater(root.closeImmediately) } // Update preview when selection changes diff --git a/Modules/Panels/Launcher/LauncherCore.qml b/Modules/Panels/Launcher/LauncherCore.qml index 7146b3b76..637ce4556 100644 --- a/Modules/Panels/Launcher/LauncherCore.qml +++ b/Modules/Panels/Launcher/LauncherCore.qml @@ -3,1551 +3,874 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Widgets +import "Helpers/LauncherNavigation.js" as LauncherNav import "Providers" import qs.Commons import qs.Services.Keyboard -import qs.Services.Noctalia import qs.Services.UI import qs.Widgets // Core launcher logic and UI - shared between SmartPanel (Launcher.qml) and overlay (LauncherOverlayWindow.qml) Rectangle { - id: root - color: "transparent" + id: root + color: "transparent" - // External interface - set by parent - property var screen: null - property bool isOpen: false - signal requestClose - signal requestCloseImmediately + // External interface - set by parent + property var screen: null + property bool isOpen: false + signal requestClose + signal requestCloseImmediately - function closeImmediately() { - requestCloseImmediately(); + function closeImmediately() { + requestCloseImmediately(); + } + + // Expose for preview panel positioning + readonly property var resultsView: resultsViewLoader.item + + // State + property string searchText: "" + property int selectedIndex: 0 + property var results: [] + property var providers: [] + property var activeProvider: null + property bool resultsReady: false + property var pluginProviderInstances: ({}) + property bool ignoreMouseHover: true // Transient flag, should always be true on init + + // Global mouse tracking for movement detection across delegates + property real globalLastMouseX: 0 + property real globalLastMouseY: 0 + property bool globalMouseInitialized: false + property bool mouseTrackingReady: false // Delay tracking until panel is settled + + readonly property bool animationsDisabled: Settings.data.general.animationDisabled + + Timer { + id: mouseTrackingDelayTimer + interval: root.animationsDisabled ? 0 : (Style.animationNormal + 50) // Wait for panel animation to complete + safety margin + repeat: false + onTriggered: { + root.mouseTrackingReady = true; + root.globalMouseInitialized = false; // Reset so we get fresh initial position + } + } + + readonly property var defaultProvider: appsProvider + readonly property var currentProvider: activeProvider || defaultProvider + + readonly property string launcherDensity: (currentProvider && currentProvider.ignoreDensity === false) ? (Settings.data.appLauncher.density || "default") : "comfortable" + readonly property int effectiveIconSize: launcherDensity === "comfortable" ? 48 : (launcherDensity === "default" ? 36 : 24) + readonly property int badgeSize: Math.round(effectiveIconSize * Style.uiScaleRatio) + readonly property int entryHeight: Math.round(badgeSize + (launcherDensity === "compact" ? (Style.marginL + Style.marginXXS) : (Style.marginXL + Style.marginS))) + + readonly property bool providerShowsCategories: currentProvider.showsCategories === true + + readonly property var providerCategories: { + if (currentProvider.availableCategories && currentProvider.availableCategories.length > 0) { + return currentProvider.availableCategories; + } + return currentProvider.categories || []; + } + + readonly property bool showProviderCategories: { + if (!providerShowsCategories || providerCategories.length === 0) + return false; + if (currentProvider === defaultProvider) + return Settings.data.appLauncher.showCategories; + return true; + } + + readonly property bool providerHasDisplayString: results.length > 0 && !!results[0].displayString + + readonly property string providerSupportedLayouts: { + if (activeProvider && activeProvider.supportedLayouts) + return activeProvider.supportedLayouts; + if (results.length > 0 && results[0].provider && results[0].provider.supportedLayouts) + return results[0].provider.supportedLayouts; + if (defaultProvider && defaultProvider.supportedLayouts) + return defaultProvider.supportedLayouts; + return "both"; + } + + readonly property bool showLayoutToggle: !providerHasDisplayString && providerSupportedLayouts === "both" + + readonly property string layoutMode: { + if (searchText === ">") + return "list"; + if (providerSupportedLayouts === "grid") + return "grid"; + if (providerSupportedLayouts === "list") + return "list"; + if (providerSupportedLayouts === "single") + return "single"; + if (providerHasDisplayString) + return "grid"; + return Settings.data.appLauncher.viewMode; + } + + readonly property bool isGridView: layoutMode === "grid" + readonly property bool isSingleView: layoutMode === "single" + readonly property bool isCompactDensity: launcherDensity === "compact" + + readonly property int targetGridColumns: { + let base = 5; + if (launcherDensity === "comfortable") + base = 4; + else if (launcherDensity === "compact") + base = 6; + + if (!activeProvider || activeProvider === defaultProvider) + return base; + + if (activeProvider.preferredGridColumns) { + let multiplier = base / 5.0; + return Math.max(1, Math.round(activeProvider.preferredGridColumns * multiplier)); } - // Expose for preview panel positioning - readonly property var resultsView: resultsViewLoader.item + return base; + } + readonly property int listPanelWidth: Math.round(500 * Style.uiScaleRatio) + readonly property int gridContentWidth: listPanelWidth - (2 * Style.marginXS) + readonly property int gridCellSize: Math.floor((gridContentWidth - ((targetGridColumns - 1) * Style.marginS)) / targetGridColumns) - // State - property string searchText: "" - property int selectedIndex: 0 - property var results: [] - property var providers: [] - property var activeProvider: null - property bool resultsReady: false - property var pluginProviderInstances: ({}) - property bool ignoreMouseHover: true // Transient flag, should always be true on init + readonly property int gridColumns: targetGridColumns - // Global mouse tracking for movement detection across delegates - property real globalLastMouseX: 0 - property real globalLastMouseY: 0 - property bool globalMouseInitialized: false - property bool mouseTrackingReady: false // Delay tracking until panel is settled + // Check if current provider allows wrap navigation (default true) + readonly property bool allowWrapNavigation: { + var provider = activeProvider || currentProvider; + return provider && provider.wrapNavigation !== undefined ? provider.wrapNavigation : true; + } - Timer { - id: mouseTrackingDelayTimer - interval: Style.animationNormal + 50 // Wait for panel animation to complete + safety margin - repeat: false - onTriggered: { - root.mouseTrackingReady = true; - root.globalMouseInitialized = false; // Reset so we get fresh initial position + // Listen for plugin provider registry changes + Connections { + target: LauncherProviderRegistry + function onPluginProviderRegistryUpdated() { + root.syncPluginProviders(); + } + } + + // Lifecycle + onIsOpenChanged: { + if (isOpen) { + onOpened(); + } else { + onClosed(); + } + } + + onSearchTextChanged: { + if (isOpen) { + updateResults(); + } + } + + function onOpened() { + ignoreMouseHover = true; + globalMouseInitialized = false; + mouseTrackingReady = false; + mouseTrackingDelayTimer.restart(); + + // Show launcher immediately, results will populate asynchronously + resultsReady = true; + focusSearchInput(); + + Qt.callLater(() => { + syncPluginProviders(); + for (let provider of providers) { + if (provider.onOpened) + provider.onOpened(); + } + updateResults(); + }); + } + + function onClosed() { + searchText = ""; + ignoreMouseHover = true; + for (let provider of providers) { + if (provider.onClosed) + provider.onClosed(); + } + } + + function close() { + requestClose(); + } + + // Public API + function setSearchText(text) { + searchText = text; + } + + function focusSearchInput() { + if (searchInput.inputItem) { + searchInput.inputItem.forceActiveFocus(); + } + } + + // Provider registration + function registerProvider(provider) { + providers.push(provider); + provider.launcher = root; + if (provider.init) + provider.init(); + } + + function syncPluginProviders() { + var registeredIds = LauncherProviderRegistry.getPluginProviders(); + var changed = false; + + // Remove providers that are no longer registered + for (var existingId in pluginProviderInstances) { + if (registeredIds.indexOf(existingId) === -1) { + var idx = providers.indexOf(pluginProviderInstances[existingId]); + if (idx >= 0) + providers.splice(idx, 1); + delete pluginProviderInstances[existingId]; + Logger.d("Launcher", "Removed plugin provider:", existingId); + changed = true; + } + } + + // Adopt persistent instances from the registry + for (var i = 0; i < registeredIds.length; i++) { + var providerId = registeredIds[i]; + if (!pluginProviderInstances[providerId]) { + var instance = LauncherProviderRegistry.getProviderInstance(providerId); + if (instance) { + pluginProviderInstances[providerId] = instance; + providers.push(instance); + instance.launcher = root; + Logger.d("Launcher", "Adopted plugin provider:", providerId); + changed = true; } + } } - readonly property var defaultProvider: appsProvider - readonly property var currentProvider: activeProvider || defaultProvider + // Update results only if providers changed + if (changed && root.isOpen) { + updateResults(); + } + } - readonly property string launcherDensity: (currentProvider && currentProvider.ignoreDensity === false) ? (Settings.data.appLauncher.density || "default") : "comfortable" - readonly property int effectiveIconSize: launcherDensity === "comfortable" ? 48 : (launcherDensity === "default" ? 36 : 24) - readonly property int badgeSize: Math.round(effectiveIconSize * Style.uiScaleRatio) - readonly property int entryHeight: Math.round(badgeSize + (launcherDensity === "compact" ? (Style.marginL + Style.marginXXS) : (Style.marginXL + Style.marginS))) + // Search handling + function updateResults() { + results = []; + var newActiveProvider = null; - readonly property bool providerShowsCategories: currentProvider.showsCategories === true - - readonly property var providerCategories: { - if (currentProvider.availableCategories && currentProvider.availableCategories.length > 0) { - return currentProvider.availableCategories; + // Check for command mode + if (searchText.startsWith(">")) { + for (let provider of providers) { + if (provider.handleCommand && provider.handleCommand(searchText)) { + newActiveProvider = provider; + results = provider.getResults(searchText); + break; } - return currentProvider.categories || []; - } + } - readonly property bool showProviderCategories: { - if (!providerShowsCategories || providerCategories.length === 0) - return false; - if (currentProvider === defaultProvider) - return Settings.data.appLauncher.showCategories; - return true; - } - - readonly property bool providerHasDisplayString: results.length > 0 && !!results[0].displayString - - readonly property string providerSupportedLayouts: { - if (activeProvider && activeProvider.supportedLayouts) - return activeProvider.supportedLayouts; - if (results.length > 0 && results[0].provider && results[0].provider.supportedLayouts) - return results[0].provider.supportedLayouts; - if (defaultProvider && defaultProvider.supportedLayouts) - return defaultProvider.supportedLayouts; - return "both"; - } - - readonly property bool showLayoutToggle: !providerHasDisplayString && providerSupportedLayouts === "both" - - readonly property string layoutMode: { - if (searchText === ">") - return "list"; - if (providerSupportedLayouts === "grid") - return "grid"; - if (providerSupportedLayouts === "list") - return "list"; - if (providerSupportedLayouts === "single") - return "single"; - if (providerHasDisplayString) - return "grid"; - return Settings.data.appLauncher.viewMode; - } - - readonly property bool isGridView: layoutMode === "grid" - readonly property bool isSingleView: layoutMode === "single" - readonly property bool isCompactDensity: launcherDensity === "compact" - - readonly property int targetGridColumns: { - let base = 5; - if (launcherDensity === "comfortable") - base = 4; - else if (launcherDensity === "compact") - base = 6; - - if (!activeProvider || activeProvider === defaultProvider) - return base; - - if (activeProvider.preferredGridColumns) { - let multiplier = base / 5.0; - return Math.max(1, Math.round(activeProvider.preferredGridColumns * multiplier)); - } - - return base; - } - readonly property int listPanelWidth: Math.round(500 * Style.uiScaleRatio) - readonly property int gridContentWidth: listPanelWidth - (2 * Style.marginXS) - readonly property int gridCellSize: Math.floor((gridContentWidth - ((targetGridColumns - 1) * Style.marginS)) / targetGridColumns) - - property int gridColumns: 5 - - // Check if current provider allows wrap navigation (default true) - readonly property bool allowWrapNavigation: { - var provider = activeProvider || currentProvider; - return provider && provider.wrapNavigation !== undefined ? provider.wrapNavigation : true; - } - - // Listen for plugin provider registry changes - Connections { - target: LauncherProviderRegistry - function onPluginProviderRegistryUpdated() { - root.syncPluginProviders(); - } - } - - // Lifecycle - onIsOpenChanged: { - if (isOpen) { - onOpened(); - } else { - onClosed(); - } - } - - function onOpened() { - resultsReady = false; - ignoreMouseHover = true; - globalMouseInitialized = false; - mouseTrackingReady = false; - mouseTrackingDelayTimer.restart(); - syncPluginProviders(); - Qt.callLater(() => { - for (let provider of providers) { - if (provider.onOpened) - provider.onOpened(); - } - updateResults(); - resultsReady = true; - focusSearchInput(); - }); - } - - function onClosed() { - searchText = ""; - ignoreMouseHover = true; + // Show available commands if just ">" or filter commands if partial match + if (!newActiveProvider) { + let allCommands = []; for (let provider of providers) { - if (provider.onClosed) - provider.onClosed(); + if (provider.commands) + allCommands = allCommands.concat(provider.commands()); } - } - - onSearchTextChanged: { - if (isOpen) - updateResults(); - } - - function close() { - requestClose(); - } - - // Public API - function setSearchText(text) { - searchText = text; - } - - function focusSearchInput() { - if (searchInput.inputItem) { - searchInput.inputItem.forceActiveFocus(); + if (searchText === ">") { + results = allCommands; + } else if (searchText.length > 1) { + const query = searchText.substring(1); + if (typeof FuzzySort !== 'undefined') { + const fuzzyResults = FuzzySort.go(query, allCommands, { + "keys": ["name"], + "limit": 50 + }); + results = fuzzyResults.map(result => result.obj); + } else { + const queryLower = query.toLowerCase(); + results = allCommands.filter(cmd => (cmd.name || "").toLowerCase().includes(queryLower)); + } } - } - - // Provider registration - function registerProvider(provider) { - providers.push(provider); - provider.launcher = root; - if (provider.init) - provider.init(); - } - - function syncPluginProviders() { - var registeredIds = LauncherProviderRegistry.getPluginProviders(); - - // Remove providers that are no longer registered - for (var existingId in pluginProviderInstances) { - if (registeredIds.indexOf(existingId) === -1) { - var idx = providers.indexOf(pluginProviderInstances[existingId]); - if (idx >= 0) - providers.splice(idx, 1); - pluginProviderInstances[existingId].destroy(); - delete pluginProviderInstances[existingId]; - Logger.d("Launcher", "Removed plugin provider:", existingId); - } + } + } else { + // Regular search - let providers contribute results + let allResults = []; + for (let provider of providers) { + if (provider.handleSearch) { + const providerResults = provider.getResults(searchText); + allResults = allResults.concat(providerResults); } + } - // Add new providers - for (var i = 0; i < registeredIds.length; i++) { - var providerId = registeredIds[i]; - if (!pluginProviderInstances[providerId]) { - var component = LauncherProviderRegistry.getProviderComponent(providerId); - var pluginId = providerId.substring(7); // Remove "plugin:" prefix - var pluginApi = PluginService.getPluginAPI(pluginId); - if (component && pluginApi) { - var instance = component.createObject(root, { - pluginApi: pluginApi - }); - if (instance) { - pluginProviderInstances[providerId] = instance; - registerProvider(instance); - Logger.d("Launcher", "Registered plugin provider:", providerId); - } - } - } - } + // Sort by _score (higher = better match), items without _score go first + if (searchText.trim() !== "") { + const boostByUsage = Settings.data.appLauncher.sortByMostUsed; - // Update results if launcher is open - if (root.isOpen) { - updateResults(); - } - } + allResults.sort((a, b) => { + let sa = a._score !== undefined ? a._score : 0; + let sb = b._score !== undefined ? b._score : 0; - // Search handling - function updateResults() { - results = []; - var newActiveProvider = null; + // Boost scores for frequently used items from tracked providers + // _score is normalized 0–1, so boost is scaled to nudge, not overwhelm + if (boostByUsage) { + if (a.provider && a.provider.trackUsage && a.usageKey) { + sa += 0.1 * Math.log2(1 + ShellState.getLauncherUsageCount(a.usageKey)); + } + if (b.provider && b.provider.trackUsage && b.usageKey) { + sb += 0.1 * Math.log2(1 + ShellState.getLauncherUsageCount(b.usageKey)); + } + } - // Check for command mode - if (searchText.startsWith(">")) { - for (let provider of providers) { - if (provider.handleCommand && provider.handleCommand(searchText)) { - newActiveProvider = provider; - results = provider.getResults(searchText); - break; - } - } - - // Show available commands if just ">" or filter commands if partial match - if (!newActiveProvider) { - let allCommands = []; - for (let provider of providers) { - if (provider.commands) - allCommands = allCommands.concat(provider.commands()); - } - if (searchText === ">") { - results = allCommands; - } else if (searchText.length > 1) { - const query = searchText.substring(1); - if (typeof FuzzySort !== 'undefined') { - const fuzzyResults = FuzzySort.go(query, allCommands, { - "keys": ["name"], - "limit": 50 + return sb - sa; }); - results = fuzzyResults.map(result => result.obj); - } else { - const queryLower = query.toLowerCase(); - results = allCommands.filter(cmd => (cmd.name || "").toLowerCase().includes(queryLower)); - } - } - } - } else { - // Regular search - let providers contribute results - let allResults = []; - for (let provider of providers) { - if (provider.handleSearch) { - const providerResults = provider.getResults(searchText); - allResults = allResults.concat(providerResults); - } - } - - // Sort by _score (higher = better match), items without _score go first - if (searchText.trim() !== "") { - allResults.sort((a, b) => { - const sa = a._score !== undefined ? a._score : 0; - const sb = b._score !== undefined ? b._score : 0; - return sb - sa; - }); - } - results = allResults; - } - - // Update activeProvider only after computing new state to avoid UI flicker - activeProvider = newActiveProvider; - - // Find first selectable item to default index - var initialIdx = 0; - while (initialIdx < results.length && results[initialIdx].isHeader) { - initialIdx++; - } - selectedIndex = initialIdx < results.length ? initialIdx : 0; + } + results = allResults; } - // Navigation functions - function selectNext() { - if (results.length > 0 && selectedIndex < results.length - 1) { - var nextIdx = selectedIndex + 1; - while (nextIdx < results.length && results[nextIdx].isHeader) { - nextIdx++; - } - if (nextIdx < results.length) { - selectedIndex = nextIdx; - } - } + // Update activeProvider only after computing new state to avoid UI flicker + activeProvider = newActiveProvider; + selectedIndex = 0; + } + + // Navigation functions (delegated to LauncherNavigation.js) + function selectNext() { + selectedIndex = LauncherNav.selectNext(selectedIndex, results.length); + } + function selectPrevious() { + selectedIndex = LauncherNav.selectPrevious(selectedIndex, results.length); + } + function selectNextWrapped() { + selectedIndex = LauncherNav.selectNextWrapped(selectedIndex, results.length, allowWrapNavigation); + } + function selectPreviousWrapped() { + selectedIndex = LauncherNav.selectPreviousWrapped(selectedIndex, results.length, allowWrapNavigation); + } + function selectFirst() { + selectedIndex = LauncherNav.selectFirst(); + } + function selectLast() { + selectedIndex = LauncherNav.selectLast(results.length); + } + function selectNextPage() { + selectedIndex = LauncherNav.selectNextPage(selectedIndex, results.length, entryHeight); + } + function selectPreviousPage() { + selectedIndex = LauncherNav.selectPreviousPage(selectedIndex, results.length, entryHeight); + } + function selectPreviousRow() { + selectedIndex = LauncherNav.selectPreviousRow(selectedIndex, results.length, gridColumns); + } + function selectNextRow() { + selectedIndex = LauncherNav.selectNextRow(selectedIndex, results.length, gridColumns); + } + function selectPreviousColumn() { + selectedIndex = LauncherNav.selectPreviousColumn(selectedIndex, results.length, gridColumns); + } + function selectNextColumn() { + selectedIndex = LauncherNav.selectNextColumn(selectedIndex, results.length, gridColumns); + } + + function activate() { + if (results.length > 0 && results[selectedIndex]) { + const item = results[selectedIndex]; + const provider = item.provider || currentProvider; + + // Track usage for providers that opt in (cross-provider "most used" tracking) + if (Settings.data.appLauncher.sortByMostUsed && provider && provider.trackUsage && item.usageKey) { + ShellState.recordLauncherUsage(item.usageKey); + } + + // Check if auto-paste is enabled and provider/item supports it + if (Settings.data.appLauncher.autoPasteClipboard && provider && provider.supportsAutoPaste && item.autoPasteText) { + if (item.onAutoPaste) + item.onAutoPaste(); + closeImmediately(); + Qt.callLater(() => { + ClipboardService.pasteText(item.autoPasteText); + }); + return; + } + + if (item.onActivate) + item.onActivate(); + } + } + + function checkKey(event, settingName) { + return Keybinds.checkKey(event, settingName, Settings); + } + + // Keyboard handler + function handleKeyPress(event) { + if (checkKey(event, 'escape')) { + close(); + event.accepted = true; + return; } - function selectPrevious() { - if (results.length > 0 && selectedIndex > 0) { - var prevIdx = selectedIndex - 1; - while (prevIdx >= 0 && results[prevIdx].isHeader) { - prevIdx--; - } - if (prevIdx >= 0) { - selectedIndex = prevIdx; - } - } + if (checkKey(event, 'enter')) { + activate(); + event.accepted = true; + return; } - function selectNextWrapped() { - if (results.length > 0) { - if (allowWrapNavigation) { - var nextIdx = (selectedIndex + 1) % results.length; - var scanned = 0; - while (scanned < results.length && results[nextIdx].isHeader) { - nextIdx = (nextIdx + 1) % results.length; - scanned++; - } - selectedIndex = nextIdx; - } else { - selectNext(); - } - } + if (checkKey(event, 'up')) { + if (!isSingleView) { + isGridView ? selectPreviousRow() : selectPreviousWrapped(); + } + event.accepted = true; + return; } - function selectPreviousWrapped() { - if (results.length > 0) { - if (allowWrapNavigation) { - var prevIdx = (((selectedIndex - 1) % results.length) + results.length) % results.length; - var scanned = 0; - while (scanned < results.length && results[prevIdx].isHeader) { - prevIdx = (((prevIdx - 1) % results.length) + results.length) % results.length; - scanned++; - } - selectedIndex = prevIdx; - } else { - selectPrevious(); - } - } + if (checkKey(event, 'down')) { + if (!isSingleView) { + isGridView ? selectNextRow() : selectNextWrapped(); + } + event.accepted = true; + return; } - function selectFirst() { - if (results.length === 0) - return; - var idx = 0; - while (idx < results.length && results[idx].isHeader) { - idx++; - } - selectedIndex = idx < results.length ? idx : 0; + if (checkKey(event, 'left')) { + if (isGridView) { + selectPreviousColumn(); + event.accepted = true; + return; + } } - function selectLast() { - if (results.length === 0) - return; - var idx = results.length - 1; - while (idx >= 0 && results[idx].isHeader) { - idx--; - } - selectedIndex = idx >= 0 ? idx : Math.max(0, results.length - 1); + if (checkKey(event, 'right')) { + if (isGridView) { + selectNextColumn(); + event.accepted = true; + return; + } } - function selectNextPage() { - if (results.length > 0) { - const page = Math.max(1, Math.floor(600 / entryHeight)); - selectedIndex = Math.min(selectedIndex + page, results.length - 1); - } + // Static bindings + switch (event.key) { + case Qt.Key_Tab: + if (showProviderCategories) { + var cats = providerCategories; + var idx = cats.indexOf(currentProvider.selectedCategory); + var nextIdx = (idx + 1) % cats.length; + currentProvider.selectCategory(cats[nextIdx]); + categoryTabs.currentIndex = nextIdx; + } else { + selectNextWrapped(); + } + event.accepted = true; + break; + case Qt.Key_Backtab: + if (showProviderCategories) { + var cats2 = providerCategories; + var idx2 = cats2.indexOf(currentProvider.selectedCategory); + var prevIdx = ((idx2 - 1) % cats2.length + cats2.length) % cats2.length; + currentProvider.selectCategory(cats2[prevIdx]); + categoryTabs.currentIndex = prevIdx; + } else { + selectPreviousWrapped(); + } + event.accepted = true; + break; + case Qt.Key_Home: + selectFirst(); + event.accepted = true; + break; + case Qt.Key_End: + selectLast(); + event.accepted = true; + break; + case Qt.Key_PageUp: + selectPreviousPage(); + event.accepted = true; + break; + case Qt.Key_PageDown: + selectNextPage(); + event.accepted = true; + break; + case Qt.Key_Delete: + if (selectedIndex >= 0 && results && results[selectedIndex]) { + var item = results[selectedIndex]; + var provider = item.provider || currentProvider; + if (provider && provider.canDeleteItem && provider.canDeleteItem(item)) + provider.deleteItem(item); + } + event.accepted = true; + break; } + } - function selectPreviousPage() { - if (results.length > 0) { - const page = Math.max(1, Math.floor(600 / entryHeight)); - selectedIndex = Math.max(selectedIndex - page, 0); - } + // ----------------------- + // Provider components + // ----------------------- + ApplicationsProvider { + id: appsProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: ApplicationsProvider"); } + } - // Grid view navigation functions - function selectPreviousRow() { - if (results.length > 0 && isGridView && gridColumns > 0) { - const currentRow = Math.floor(selectedIndex / gridColumns); - const currentCol = selectedIndex % gridColumns; - - if (currentRow > 0) { - const targetRow = currentRow - 1; - const targetIndex = targetRow * gridColumns + currentCol; - const itemsInTargetRow = Math.min(gridColumns, results.length - targetRow * gridColumns); - if (currentCol < itemsInTargetRow) { - selectedIndex = targetIndex; - } else { - selectedIndex = targetRow * gridColumns + itemsInTargetRow - 1; - } - } else { - // Wrap to last row, same column - const totalRows = Math.ceil(results.length / gridColumns); - const lastRow = totalRows - 1; - const itemsInLastRow = Math.min(gridColumns, results.length - lastRow * gridColumns); - if (currentCol < itemsInLastRow) { - selectedIndex = lastRow * gridColumns + currentCol; - } else { - selectedIndex = results.length - 1; - } - } - } + ClipboardProvider { + id: clipProvider + Component.onCompleted: { + if (Settings.data.appLauncher.enableClipboardHistory) { + registerProvider(this); + Logger.d("Launcher", "Registered: ClipboardProvider"); + } } + } - function selectNextRow() { - if (results.length > 0 && isGridView && gridColumns > 0) { - const currentRow = Math.floor(selectedIndex / gridColumns); - const currentCol = selectedIndex % gridColumns; - const totalRows = Math.ceil(results.length / gridColumns); - - if (currentRow < totalRows - 1) { - const targetRow = currentRow + 1; - const targetIndex = targetRow * gridColumns + currentCol; - if (targetIndex < results.length) { - selectedIndex = targetIndex; - } else { - const itemsInTargetRow = results.length - targetRow * gridColumns; - if (itemsInTargetRow > 0) { - selectedIndex = targetRow * gridColumns + itemsInTargetRow - 1; - } else { - selectedIndex = Math.min(currentCol, results.length - 1); - } - } - } else { - // Wrap to first row, same column - selectedIndex = Math.min(currentCol, results.length - 1); - } - } + CommandProvider { + id: cmdProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: CommandProvider"); } + } - function selectPreviousColumn() { - if (results.length > 0 && isGridView) { - const currentRow = Math.floor(selectedIndex / gridColumns); - const currentCol = selectedIndex % gridColumns; - if (currentCol > 0) { - selectedIndex = currentRow * gridColumns + (currentCol - 1); - } else if (currentRow > 0) { - selectedIndex = (currentRow - 1) * gridColumns + (gridColumns - 1); - } else { - const totalRows = Math.ceil(results.length / gridColumns); - const lastRowIndex = (totalRows - 1) * gridColumns + (gridColumns - 1); - selectedIndex = Math.min(lastRowIndex, results.length - 1); - } - } + EmojiProvider { + id: emojiProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: EmojiProvider"); } + } - function selectNextColumn() { - if (results.length > 0 && isGridView) { - const currentRow = Math.floor(selectedIndex / gridColumns); - const currentCol = selectedIndex % gridColumns; - const itemsInCurrentRow = Math.min(gridColumns, results.length - currentRow * gridColumns); - - if (currentCol < itemsInCurrentRow - 1) { - selectedIndex = currentRow * gridColumns + (currentCol + 1); - } else { - const totalRows = Math.ceil(results.length / gridColumns); - if (currentRow < totalRows - 1) { - selectedIndex = (currentRow + 1) * gridColumns; - } else { - selectedIndex = 0; - } - } - } + CalculatorProvider { + id: calcProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: CalculatorProvider"); } + } - function activate() { - if (results.length > 0 && results[selectedIndex]) { - const item = results[selectedIndex]; - const provider = item.provider || currentProvider; - - // Check if auto-paste is enabled and provider/item supports it - if (Settings.data.appLauncher.autoPasteClipboard && provider && provider.supportsAutoPaste && item.autoPasteText) { - if (item.onAutoPaste) - item.onAutoPaste(); - closeImmediately(); - Qt.callLater(() => { - ClipboardService.pasteText(item.autoPasteText); - }); - return; - } - - if (item.onActivate) - item.onActivate(); - } + SettingsProvider { + id: settingsProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: SettingsProvider"); } + } - function checkKey(event, settingName) { - return Keybinds.checkKey(event, settingName, Settings); + SessionProvider { + id: sessionProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: SessionProvider"); } + } - // Keyboard handler - function handleKeyPress(event) { - if (checkKey(event, 'escape')) { - close(); - event.accepted = true; - return; - } - - if (checkKey(event, 'enter')) { - activate(); - event.accepted = true; - return; - } - - if (checkKey(event, 'up')) { - if (!isSingleView) { - isGridView ? selectPreviousRow() : selectPreviousWrapped(); - } - event.accepted = true; - return; - } - - if (checkKey(event, 'down')) { - if (!isSingleView) { - isGridView ? selectNextRow() : selectNextWrapped(); - } - event.accepted = true; - return; - } - - if (checkKey(event, 'left')) { - if (isGridView) { - selectPreviousColumn(); - event.accepted = true; - return; - } - } - - if (checkKey(event, 'right')) { - if (isGridView) { - selectNextColumn(); - event.accepted = true; - return; - } - } - - // Static bindings - switch (event.key) { - case Qt.Key_Tab: - if (showProviderCategories) { - var cats = providerCategories; - var idx = cats.indexOf(currentProvider.selectedCategory); - currentProvider.selectCategory(cats[(idx + 1) % cats.length]); - } else { - selectNextWrapped(); - } - event.accepted = true; - break; - case Qt.Key_Backtab: - if (showProviderCategories) { - var cats2 = providerCategories; - var idx2 = cats2.indexOf(currentProvider.selectedCategory); - currentProvider.selectCategory(cats2[((idx2 - 1) % cats2.length + cats2.length) % cats2.length]); - } else { - selectPreviousWrapped(); - } - event.accepted = true; - break; - case Qt.Key_Home: - selectFirst(); - event.accepted = true; - break; - case Qt.Key_End: - selectLast(); - event.accepted = true; - break; - case Qt.Key_PageUp: - selectPreviousPage(); - event.accepted = true; - break; - case Qt.Key_PageDown: - selectNextPage(); - event.accepted = true; - break; - case Qt.Key_Delete: - if (selectedIndex >= 0 && results && results[selectedIndex]) { - var item = results[selectedIndex]; - var provider = item.provider || currentProvider; - if (provider && provider.canDeleteItem && provider.canDeleteItem(item)) - provider.deleteItem(item); - } - event.accepted = true; - break; - } + WindowsProvider { + id: windowsProvider + Component.onCompleted: { + registerProvider(this); + Logger.d("Launcher", "Registered: WindowsProvider"); } + } + + // ==================== UI Content ==================== + + opacity: resultsReady ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + HoverHandler { + id: globalHoverHandler + enabled: !Settings.data.appLauncher.ignoreMouseInput + + onPointChanged: { + if (!root.mouseTrackingReady) { + return; + } + + if (!root.globalMouseInitialized) { + root.globalLastMouseX = point.position.x; + root.globalLastMouseY = point.position.y; + root.globalMouseInitialized = true; + return; + } + + const deltaX = Math.abs(point.position.x - root.globalLastMouseX); + const deltaY = Math.abs(point.position.y - root.globalLastMouseY); + if (deltaX + deltaY >= 5) { + root.ignoreMouseHover = false; + root.globalLastMouseX = point.position.x; + root.globalLastMouseY = point.position.y; + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.topMargin: Style.marginL + anchors.bottomMargin: Style.marginL + spacing: Style.marginL + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: Style.marginL + Layout.rightMargin: Style.marginL + spacing: Style.marginS + + NTextInput { + id: searchInput + Layout.fillWidth: true + radius: Style.iRadiusM + text: root.searchText + placeholderText: I18n.tr("placeholders.search-launcher") + fontSize: Style.fontSizeM + onTextChanged: root.searchText = text - // ----------------------- - // Provider components - // ----------------------- - ApplicationsProvider { - id: appsProvider Component.onCompleted: { - registerProvider(this); - Logger.d("Launcher", "Registered: ApplicationsProvider"); + if (searchInput.inputItem) { + searchInput.inputItem.forceActiveFocus(); + searchInput.inputItem.Keys.onPressed.connect(function (event) { + root.handleKeyPress(event); + }); + } } + } + + NIconButton { + visible: root.showLayoutToggle + icon: Settings.data.appLauncher.viewMode === "grid" ? "layout-list" : "layout-grid" + tooltipText: Settings.data.appLauncher.viewMode === "grid" ? I18n.tr("tooltips.list-view") : I18n.tr("tooltips.grid-view") + customRadius: Style.iRadiusM + Layout.preferredWidth: searchInput.height + Layout.preferredHeight: searchInput.height + onClicked: Settings.data.appLauncher.viewMode = Settings.data.appLauncher.viewMode === "grid" ? "list" : "grid" + } } - ClipboardProvider { - id: clipProvider - Component.onCompleted: { - if (Settings.data.appLauncher.enableClipboardHistory) { - registerProvider(this); - Logger.d("Launcher", "Registered: ClipboardProvider"); - } + // Unified category tabs (works with any provider that has categories) + NTabBar { + id: categoryTabs + visible: root.showProviderCategories + Layout.fillWidth: true + Layout.leftMargin: Style.marginL + Layout.rightMargin: Style.marginL + margins: 0 + border.color: Style.boxBorderColor + border.width: Style.borderS + + property int computedCurrentIndex: visible && root.providerCategories.length > 0 ? root.providerCategories.indexOf(root.currentProvider.selectedCategory) : 0 + currentIndex: computedCurrentIndex + + Repeater { + model: root.providerCategories + NTabButton { + required property string modelData + required property int index + icon: root.currentProvider.categoryIcons ? (root.currentProvider.categoryIcons[modelData] || "star") : "star" + tooltipText: root.currentProvider.getCategoryName ? root.currentProvider.getCategoryName(modelData) : modelData + tabIndex: index + checked: categoryTabs.currentIndex === index + onClicked: root.currentProvider.selectCategory(modelData) } + } } - CommandProvider { - id: cmdProvider - Component.onCompleted: { - registerProvider(this); - Logger.d("Launcher", "Registered: CommandProvider"); - } + // Results view + Loader { + id: resultsViewLoader + Layout.fillWidth: true + Layout.leftMargin: Style.marginL + Layout.rightMargin: Style.marginL + Layout.fillHeight: true + sourceComponent: root.isSingleView ? singleViewComponent : (root.isGridView ? gridViewComponent : listViewComponent) } - EmojiProvider { - id: emojiProvider - Component.onCompleted: { - registerProvider(this); - Logger.d("Launcher", "Registered: EmojiProvider"); + // -------------------------- + // LIST VIEW + Component { + id: listViewComponent + NListView { + id: resultsList + + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AlwaysOff + reserveScrollbarSpace: false + gradientColor: Settings.data.ui.panelBackgroundOpacity < 1 ? "transparent" : Color.mSurface + wheelScrollMultiplier: 4.0 + + width: parent.width + height: parent.height + spacing: Style.marginS + model: root.results + currentIndex: root.selectedIndex + cacheBuffer: resultsList.height * 2 + interactive: !Settings.data.appLauncher.ignoreMouseInput + onCurrentIndexChanged: { + cancelFlick(); + if (currentIndex >= 0) { + positionViewAtIndex(currentIndex, ListView.Contain); + } } + onModelChanged: {} + + delegate: LauncherListDelegate { + launcher: root + } + } } - CalculatorProvider { - id: calcProvider - Component.onCompleted: { - registerProvider(this); - Logger.d("Launcher", "Registered: CalculatorProvider"); - } - } + // -------------------------- + // SINGLE ITEM VIEW + Component { + id: singleViewComponent - SettingsProvider { - id: settingsProvider - Component.onCompleted: { - registerProvider(this); - Logger.d("Launcher", "Registered: SettingsProvider"); - } - } + Item { + Layout.fillWidth: true + Layout.fillHeight: true - SessionProvider { - id: sessionProvider - Component.onCompleted: { - registerProvider(this); - Logger.d("Launcher", "Registered: SessionProvider"); - } - } + NBox { + anchors.fill: parent + color: Color.mSurfaceVariant + forceOpaque: true + Layout.fillWidth: true + Layout.fillHeight: true - WindowsProvider { - id: windowsProvider - Component.onCompleted: { - registerProvider(this); - Logger.d("Launcher", "Registered: WindowsProvider"); - } - } + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL + Layout.fillWidth: true + Layout.fillHeight: true - // ==================== UI Content ==================== - - opacity: resultsReady ? 1.0 : 0.0 - - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - easing.type: Easing.OutCirc - } - } - - HoverHandler { - id: globalHoverHandler - enabled: !Settings.data.appLauncher.ignoreMouseInput - - onPointChanged: { - if (!root.mouseTrackingReady) { - return; + Item { + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + NText { + text: root.results.length > 0 ? root.results[0].name : "" + pointSize: Style.fontSizeL + font.weight: Font.Bold + color: Color.mPrimary + } } - if (!root.globalMouseInitialized) { - root.globalLastMouseX = point.position.x; - root.globalLastMouseY = point.position.y; - root.globalMouseInitialized = true; - return; + NScrollView { + id: descriptionScrollView + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.topMargin: Style.fontSizeL + Style.marginXL + Layout.fillWidth: true + Layout.fillHeight: true + horizontalPolicy: ScrollBar.AlwaysOff + reserveScrollbarSpace: false + + NText { + width: descriptionScrollView.availableWidth + text: root.results.length > 0 ? root.results[0].description : "" + pointSize: Style.fontSizeM + font.weight: Font.Bold + color: Color.mOnSurface + horizontalAlignment: Text.AlignHLeft + verticalAlignment: Text.AlignTop + wrapMode: Text.Wrap + markdownTextEnabled: true + } + } + } + } + } + } + + // // -------------------------- + // GRID VIEW + Component { + id: gridViewComponent + NGridView { + id: resultsGrid + + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AlwaysOff + reserveScrollbarSpace: false + gradientColor: Settings.data.ui.panelBackgroundOpacity < 1 ? "transparent" : Color.mSurface + wheelScrollMultiplier: 4.0 + trackedSelectionIndex: root.selectedIndex + + width: parent.width + height: parent.height + cellWidth: parent.width / root.targetGridColumns + cellHeight: { + var cellWidth = parent.width / root.targetGridColumns; + // Use provider's preferred ratio if available + if (root.currentProvider && root.currentProvider.preferredGridCellRatio) { + return cellWidth * root.currentProvider.preferredGridCellRatio; + } + return cellWidth; + } + leftMargin: 0 + rightMargin: 0 + topMargin: 0 + bottomMargin: 0 + model: root.results + cacheBuffer: resultsGrid.height * 2 + keyNavigationEnabled: false + focus: false + interactive: !Settings.data.appLauncher.ignoreMouseInput + + // Completely disable GridView key handling + Keys.enabled: false + + // Handle scrolling to show selected item when it changes + Connections { + target: root + enabled: root.isGridView + function onSelectedIndexChanged() { + if (!root.isGridView || root.selectedIndex < 0 || !resultsGrid) { + return; } - const deltaX = Math.abs(point.position.x - root.globalLastMouseX); - const deltaY = Math.abs(point.position.y - root.globalLastMouseY); - if (deltaX + deltaY >= 5) { - root.ignoreMouseHover = false; - root.globalLastMouseX = point.position.x; - root.globalLastMouseY = point.position.y; - } + Qt.callLater(() => { + if (root.isGridView && resultsGrid && resultsGrid.cancelFlick) { + resultsGrid.cancelFlick(); + resultsGrid.positionViewAtIndex(root.selectedIndex, GridView.Contain); + } + }); + } } + + delegate: LauncherGridDelegate { + launcher: root + } + } } ColumnLayout { - anchors.fill: parent - anchors.topMargin: Style.marginL - anchors.bottomMargin: Style.marginL - spacing: Style.marginL + Layout.leftMargin: Style.marginL + Layout.rightMargin: Style.marginL - RowLayout { - Layout.fillWidth: true - Layout.leftMargin: Style.marginL - Layout.rightMargin: Style.marginL - spacing: Style.marginS + NDivider { + Layout.fillWidth: true + Layout.bottomMargin: Style.marginS + } - NTextInput { - id: searchInput - Layout.fillWidth: true - radius: Style.iRadiusM - text: root.searchText - placeholderText: I18n.tr("placeholders.search-launcher") - fontSize: Style.fontSizeM - onTextChanged: root.searchText = text - - Component.onCompleted: { - if (searchInput.inputItem) { - searchInput.inputItem.forceActiveFocus(); - searchInput.inputItem.Keys.onPressed.connect(function (event) { - root.handleKeyPress(event); - }); - } - } + NText { + Layout.fillWidth: true + text: { + if (root.results.length === 0) { + if (root.searchText) { + return I18n.tr("common.no-results"); } - - NIconButton { - id: dateFilterButton - visible: root.activeProvider && root.activeProvider.hasDateFilter - icon: root.activeProvider && root.activeProvider.dateFilter !== "all" ? "filter-check" : "filter" - tooltipText: I18n.tr("tooltips.date-filter") || "Date Filter" - customRadius: Style.iRadiusM - Layout.preferredWidth: searchInput.height - Layout.preferredHeight: searchInput.height - - onClicked: { - dateFilterMenu.openAtItem(dateFilterButton, 0, dateFilterButton.height + Style.marginS); - } - - NContextMenu { - id: dateFilterMenu - parent: Overlay.overlay - model: root.activeProvider ? root.activeProvider.availableDateFilters : [] - onTriggered: action => { - if (root.activeProvider && root.activeProvider.selectDateFilter) { - root.activeProvider.selectDateFilter(action); - } - } - } - } - - NIconButton { - visible: root.showLayoutToggle - icon: Settings.data.appLauncher.viewMode === "grid" ? "layout-list" : "layout-grid" - tooltipText: Settings.data.appLauncher.viewMode === "grid" ? I18n.tr("tooltips.list-view") : I18n.tr("tooltips.grid-view") - customRadius: Style.iRadiusM - Layout.preferredWidth: searchInput.height - Layout.preferredHeight: searchInput.height - onClicked: Settings.data.appLauncher.viewMode = Settings.data.appLauncher.viewMode === "grid" ? "list" : "grid" - } - } - - // Unified category tabs (works with any provider that has categories) - NTabBar { - id: categoryTabs - visible: root.showProviderCategories - Layout.fillWidth: true - Layout.leftMargin: Style.marginL - Layout.rightMargin: Style.marginL - margins: 0 - border.color: Style.boxBorderColor - border.width: Style.borderS - - property int computedCurrentIndex: visible && root.providerCategories.length > 0 ? root.providerCategories.indexOf(root.currentProvider.selectedCategory) : 0 - currentIndex: computedCurrentIndex - - Repeater { - model: root.providerCategories - NTabButton { - required property string modelData - required property int index - icon: root.currentProvider.categoryIcons ? (root.currentProvider.categoryIcons[modelData] || "star") : "star" - tooltipText: root.currentProvider.getCategoryName ? root.currentProvider.getCategoryName(modelData) : modelData - tabIndex: index - checked: categoryTabs.currentIndex === index - onClicked: root.currentProvider.selectCategory(modelData) - } - } - } - - // Results view - Loader { - id: resultsViewLoader - Layout.fillWidth: true - Layout.leftMargin: Style.marginL - Layout.rightMargin: Style.marginL - Layout.fillHeight: true - sourceComponent: root.isSingleView ? singleViewComponent : (root.isGridView ? gridViewComponent : listViewComponent) - } - - // -------------------------- - // LIST VIEW - Component { - id: listViewComponent - NListView { - id: resultsList - - horizontalPolicy: ScrollBar.AlwaysOff - verticalPolicy: ScrollBar.AlwaysOff - reserveScrollbarSpace: false - gradientColor: Color.mSurfaceVariant - wheelScrollMultiplier: 4.0 - - width: parent.width - height: parent.height - spacing: Style.marginS - model: root.results - currentIndex: root.selectedIndex - cacheBuffer: resultsList.height * 2 - interactive: !Settings.data.appLauncher.ignoreMouseInput - onCurrentIndexChanged: { - cancelFlick(); - if (currentIndex >= 0) { - positionViewAtIndex(currentIndex, ListView.Contain); - } - } - onModelChanged: {} - - delegate: NBox { - id: entry - - property bool isSelected: (!modelData.isHeader) && ((!root.ignoreMouseHover && mouseArea.containsMouse) || (index === root.selectedIndex)) - - // Prepare item when it becomes visible (e.g., decode images) - Component.onCompleted: { - var provider = modelData.provider; - if (provider && provider.prepareItem) { - provider.prepareItem(modelData); - } - } - - width: resultsList.availableWidth - implicitHeight: modelData.isHeader ? Math.round(root.entryHeight * 0.45) : root.entryHeight - clip: true - color: entry.isSelected ? Color.mHover : Color.mSurface - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - easing.type: Easing.OutCirc - } - } - - ColumnLayout { - id: contentLayout - anchors.fill: parent - anchors.margins: modelData.isHeader ? Style.marginXXS : (root.isCompactDensity ? Style.marginXS : Style.marginM) - spacing: root.isCompactDensity ? Style.marginXS : Style.marginM - - // Top row - Main entry content with action buttons - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: true - spacing: root.isCompactDensity ? Style.marginS : Style.marginM - - // Icon badge or Image preview or Emoji - Item { - visible: !modelData.hideIcon - Layout.preferredWidth: modelData.hideIcon ? 0 : root.badgeSize - Layout.preferredHeight: modelData.hideIcon ? 0 : root.badgeSize - - // Icon background - Rectangle { - anchors.fill: parent - radius: Style.radiusXS - color: modelData.colorHex ? modelData.colorHex : Color.mSurfaceVariant - visible: (Settings.data.appLauncher.showIconBackground && !modelData.isImage) || !!modelData.colorHex - border.width: modelData.colorHex ? 1 : 0 - border.color: modelData.colorHex ? Color.mOnSurfaceVariant : "transparent" - } - - // Image preview - uses provider's getImageUrl if available - NImageRounded { - id: imagePreview - anchors.fill: parent - visible: !!modelData.isImage && !modelData.displayString - radius: Style.radiusXS - borderColor: Color.mOnSurface - borderWidth: Style.borderM - imageFillMode: Image.PreserveAspectCrop - - // Use provider's image revision for reactive updates - readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0 - - // Get image URL from provider - imagePath: { - _rev; - var provider = modelData.provider; - if (provider && provider.getImageUrl) { - return provider.getImageUrl(modelData); - } - return ""; - } - - Rectangle { - anchors.fill: parent - visible: parent.status === Image.Loading - color: Color.mSurfaceVariant - - BusyIndicator { - anchors.centerIn: parent - running: true - width: Style.baseWidgetSize * 0.5 - height: width - } - } - - onStatusChanged: status => { - if (status === Image.Error) { - iconLoader.visible = true; - imagePreview.visible = false; - } - } - } - - Loader { - id: iconLoader - anchors.fill: parent - anchors.margins: Style.marginXS - - visible: (!modelData.isImage && !modelData.displayString && !modelData.colorHex) || (!!modelData.isImage && imagePreview.status === Image.Error) - active: visible - - sourceComponent: Component { - Loader { - anchors.fill: parent - sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? tablerIconComponent : systemIconComponent - } - } - - Component { - id: tablerIconComponent - NIcon { - icon: modelData.icon - pointSize: Style.fontSizeXXXL - visible: modelData.icon && !modelData.displayString - color: (entry.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface - } - } - - Component { - id: systemIconComponent - IconImage { - anchors.fill: parent - source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : "" - visible: modelData.icon && source !== "" && !modelData.displayString - asynchronous: true - } - } - } - - // String display - takes precedence when displayString is present - NText { - id: stringDisplay - anchors.centerIn: parent - visible: !!modelData.displayString || (!imagePreview.visible && !iconLoader.visible && !modelData.colorHex) - text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?") - pointSize: modelData.displayString ? (modelData.displayStringSize || Style.fontSizeXXXL) : Style.fontSizeXXL - font.weight: Style.fontWeightBold - color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary - } - - // Image type indicator overlay - Rectangle { - visible: !!modelData.isImage && imagePreview.visible - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.margins: 2 - width: formatLabel.width + Style.marginXS - height: formatLabel.height + Style.marginXXS - color: Color.mSurfaceVariant - radius: Style.radiusXXS - NText { - id: formatLabel - anchors.centerIn: parent - text: { - if (!modelData.isImage) - return ""; - const desc = modelData.description || ""; - const parts = desc.split(" \u2022 "); - return parts[0] || "IMG"; - } - pointSize: Style.fontSizeXXS - color: Color.mOnSurfaceVariant - } - } - - // Badge icon overlay (generic indicator for any provider) - Rectangle { - visible: !!modelData.badgeIcon - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.margins: 2 - width: height - height: Style.fontSizeM + Style.marginXS - color: Color.mSurfaceVariant - radius: Style.radiusXXS - NIcon { - anchors.centerIn: parent - icon: modelData.badgeIcon || "" - pointSize: Style.fontSizeS - color: Color.mOnSurfaceVariant - } - } - } - - // Text content - ColumnLayout { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: modelData.isHeader ? Style.marginM : 0 - spacing: 0 - - NText { - text: modelData.name || "Unknown" - pointSize: modelData.isHeader ? Style.fontSizeM : Style.fontSizeL - font.weight: Style.fontWeightBold - color: entry.isSelected ? Color.mOnHover : Color.mOnSurface - elide: Text.ElideRight - maximumLineCount: 1 - wrapMode: Text.Wrap - clip: true - Layout.fillWidth: true - } - - NText { - text: modelData.description || "" - pointSize: Style.fontSizeS - color: entry.isSelected ? Color.mOnHover : Color.mOnSurfaceVariant - elide: Text.ElideRight - maximumLineCount: 1 - Layout.fillWidth: true - visible: text !== "" && !root.isCompactDensity - } - } - - // Action buttons row - dynamically populated from provider - RowLayout { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - spacing: Style.marginXS - visible: entry.isSelected && itemActions.length > 0 - - property var itemActions: { - if (!entry.isSelected) - return []; - var provider = modelData.provider || root.currentProvider; - if (provider && provider.getItemActions) { - return provider.getItemActions(modelData); - } - return []; - } - - Repeater { - model: parent.itemActions - NIconButton { - icon: modelData.icon - baseSize: Style.baseWidgetSize * 0.75 - tooltipText: modelData.tooltip - z: 1 - onClicked: { - if (modelData.action) { - modelData.action(); - } - } - } - } - } - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - z: -1 - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - enabled: !Settings.data.appLauncher.ignoreMouseInput - onEntered: { - if (!root.ignoreMouseHover && !modelData.isHeader) { - root.selectedIndex = index; - } - } - onClicked: mouse => { - if (mouse.button === Qt.LeftButton && !modelData.isHeader) { - root.selectedIndex = index; - root.activate(); - mouse.accepted = true; - } - } - acceptedButtons: Qt.LeftButton - } - } - } - } - - // -------------------------- - // SINGLE ITEM VIEW - Component { - id: singleViewComponent - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - NBox { - anchors.fill: parent - color: Color.mSurfaceVariant - Layout.fillWidth: true - Layout.fillHeight: true - - ColumnLayout { - anchors.fill: parent - anchors.margins: Style.marginL - Layout.fillWidth: true - Layout.fillHeight: true - - Item { - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - NText { - text: root.results.length > 0 ? root.results[0].name : "" - pointSize: Style.fontSizeL - font.weight: Font.Bold - color: Color.mPrimary - } - } - - NScrollView { - id: descriptionScrollView - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Layout.topMargin: Style.fontSizeL + Style.marginXL - Layout.fillWidth: true - Layout.fillHeight: true - horizontalPolicy: ScrollBar.AlwaysOff - reserveScrollbarSpace: false - - NText { - width: descriptionScrollView.availableWidth - text: root.results.length > 0 ? root.results[0].description : "" - pointSize: Style.fontSizeM - font.weight: Font.Bold - color: Color.mOnSurface - horizontalAlignment: Text.AlignHLeft - verticalAlignment: Text.AlignTop - wrapMode: Text.Wrap - markdownTextEnabled: true - } - } - } - } - } - } - - // -------------------------- - // GRID VIEW - Component { - id: gridViewComponent - NGridView { - id: resultsGrid - - horizontalPolicy: ScrollBar.AlwaysOff - verticalPolicy: ScrollBar.AlwaysOff - reserveScrollbarSpace: false - gradientColor: Color.mSurfaceVariant - wheelScrollMultiplier: 4.0 - trackedSelectionIndex: root.selectedIndex - - width: parent.width - height: parent.height - cellWidth: parent.width / root.targetGridColumns - cellHeight: { - var cellWidth = parent.width / root.targetGridColumns; - // Use provider's preferred ratio if available - if (root.currentProvider && root.currentProvider.preferredGridCellRatio) { - return cellWidth * root.currentProvider.preferredGridCellRatio; - } - return cellWidth; - } - leftMargin: 0 - rightMargin: 0 - topMargin: 0 - bottomMargin: 0 - model: root.results - cacheBuffer: resultsGrid.height * 2 - keyNavigationEnabled: false - focus: false - interactive: !Settings.data.appLauncher.ignoreMouseInput - - // Completely disable GridView key handling - Keys.enabled: false - - Component.onCompleted: root.gridColumns = root.targetGridColumns - onWidthChanged: root.gridColumns = root.targetGridColumns - onModelChanged: {} - - // Handle scrolling to show selected item when it changes - Connections { - target: root - enabled: root.isGridView - function onSelectedIndexChanged() { - if (!root.isGridView || root.selectedIndex < 0 || !resultsGrid) { - return; - } - - Qt.callLater(() => { - if (root.isGridView && resultsGrid && resultsGrid.cancelFlick) { - resultsGrid.cancelFlick(); - resultsGrid.positionViewAtIndex(root.selectedIndex, GridView.Contain); - } - }); - } - } - - delegate: Item { - id: gridEntryContainer - width: resultsGrid.cellWidth - height: resultsGrid.cellHeight - - property bool isSelected: (!root.ignoreMouseHover && mouseArea.containsMouse) || (index === root.selectedIndex) - - // Prepare item when it becomes visible (e.g., decode images) - Component.onCompleted: { - var provider = modelData.provider; - if (provider && provider.prepareItem) { - provider.prepareItem(modelData); - } - } - - NBox { - id: gridEntry - anchors.fill: parent - anchors.margins: Style.marginXXS - color: gridEntryContainer.isSelected ? Color.mHover : Color.mSurface - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - easing.type: Easing.OutCirc - } - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: root.isCompactDensity ? Style.marginXS : Style.marginS - anchors.bottomMargin: root.isCompactDensity ? Style.marginXS : Style.marginS - spacing: root.isCompactDensity ? 0 : Style.marginXXS - - // Icon badge or Image preview or Emoji - Item { - // Use consistent 65% sizing for all items - Layout.preferredWidth: Math.round(gridEntry.width * 0.65) - Layout.preferredHeight: Math.round(gridEntry.width * 0.65) - Layout.alignment: Qt.AlignHCenter - - // Icon background - Rectangle { - anchors.fill: parent - radius: Style.radiusM - color: Color.mSurfaceVariant - visible: Settings.data.appLauncher.showIconBackground && !modelData.isImage - } - - // Image preview - uses provider's getImageUrl if available - NImageRounded { - id: gridImagePreview - anchors.fill: parent - visible: !!modelData.isImage && !modelData.displayString - radius: Style.radiusM - - // Use provider's image revision for reactive updates - readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0 - - // Get image URL from provider - imagePath: { - _rev; - var provider = modelData.provider; - if (provider && provider.getImageUrl) { - return provider.getImageUrl(modelData); - } - return ""; - } - - Rectangle { - anchors.fill: parent - visible: parent.status === Image.Loading - color: Color.mSurfaceVariant - - BusyIndicator { - anchors.centerIn: parent - running: true - width: Style.baseWidgetSize * 0.5 - height: width - } - } - - onStatusChanged: status => { - if (status === Image.Error) { - gridIconLoader.visible = true; - gridImagePreview.visible = false; - } - } - } - - Loader { - id: gridIconLoader - anchors.fill: parent - anchors.margins: Style.marginXS - - visible: (!modelData.isImage && !modelData.displayString) || (!!modelData.isImage && gridImagePreview.status === Image.Error) - active: visible - - sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? gridTablerIconComponent : gridSystemIconComponent - - Component { - id: gridTablerIconComponent - NIcon { - icon: modelData.icon - pointSize: Style.fontSizeXXXL - visible: modelData.icon && !modelData.displayString - color: (gridEntryContainer.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface - } - } - - Component { - id: gridSystemIconComponent - IconImage { - anchors.fill: parent - source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : "" - visible: modelData.icon && source !== "" && !modelData.displayString - asynchronous: true - } - } - } - - // String display - NText { - id: gridStringDisplay - anchors.centerIn: parent - visible: !!modelData.displayString || (!gridImagePreview.visible && !gridIconLoader.visible) - text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?") - pointSize: { - if (modelData.displayString) { - // Use custom size if provided, otherwise default scaling - if (modelData.displayStringSize) { - return modelData.displayStringSize * Style.uiScaleRatio; - } - if (root.providerHasDisplayString) { - // Scale with cell width but cap at reasonable maximum - const cellBasedSize = gridEntry.width * 0.4; - const maxSize = Style.fontSizeXXXL * Style.uiScaleRatio; - return Math.min(cellBasedSize, maxSize); - } - return Style.fontSizeXXL * 2 * Style.uiScaleRatio; - } - // Scale font size relative to cell width for low res, but cap at maximum - const cellBasedSize = gridEntry.width * 0.25; - const baseSize = Style.fontSizeXL * Style.uiScaleRatio; - const maxSize = Style.fontSizeXXL * Style.uiScaleRatio; - return Math.min(Math.max(cellBasedSize, baseSize), maxSize); - } - font.weight: Style.fontWeightBold - color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary - } - - // Badge icon overlay (generic indicator for any provider) - Rectangle { - visible: !!modelData.badgeIcon - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.margins: 2 - width: height - height: Style.fontSizeM + Style.marginXS - color: Color.mSurfaceVariant - radius: Style.radiusXXS - NIcon { - anchors.centerIn: parent - icon: modelData.badgeIcon || "" - pointSize: Style.fontSizeS - color: Color.mOnSurfaceVariant - } - } - } - - // Text content (hidden when hideLabel is true) - NText { - visible: !modelData.hideLabel - text: modelData.name || "Unknown" - pointSize: { - if (root.providerHasDisplayString && modelData.displayString) { - return Style.fontSizeS * Style.uiScaleRatio; - } - // Scale font size relative to cell width for low res, but cap at maximum - const cellBasedSize = gridEntry.width * 0.12; - const baseSize = Style.fontSizeS * Style.uiScaleRatio; - const maxSize = Style.fontSizeM * Style.uiScaleRatio; - return Math.min(Math.max(cellBasedSize, baseSize), maxSize); - } - font.weight: Style.fontWeightSemiBold - color: gridEntryContainer.isSelected ? Color.mOnHover : Color.mOnSurface - elide: Text.ElideRight - Layout.fillWidth: true - Layout.maximumWidth: gridEntry.width - 8 - Layout.leftMargin: (root.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0 - Layout.rightMargin: (root.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0 - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.NoWrap - maximumLineCount: 1 - } - } - - // Action buttons (overlay in top-right corner) - dynamically populated from provider - Row { - visible: gridEntryContainer.isSelected && gridItemActions.length > 0 - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: Style.marginXS - z: 10 - spacing: Style.marginXXS - - property var gridItemActions: { - if (!gridEntryContainer.isSelected) - return []; - var provider = modelData.provider || root.currentProvider; - if (provider && provider.getItemActions) { - return provider.getItemActions(modelData); - } - return []; - } - - Repeater { - model: parent.gridItemActions - NIconButton { - icon: modelData.icon - baseSize: Style.baseWidgetSize * 0.75 - tooltipText: modelData.tooltip - z: 11 - onClicked: { - if (modelData.action) { - modelData.action(); - } - } - } - } - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - z: -1 - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - enabled: !Settings.data.appLauncher.ignoreMouseInput - - onEntered: { - if (!root.ignoreMouseHover && !modelData.isHeader) { - root.selectedIndex = index; - } - } - onClicked: mouse => { - if (mouse.button === Qt.LeftButton && !modelData.isHeader) { - root.selectedIndex = index; - root.activate(); - mouse.accepted = true; - } - } - acceptedButtons: Qt.LeftButton - } - } - } - } - - ColumnLayout { - Layout.leftMargin: Style.marginL - Layout.rightMargin: Style.marginL - - NDivider { - Layout.fillWidth: true - Layout.bottomMargin: Style.marginS - } - - NText { - Layout.fillWidth: true - text: { - if (root.results.length === 0) { - if (root.searchText) { - return I18n.tr("common.no-results"); - } - // Use provider's empty browsing message if available - var provider = root.currentProvider; - if (provider && provider.emptyBrowsingMessage) { - return provider.emptyBrowsingMessage; - } - return ""; - } - var prefix = root.activeProvider && root.activeProvider.name ? root.activeProvider.name + ": " : ""; - return prefix + I18n.trp("common.result-count", root.results.length); - } - pointSize: Style.fontSizeXS - color: Color.mOnSurfaceVariant - horizontalAlignment: Text.AlignCenter + // Use provider's empty browsing message if available + var provider = root.currentProvider; + if (provider && provider.emptyBrowsingMessage) { + return provider.emptyBrowsingMessage; } + return ""; + } + var prefix = root.activeProvider && root.activeProvider.name ? root.activeProvider.name + ": " : ""; + return prefix + I18n.trp("common.result-count", root.results.length); } + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignCenter + } } + } } diff --git a/Modules/Panels/Launcher/LauncherGridDelegate.qml b/Modules/Panels/Launcher/LauncherGridDelegate.qml new file mode 100644 index 000000000..e6bc3f84c --- /dev/null +++ b/Modules/Panels/Launcher/LauncherGridDelegate.qml @@ -0,0 +1,272 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets + +import qs.Commons +import qs.Widgets + +Item { + id: gridEntryContainer + + required property var modelData + required property int index + required property var launcher + + width: GridView.view.cellWidth + height: GridView.view.cellHeight + + property bool isSelected: (!launcher.ignoreMouseHover && mouseArea.containsMouse) || (index === launcher.selectedIndex) + + // Prepare item when it becomes visible (e.g., decode images) + Component.onCompleted: { + var provider = modelData.provider; + if (provider && provider.prepareItem) { + provider.prepareItem(modelData); + } + } + + NBox { + id: gridEntry + anchors.fill: parent + anchors.margins: Style.marginXXS + color: gridEntryContainer.isSelected ? Color.mHover : Color.mSurfaceVariant + forceOpaque: gridEntryContainer.isSelected + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: launcher.isCompactDensity ? Style.marginXS : Style.marginS + anchors.bottomMargin: launcher.isCompactDensity ? Style.marginXS : Style.marginS + spacing: launcher.isCompactDensity ? 0 : Style.marginXXS + + // Icon badge or Image preview or Emoji + Item { + // Size image at 65% of cell dimensions. + Layout.preferredWidth: Math.round(gridEntry.width * 0.65) + Layout.preferredHeight: Math.round(gridEntry.height * 0.65) + Layout.alignment: Qt.AlignHCenter + + // Icon background + Rectangle { + anchors.fill: parent + radius: Style.radiusM + color: Color.mSurfaceVariant + visible: Settings.data.appLauncher.showIconBackground && !modelData.isImage + } + + // Image preview - uses provider's getImageUrl if available + NImageRounded { + id: gridImagePreview + anchors.fill: parent + visible: !!modelData.isImage && !modelData.displayString + radius: Style.radiusM + + // Use provider's image revision for reactive updates + readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0 + + // Get image URL from provider + imagePath: { + _rev; + var provider = modelData.provider; + if (provider && provider.getImageUrl) { + return provider.getImageUrl(modelData); + } + return ""; + } + + Rectangle { + anchors.fill: parent + visible: parent.status === Image.Loading + color: Color.mSurfaceVariant + + BusyIndicator { + anchors.centerIn: parent + running: true + width: Style.baseWidgetSize * 0.5 + height: width + } + } + + onStatusChanged: status => { + if (status === Image.Error) { + gridIconLoader.visible = true; + gridImagePreview.visible = false; + } + } + } + + Loader { + id: gridIconLoader + anchors.fill: parent + anchors.margins: Style.marginXS + + visible: (!modelData.isImage && !modelData.displayString) || (!!modelData.isImage && gridImagePreview.status === Image.Error) + active: visible + + sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? gridTablerIconComponent : gridSystemIconComponent + + Component { + id: gridTablerIconComponent + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeXXXL + visible: modelData.icon && !modelData.displayString + color: (gridEntryContainer.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface + } + } + + Component { + id: gridSystemIconComponent + IconImage { + anchors.fill: parent + source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : "" + visible: modelData.icon && source !== "" && !modelData.displayString + asynchronous: true + } + } + } + + // String display + NText { + id: gridStringDisplay + anchors.centerIn: parent + visible: !!modelData.displayString || (!gridImagePreview.visible && !gridIconLoader.visible) + text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?") + pointSize: { + if (modelData.displayString) { + // Use custom size if provided, otherwise default scaling + if (modelData.displayStringSize) { + return modelData.displayStringSize * Style.uiScaleRatio; + } + if (launcher.providerHasDisplayString) { + // Scale with cell width but cap at reasonable maximum + const cellBasedSize = gridEntry.width * 0.4; + const maxSize = Style.fontSizeXXXL * Style.uiScaleRatio; + return Math.min(cellBasedSize, maxSize); + } + return Style.fontSizeXXL * 2 * Style.uiScaleRatio; + } + // Scale font size relative to cell width for low res, but cap at maximum + const cellBasedSize = gridEntry.width * 0.25; + const baseSize = Style.fontSizeXL * Style.uiScaleRatio; + const maxSize = Style.fontSizeXXL * Style.uiScaleRatio; + return Math.min(Math.max(cellBasedSize, baseSize), maxSize); + } + font.weight: Style.fontWeightBold + color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary + } + + // Badge icon overlay (generic indicator for any provider) + Rectangle { + visible: !!modelData.badgeIcon + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: 2 + width: height + height: Style.fontSizeM + Style.marginXS + color: Color.mSurfaceVariant + radius: Style.radiusXXS + NIcon { + anchors.centerIn: parent + icon: modelData.badgeIcon || "" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Text content (hidden when hideLabel is true) + NText { + visible: !modelData.hideLabel + text: modelData.name || "Unknown" + pointSize: { + if (launcher.providerHasDisplayString && modelData.displayString) { + return Style.fontSizeS * Style.uiScaleRatio; + } + // Scale font size relative to cell width for low res, but cap at maximum + const cellBasedSize = gridEntry.width * 0.1; + const baseSize = Style.fontSizeXS * Style.uiScaleRatio; + const maxSize = Style.fontSizeS * Style.uiScaleRatio; + return Math.min(Math.max(cellBasedSize, baseSize), maxSize); + } + font.weight: Style.fontWeightSemiBold + color: gridEntryContainer.isSelected ? Color.mOnHover : Color.mOnSurface + elide: Text.ElideRight + Layout.fillWidth: true + Layout.maximumWidth: gridEntry.width - 8 + Layout.leftMargin: (launcher.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0 + Layout.rightMargin: (launcher.providerHasDisplayString && modelData.displayString) ? Style.marginS : 0 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.NoWrap + maximumLineCount: 1 + } + } + + // Action buttons (overlay in top-right corner) - dynamically populated from provider + Row { + visible: gridEntryContainer.isSelected && gridItemActions.length > 0 + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginXS + z: 10 + spacing: Style.marginXXS + + property var gridItemActions: { + if (!gridEntryContainer.isSelected) + return []; + var provider = modelData.provider || launcher.currentProvider; + if (provider && provider.getItemActions) { + return provider.getItemActions(modelData); + } + return []; + } + + Repeater { + model: parent.gridItemActions + NIconButton { + required property var modelData + icon: modelData.icon + baseSize: Style.baseWidgetSize * 0.75 + tooltipText: modelData.tooltip + z: 11 + handleWheel: true + onClicked: { + if (modelData.action) { + modelData.action(); + } + } + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + z: -1 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + enabled: !Settings.data.appLauncher.ignoreMouseInput + + onEntered: { + if (!launcher.ignoreMouseHover) { + launcher.selectedIndex = gridEntryContainer.index; + } + } + onClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + launcher.selectedIndex = gridEntryContainer.index; + launcher.activate(); + mouse.accepted = true; + } + } + acceptedButtons: Qt.LeftButton + } +} diff --git a/Modules/Panels/Launcher/LauncherListDelegate.qml b/Modules/Panels/Launcher/LauncherListDelegate.qml new file mode 100644 index 000000000..63f1d767f --- /dev/null +++ b/Modules/Panels/Launcher/LauncherListDelegate.qml @@ -0,0 +1,284 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets + +import qs.Commons +import qs.Widgets + +NBox { + id: entry + + required property var modelData + required property int index + required property var launcher + + property bool isSelected: (!launcher.ignoreMouseHover && mouseArea.containsMouse) || (index === launcher.selectedIndex) + + width: ListView.view.width + implicitHeight: launcher.entryHeight + clip: true + color: entry.isSelected ? Color.mHover : Color.mSurfaceVariant + forceOpaque: entry.isSelected + + // Prepare item when it becomes visible (e.g., decode images) + Component.onCompleted: { + var provider = modelData.provider; + if (provider && provider.prepareItem) { + provider.prepareItem(modelData); + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCirc + } + } + + ColumnLayout { + id: contentLayout + anchors.fill: parent + anchors.margins: launcher.isCompactDensity ? Style.marginXS : Style.marginM + spacing: launcher.isCompactDensity ? Style.marginXS : Style.marginM + + // Top row - Main entry content with action buttons + RowLayout { + Layout.fillWidth: true + spacing: launcher.isCompactDensity ? Style.marginS : Style.marginM + + // Icon badge or Image preview or Emoji + Item { + visible: !modelData.hideIcon + Layout.preferredWidth: modelData.hideIcon ? 0 : launcher.badgeSize + Layout.preferredHeight: modelData.hideIcon ? 0 : launcher.badgeSize + + // Icon background + Rectangle { + anchors.fill: parent + radius: Style.radiusXS + color: Color.mSurfaceVariant + visible: Settings.data.appLauncher.showIconBackground && !modelData.isImage + } + + // Image preview - uses provider's getImageUrl if available + NImageRounded { + id: imagePreview + anchors.fill: parent + visible: !!modelData.isImage && !modelData.displayString + radius: Style.radiusXS + borderColor: Color.mOnSurface + borderWidth: Style.borderM + imageFillMode: Image.PreserveAspectCrop + + // Use provider's image revision for reactive updates + readonly property int _rev: modelData.provider && modelData.provider.imageRevision ? modelData.provider.imageRevision : 0 + + // Get image URL from provider + imagePath: { + _rev; + var provider = modelData.provider; + if (provider && provider.getImageUrl) { + return provider.getImageUrl(modelData); + } + return ""; + } + + Rectangle { + anchors.fill: parent + visible: parent.status === Image.Loading + color: Color.mSurfaceVariant + + BusyIndicator { + anchors.centerIn: parent + running: true + width: Style.baseWidgetSize * 0.5 + height: width + } + } + + onStatusChanged: status => { + if (status === Image.Error) { + iconLoader.visible = true; + imagePreview.visible = false; + } + } + } + + Loader { + id: iconLoader + anchors.fill: parent + anchors.margins: Style.marginXS + + visible: (!modelData.isImage && !modelData.displayString) || (!!modelData.isImage && imagePreview.status === Image.Error) + active: visible + + sourceComponent: Component { + Loader { + anchors.fill: parent + sourceComponent: Settings.data.appLauncher.iconMode === "tabler" && modelData.isTablerIcon ? tablerIconComponent : systemIconComponent + } + } + + Component { + id: tablerIconComponent + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeXXXL + visible: modelData.icon && !modelData.displayString + color: (entry.isSelected && !Settings.data.appLauncher.showIconBackground) ? Color.mOnHover : Color.mOnSurface + } + } + + Component { + id: systemIconComponent + IconImage { + anchors.fill: parent + source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : "" + visible: modelData.icon && source !== "" && !modelData.displayString + asynchronous: true + } + } + } + + // String display - takes precedence when displayString is present + NText { + id: stringDisplay + anchors.centerIn: parent + visible: !!modelData.displayString || (!imagePreview.visible && !iconLoader.visible) + text: modelData.displayString ? modelData.displayString : (modelData.name ? modelData.name.charAt(0).toUpperCase() : "?") + pointSize: modelData.displayString ? (modelData.displayStringSize || Style.fontSizeXXXL) : Style.fontSizeXXL + font.weight: Style.fontWeightBold + color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary + } + + // Image type indicator overlay + Rectangle { + visible: !!modelData.isImage && imagePreview.visible + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: 2 + width: formatLabel.width + Style.marginXS + height: formatLabel.height + Style.marginXXS + color: Color.mSurfaceVariant + radius: Style.radiusXXS + NText { + id: formatLabel + anchors.centerIn: parent + text: { + if (!modelData.isImage) + return ""; + const desc = modelData.description || ""; + const parts = desc.split(" \u2022 "); + return parts[0] || "IMG"; + } + pointSize: Style.fontSizeXXS + color: Color.mOnSurfaceVariant + } + } + + // Badge icon overlay (generic indicator for any provider) + Rectangle { + visible: !!modelData.badgeIcon + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: 2 + width: height + height: Style.fontSizeM + Style.marginXS + color: Color.mSurfaceVariant + radius: Style.radiusXXS + NIcon { + anchors.centerIn: parent + icon: modelData.badgeIcon || "" + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + } + + // Text content + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + NText { + text: modelData.name || "Unknown" + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: entry.isSelected ? Color.mOnHover : Color.mOnSurface + elide: Text.ElideRight + maximumLineCount: 1 + wrapMode: Text.Wrap + clip: true + Layout.fillWidth: true + } + + NText { + text: modelData.description || "" + pointSize: Style.fontSizeS + color: entry.isSelected ? Color.mOnHover : Color.mOnSurfaceVariant + elide: Text.ElideRight + maximumLineCount: 1 + Layout.fillWidth: true + visible: text !== "" && !launcher.isCompactDensity + } + } + + // Action buttons row - dynamically populated from provider + RowLayout { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + spacing: Style.marginXS + visible: entry.isSelected && itemActions.length > 0 + + property var itemActions: { + if (!entry.isSelected) + return []; + var provider = modelData.provider || launcher.currentProvider; + if (provider && provider.getItemActions) { + return provider.getItemActions(modelData); + } + return []; + } + + Repeater { + model: parent.itemActions + NIconButton { + required property var modelData + icon: modelData.icon + baseSize: Style.baseWidgetSize * 0.75 + tooltipText: modelData.tooltip + z: 1 + handleWheel: true + onClicked: { + if (modelData.action) { + modelData.action(); + } + } + } + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + z: -1 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + enabled: !Settings.data.appLauncher.ignoreMouseInput + onEntered: { + if (!launcher.ignoreMouseHover) { + launcher.selectedIndex = entry.index; + } + } + onClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + launcher.selectedIndex = entry.index; + launcher.activate(); + mouse.accepted = true; + } + } + acceptedButtons: Qt.LeftButton + } +} diff --git a/Modules/Panels/Launcher/LauncherOverlayWindow.qml b/Modules/Panels/Launcher/LauncherOverlayWindow.qml index 29ebf4638..84dc88b3a 100644 --- a/Modules/Panels/Launcher/LauncherOverlayWindow.qml +++ b/Modules/Panels/Launcher/LauncherOverlayWindow.qml @@ -11,386 +11,417 @@ import qs.Widgets // Standalone launcher window for Overlay layer mode. // This window appears above fullscreen windows and does not attach to the bar. Variants { - id: launcherVariants + id: launcherVariants - model: Quickshell.screens.filter(screen => Settings.data.appLauncher.overviewLayer) + model: Quickshell.screens.filter(screen => Settings.data.appLauncher.overviewLayer) - delegate: Loader { - id: windowLoader + delegate: Loader { + id: windowLoader - required property ShellScreen modelData + required property ShellScreen modelData - active: PanelService.overlayLauncherOpen && PanelService.overlayLauncherScreen === modelData + active: PanelService.overlayLauncherOpen && PanelService.overlayLauncherScreen === modelData - sourceComponent: PanelWindow { - id: launcherWindow - screen: windowLoader.modelData + sourceComponent: PanelWindow { + id: launcherWindow + screen: windowLoader.modelData - anchors { - top: true - bottom: true - left: true - right: true - } + anchors { + top: true + bottom: true + left: true + right: true + } - color: "transparent" + color: "transparent" - WlrLayershell.namespace: "noctalia-launcher-overlay-" + (screen?.name || "unknown") - WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "noctalia-launcher-overlay-" + (screen?.name || "unknown") + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.exclusionMode: ExclusionMode.Ignore - // Positioning logic (respects settings but doesn't attach to bar) - readonly property string barPosition: Settings.data.bar.position - readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" - readonly property int barThickness: Math.round(Style.barHeight + Style.marginL) + BackgroundEffect.blurRegion: Settings.data.general.enableBlurBehind ? launcherBlurRegion : null + Region { + id: launcherBlurRegion - readonly property string panelPosition: { - var pos = Settings.data.appLauncher.position; - if (pos === "follow_bar") { - if (barIsVertical) { - return "center_" + barPosition; - } else { - return barPosition + "_center"; - } - } - return pos; - } - - // Preview panel support - readonly property int listPanelWidth: Math.round(500 * Style.uiScaleRatio) - readonly property int previewPanelWidth: Math.round(400 * Style.uiScaleRatio) - readonly property bool previewActive: { - if (!launcherCore) - return false; - var provider = launcherCore.activeProvider; - if (!provider || !provider.hasPreview) - return false; - if (!Settings.data.appLauncher.enableClipPreview) - return false; - var item = launcherCore.results[launcherCore.selectedIndex]; - return launcherCore.selectedIndex >= 0 && launcherCore.results && !!item && !item.isHeader; - } - - // Dimmer background (click to close) - Rectangle { - anchors.fill: parent - color: Qt.alpha(Color.mSurface, Settings.data.general.dimmerOpacity) - - MouseArea { - anchors.fill: parent - onClicked: PanelService.closeOverlayLauncher() - } - } - - // Shadow for launcher panel - NDropShadow { - source: launcherPanel - anchors.fill: launcherPanel - autoPaddingEnabled: true - } - - // Launcher panel with position-based anchoring - Item { - id: launcherPanel - width: Math.round(Math.max(parent.width * 0.25, launcherWindow.listPanelWidth + Style.margin2L * 2)) - height: Math.round(Math.max(parent.height * 0.5, 600 * Style.uiScaleRatio)) - clip: false - - // Entrance animation - opacity: 0 - transformOrigin: { - if (touchingTop && touchingLeft) - return Item.TopLeft; - if (touchingTop && touchingRight) - return Item.TopRight; - if (touchingBottom && touchingLeft) - return Item.BottomLeft; - if (touchingBottom && touchingRight) - return Item.BottomRight; - if (touchingTop) - return Item.Top; - if (touchingBottom) - return Item.Bottom; - if (touchingLeft) - return Item.Left; - if (touchingRight) - return Item.Right; - return Item.Center; - } - - Component.onCompleted: { - opacity = 1; - } - - Behavior on opacity { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutCubic - } - } - - // Horizontal positioning - anchors.horizontalCenter: (panelPosition === "center" || panelPosition.endsWith("_center")) ? parent.horizontalCenter : undefined - anchors.left: panelPosition.endsWith("_left") ? parent.left : undefined - anchors.right: panelPosition.endsWith("_right") ? parent.right : undefined - - // Vertical positioning - anchors.verticalCenter: (panelPosition === "center" || panelPosition.startsWith("center_")) ? parent.verticalCenter : undefined - anchors.top: panelPosition.startsWith("top_") ? parent.top : undefined - anchors.bottom: panelPosition.startsWith("bottom_") ? parent.bottom : undefined - - // Margins - only add bar clearance on the bar's edge - anchors.leftMargin: barPosition === "left" ? barThickness : 0 - anchors.rightMargin: barPosition === "right" ? barThickness : 0 - anchors.topMargin: barPosition === "top" ? barThickness : 0 - anchors.bottomMargin: barPosition === "bottom" ? barThickness : 0 - - // Edge detection - based on position setting and bar location - readonly property bool touchingLeft: panelPosition.endsWith("_left") && barPosition !== "left" - readonly property bool touchingRight: panelPosition.endsWith("_right") && barPosition !== "right" - readonly property bool touchingTop: panelPosition.startsWith("top_") && barPosition !== "top" - readonly property bool touchingBottom: panelPosition.startsWith("bottom_") && barPosition !== "bottom" - - // Corner states based on edge touching - // State 0: Normal rounded, State 1: Horizontal inversion, State 2: Vertical inversion - readonly property int topLeftCornerState: { - if (touchingLeft && touchingTop) - return 0; - if (touchingLeft) - return 2; - if (touchingTop) - return 1; - return 0; - } - readonly property int topRightCornerState: { - if (touchingRight && touchingTop) - return 0; - if (touchingRight) - return 2; - if (touchingTop) - return 1; - return 0; - } - readonly property int bottomLeftCornerState: { - if (touchingLeft && touchingBottom) - return 0; - if (touchingLeft) - return 2; - if (touchingBottom) - return 1; - return 0; - } - readonly property int bottomRightCornerState: { - if (touchingRight && touchingBottom) - return 0; - if (touchingRight) - return 2; - if (touchingBottom) - return 1; - return 0; - } - - // Background with inverted corners - extends beyond panel for inverted corners - Shape { - id: panelShape - // Extend shape to allow inverted corners to render outside panel bounds - x: -radius - y: -radius - width: launcherPanel.width + radius * 2 - height: launcherPanel.height + radius * 2 - opacity: launcherPanel.opacity - layer.enabled: true - - readonly property real radius: Style.radiusL - - // Panel dimensions (for path calculations) - readonly property real panelW: launcherPanel.width - readonly property real panelH: launcherPanel.height - - // Helper functions for corner rendering - function getMultX(state) { - return state === 1 ? -1 : 1; - } - function getMultY(state) { - return state === 2 ? -1 : 1; - } - function getArcDir(multX, multY) { - return ((multX < 0) !== (multY < 0)) ? PathArc.Counterclockwise : PathArc.Clockwise; - } - - readonly property real tlMultX: getMultX(launcherPanel.topLeftCornerState) - readonly property real tlMultY: getMultY(launcherPanel.topLeftCornerState) - readonly property real trMultX: getMultX(launcherPanel.topRightCornerState) - readonly property real trMultY: getMultY(launcherPanel.topRightCornerState) - readonly property real blMultX: getMultX(launcherPanel.bottomLeftCornerState) - readonly property real blMultY: getMultY(launcherPanel.bottomLeftCornerState) - readonly property real brMultX: getMultX(launcherPanel.bottomRightCornerState) - readonly property real brMultY: getMultY(launcherPanel.bottomRightCornerState) - - ShapePath { - strokeWidth: -1 - fillColor: Color.mSurfaceVariant - - // Offset by radius to account for Shape's extended bounds - startX: panelShape.radius + panelShape.radius * panelShape.tlMultX - startY: panelShape.radius - - // Top edge - PathLine { - relativeX: panelShape.panelW - panelShape.radius * panelShape.tlMultX - panelShape.radius * panelShape.trMultX - relativeY: 0 - } - // Top-right corner - PathArc { - relativeX: panelShape.radius * panelShape.trMultX - relativeY: panelShape.radius * panelShape.trMultY - radiusX: panelShape.radius - radiusY: panelShape.radius - direction: panelShape.getArcDir(panelShape.trMultX, panelShape.trMultY) - } - // Right edge - PathLine { - relativeX: 0 - relativeY: panelShape.panelH - panelShape.radius * panelShape.trMultY - panelShape.radius * panelShape.brMultY - } - // Bottom-right corner - PathArc { - relativeX: -panelShape.radius * panelShape.brMultX - relativeY: panelShape.radius * panelShape.brMultY - radiusX: panelShape.radius - radiusY: panelShape.radius - direction: panelShape.getArcDir(panelShape.brMultX, panelShape.brMultY) - } - // Bottom edge - PathLine { - relativeX: -(panelShape.panelW - panelShape.radius * panelShape.brMultX - panelShape.radius * panelShape.blMultX) - relativeY: 0 - } - // Bottom-left corner - PathArc { - relativeX: -panelShape.radius * panelShape.blMultX - relativeY: -panelShape.radius * panelShape.blMultY - radiusX: panelShape.radius - radiusY: panelShape.radius - direction: panelShape.getArcDir(panelShape.blMultX, panelShape.blMultY) - } - // Left edge - PathLine { - relativeX: 0 - relativeY: -(panelShape.panelH - panelShape.radius * panelShape.blMultY - panelShape.radius * panelShape.tlMultY) - } - // Top-left corner - PathArc { - relativeX: panelShape.radius * panelShape.tlMultX - relativeY: -panelShape.radius * panelShape.tlMultY - radiusX: panelShape.radius - radiusY: panelShape.radius - direction: panelShape.getArcDir(panelShape.tlMultX, panelShape.tlMultY) - } - } - } - - // Border - Rectangle { - anchors.fill: parent - color: "transparent" - radius: Style.radiusL - border.color: Style.boxBorderColor - border.width: Style.borderS - visible: !launcherPanel.touchingLeft && !launcherPanel.touchingRight && !launcherPanel.touchingTop && !launcherPanel.touchingBottom - } - - LauncherCore { - id: launcherCore - anchors.fill: parent - screen: windowLoader.modelData - isOpen: true - onRequestClose: PanelService.closeOverlayLauncher() - onRequestCloseImmediately: PanelService.closeOverlayLauncherImmediately() - - Component.onCompleted: PanelService.overlayLauncherCore = launcherCore - Component.onDestruction: PanelService.overlayLauncherCore = null - } - - // Preview Panel - clipboard preview positioned outside panel bounds - NDropShadow { - source: previewBox - anchors.fill: previewBox - autoPaddingEnabled: true - visible: previewBox.visible - } - - NBox { - id: previewBox - visible: launcherWindow.previewActive - width: launcherWindow.previewPanelWidth - height: Math.round(400 * Style.uiScaleRatio) - x: panelPosition.endsWith("_right") ? -(launcherWindow.previewPanelWidth + Style.marginM) : launcherPanel.width + Style.marginM - y: { - var view = launcherCore.resultsView; - if (!view) - return Style.marginL; - var row = launcherCore.isGridView ? Math.floor(launcherCore.selectedIndex / launcherCore.gridColumns) : launcherCore.selectedIndex; - var gridCellSize = Math.floor((launcherWindow.listPanelWidth - (2 * Style.marginXS) - ((launcherCore.targetGridColumns - 1) * Style.marginS)) / launcherCore.targetGridColumns); - var itemHeight = launcherCore.isGridView ? (gridCellSize + Style.marginXXS) : (launcherCore.entryHeight + (view.spacing || 0)); - var yPos = row * itemHeight - (view.contentY || 0); - var mapped = view.mapToItem(launcherPanel, 0, yPos); - return Math.max(Style.marginL, Math.min(mapped.y, launcherPanel.height - previewBox.height - Style.marginL)); - } - z: -1 - - opacity: visible ? 1.0 : 0.0 - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - } - } - Behavior on y { - NumberAnimation { - duration: Style.animationFast - easing.type: Easing.OutCubic - } - } - - Loader { - id: previewLoader - anchors.fill: parent - active: launcherWindow.previewActive - source: { - if (!active) - return ""; - var provider = launcherCore.activeProvider; - if (provider && provider.previewComponentPath) - return provider.previewComponentPath; - return ""; - } - - onLoaded: updatePreviewItem() - onItemChanged: updatePreviewItem() - - function updatePreviewItem() { - if (!item || launcherCore.selectedIndex < 0 || !launcherCore.results[launcherCore.selectedIndex]) - return; - var provider = launcherCore.activeProvider; - if (provider && provider.getPreviewData) { - item.currentItem = provider.getPreviewData(launcherCore.results[launcherCore.selectedIndex]); - } else { - item.currentItem = launcherCore.results[launcherCore.selectedIndex]; - } - } - } - } - - // Update preview when selection changes - Connections { - target: launcherCore - function onSelectedIndexChanged() { - if (previewLoader.item) - previewLoader.updatePreviewItem(); - } - } - } + Region { + x: Math.round(launcherPanel.x) + y: Math.round(launcherPanel.y) + width: Math.round(launcherPanel.width) + height: Math.round(launcherPanel.height) + radius: Style.radiusL + topLeftCorner: launcherPanel.topLeftCornerState + topRightCorner: launcherPanel.topRightCornerState + bottomLeftCorner: launcherPanel.bottomLeftCornerState + bottomRightCorner: launcherPanel.bottomRightCornerState } + + Region { + x: Math.round(previewBox.visible ? previewBox.x : 0) + y: Math.round(previewBox.visible ? previewBox.y : 0) + width: Math.round(previewBox.visible ? previewBox.width : 0) + height: Math.round(previewBox.visible ? previewBox.height : 0) + radius: Style.radiusL + } + } + + // Positioning logic (respects settings but doesn't attach to bar) + readonly property string barPosition: Settings.data.bar.position + readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" + readonly property int barThickness: Math.round(Style.barHeight + Style.marginL) + + readonly property string panelPosition: { + var pos = Settings.data.appLauncher.position; + if (pos === "follow_bar") { + if (barIsVertical) { + return "center_" + barPosition; + } else { + return barPosition + "_center"; + } + } + return pos; + } + + // Preview panel support + readonly property int listPanelWidth: Math.round(500 * Style.uiScaleRatio) + readonly property int previewPanelWidth: Math.round(400 * Style.uiScaleRatio) + readonly property bool previewActive: { + if (!launcherCore) + return false; + var provider = launcherCore.activeProvider; + if (!provider || !provider.hasPreview) + return false; + if (!Settings.data.appLauncher.enableClipPreview) + return false; + var item = launcherCore.results[launcherCore.selectedIndex]; + return launcherCore.selectedIndex >= 0 && launcherCore.results && !!item && !item.isHeader; + } + + // Dimmer background (click to close) + Rectangle { + anchors.fill: parent + color: Qt.alpha(Color.mSurface, Settings.data.general.dimmerOpacity) + + MouseArea { + anchors.fill: parent + onClicked: PanelService.closeOverlayLauncher() + } + } + + // Shadow for launcher panel + NDropShadow { + source: launcherPanel + anchors.fill: launcherPanel + autoPaddingEnabled: true + } + + // Launcher panel with position-based anchoring + Item { + id: launcherPanel + width: Math.round(Math.max(parent.width * 0.25, launcherWindow.listPanelWidth + Style.margin2L * 2)) + height: Math.round(Math.max(parent.height * 0.5, 600 * Style.uiScaleRatio)) + clip: false + + // Entrance animation + opacity: 0 + transformOrigin: { + if (touchingTop && touchingLeft) + return Item.TopLeft; + if (touchingTop && touchingRight) + return Item.TopRight; + if (touchingBottom && touchingLeft) + return Item.BottomLeft; + if (touchingBottom && touchingRight) + return Item.BottomRight; + if (touchingTop) + return Item.Top; + if (touchingBottom) + return Item.Bottom; + if (touchingLeft) + return Item.Left; + if (touchingRight) + return Item.Right; + return Item.Center; + } + + Component.onCompleted: { + opacity = 1; + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + // Horizontal positioning + anchors.horizontalCenter: (panelPosition === "center" || panelPosition.endsWith("_center")) ? parent.horizontalCenter : undefined + anchors.left: panelPosition.endsWith("_left") ? parent.left : undefined + anchors.right: panelPosition.endsWith("_right") ? parent.right : undefined + + // Vertical positioning + anchors.verticalCenter: (panelPosition === "center" || panelPosition.startsWith("center_")) ? parent.verticalCenter : undefined + anchors.top: panelPosition.startsWith("top_") ? parent.top : undefined + anchors.bottom: panelPosition.startsWith("bottom_") ? parent.bottom : undefined + + // Margins - only add bar clearance on the bar's edge + anchors.leftMargin: barPosition === "left" ? barThickness : 0 + anchors.rightMargin: barPosition === "right" ? barThickness : 0 + anchors.topMargin: barPosition === "top" ? barThickness : 0 + anchors.bottomMargin: barPosition === "bottom" ? barThickness : 0 + + // Edge detection - based on position setting and bar location + readonly property bool touchingLeft: panelPosition.endsWith("_left") && barPosition !== "left" + readonly property bool touchingRight: panelPosition.endsWith("_right") && barPosition !== "right" + readonly property bool touchingTop: panelPosition.startsWith("top_") && barPosition !== "top" + readonly property bool touchingBottom: panelPosition.startsWith("bottom_") && barPosition !== "bottom" + + // Corner states based on edge touching + // State 0: Normal rounded, State 1: Horizontal inversion, State 2: Vertical inversion + readonly property int topLeftCornerState: { + if (touchingLeft && touchingTop) + return 0; + if (touchingLeft) + return 2; + if (touchingTop) + return 1; + return 0; + } + readonly property int topRightCornerState: { + if (touchingRight && touchingTop) + return 0; + if (touchingRight) + return 2; + if (touchingTop) + return 1; + return 0; + } + readonly property int bottomLeftCornerState: { + if (touchingLeft && touchingBottom) + return 0; + if (touchingLeft) + return 2; + if (touchingBottom) + return 1; + return 0; + } + readonly property int bottomRightCornerState: { + if (touchingRight && touchingBottom) + return 0; + if (touchingRight) + return 2; + if (touchingBottom) + return 1; + return 0; + } + + // Background with inverted corners - extends beyond panel for inverted corners + Shape { + id: panelShape + // Extend shape to allow inverted corners to render outside panel bounds + x: -radius + y: -radius + width: launcherPanel.width + radius * 2 + height: launcherPanel.height + radius * 2 + visible: panelW > 0 && panelH > 0 + opacity: launcherPanel.opacity + layer.enabled: true + + readonly property real radius: Style.radiusL + + // Panel dimensions (for path calculations) + readonly property real panelW: launcherPanel.width + readonly property real panelH: launcherPanel.height + + // Helper functions for corner rendering + function getMultX(state) { + return state === 1 ? -1 : 1; + } + function getMultY(state) { + return state === 2 ? -1 : 1; + } + function getArcDir(multX, multY) { + return ((multX < 0) !== (multY < 0)) ? PathArc.Counterclockwise : PathArc.Clockwise; + } + + readonly property real tlMultX: getMultX(launcherPanel.topLeftCornerState) + readonly property real tlMultY: getMultY(launcherPanel.topLeftCornerState) + readonly property real trMultX: getMultX(launcherPanel.topRightCornerState) + readonly property real trMultY: getMultY(launcherPanel.topRightCornerState) + readonly property real blMultX: getMultX(launcherPanel.bottomLeftCornerState) + readonly property real blMultY: getMultY(launcherPanel.bottomLeftCornerState) + readonly property real brMultX: getMultX(launcherPanel.bottomRightCornerState) + readonly property real brMultY: getMultY(launcherPanel.bottomRightCornerState) + + ShapePath { + strokeWidth: -1 + fillColor: Qt.alpha(Color.mSurfaceVariant, Color.panelBackgroundOpacity) + + // Offset by radius to account for Shape's extended bounds + startX: panelShape.radius + panelShape.radius * panelShape.tlMultX + startY: panelShape.radius + + // Top edge + PathLine { + relativeX: panelShape.panelW - panelShape.radius * panelShape.tlMultX - panelShape.radius * panelShape.trMultX + relativeY: 0 + } + // Top-right corner + PathArc { + relativeX: panelShape.radius * panelShape.trMultX + relativeY: panelShape.radius * panelShape.trMultY + radiusX: panelShape.radius + radiusY: panelShape.radius + direction: panelShape.getArcDir(panelShape.trMultX, panelShape.trMultY) + } + // Right edge + PathLine { + relativeX: 0 + relativeY: panelShape.panelH - panelShape.radius * panelShape.trMultY - panelShape.radius * panelShape.brMultY + } + // Bottom-right corner + PathArc { + relativeX: -panelShape.radius * panelShape.brMultX + relativeY: panelShape.radius * panelShape.brMultY + radiusX: panelShape.radius + radiusY: panelShape.radius + direction: panelShape.getArcDir(panelShape.brMultX, panelShape.brMultY) + } + // Bottom edge + PathLine { + relativeX: -(panelShape.panelW - panelShape.radius * panelShape.brMultX - panelShape.radius * panelShape.blMultX) + relativeY: 0 + } + // Bottom-left corner + PathArc { + relativeX: -panelShape.radius * panelShape.blMultX + relativeY: -panelShape.radius * panelShape.blMultY + radiusX: panelShape.radius + radiusY: panelShape.radius + direction: panelShape.getArcDir(panelShape.blMultX, panelShape.blMultY) + } + // Left edge + PathLine { + relativeX: 0 + relativeY: -(panelShape.panelH - panelShape.radius * panelShape.blMultY - panelShape.radius * panelShape.tlMultY) + } + // Top-left corner + PathArc { + relativeX: panelShape.radius * panelShape.tlMultX + relativeY: -panelShape.radius * panelShape.tlMultY + radiusX: panelShape.radius + radiusY: panelShape.radius + direction: panelShape.getArcDir(panelShape.tlMultX, panelShape.tlMultY) + } + } + } + + // Border + Rectangle { + anchors.fill: parent + color: "transparent" + radius: Style.radiusL + border.color: Style.boxBorderColor + border.width: Style.borderS + visible: !launcherPanel.touchingLeft && !launcherPanel.touchingRight && !launcherPanel.touchingTop && !launcherPanel.touchingBottom + } + + LauncherCore { + id: launcherCore + anchors.fill: parent + screen: windowLoader.modelData + isOpen: true + onRequestClose: PanelService.closeOverlayLauncher() + onRequestCloseImmediately: PanelService.closeOverlayLauncherImmediately() + + Component.onCompleted: PanelService.overlayLauncherCore = launcherCore + Component.onDestruction: PanelService.overlayLauncherCore = null + } + } + + // Preview Panel - positioned as sibling of launcherPanel to avoid shadow bleed + NDropShadow { + source: previewBox + anchors.fill: previewBox + autoPaddingEnabled: true + visible: previewBox.visible + z: previewBox.z - 1 + } + + NBox { + id: previewBox + visible: launcherWindow.previewActive + width: launcherWindow.previewPanelWidth + height: Math.round(400 * Style.uiScaleRatio) + forceOpaque: true + x: { + if (panelPosition.endsWith("_right")) + return launcherPanel.x - launcherWindow.previewPanelWidth - Style.marginM; + return launcherPanel.x + launcherPanel.width + Style.marginM; + } + y: { + var view = launcherCore.resultsView; + if (!view) + return launcherPanel.y + Style.marginL; + var row = launcherCore.isGridView ? Math.floor(launcherCore.selectedIndex / launcherCore.gridColumns) : launcherCore.selectedIndex; + var gridCellSize = Math.floor((launcherWindow.listPanelWidth - (2 * Style.marginXS) - ((launcherCore.targetGridColumns - 1) * Style.marginS)) / launcherCore.targetGridColumns); + var itemHeight = launcherCore.isGridView ? (gridCellSize + Style.marginXXS) : (launcherCore.entryHeight + (view.spacing || 0)); + var yPos = row * itemHeight - (view.contentY || 0); + var mapped = view.mapToItem(launcherWindow.contentItem, 0, yPos); + return Math.max(launcherPanel.y + Style.marginL, Math.min(mapped.y, launcherPanel.y + launcherPanel.height - previewBox.height - Style.marginL)); + } + + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + Behavior on y { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Loader { + id: previewLoader + anchors.fill: parent + active: launcherWindow.previewActive + source: { + if (!active) + return ""; + var provider = launcherCore.activeProvider; + if (provider && provider.previewComponentPath) + return provider.previewComponentPath; + return ""; + } + + onLoaded: updatePreviewItem() + onItemChanged: updatePreviewItem() + + function updatePreviewItem() { + if (!item || launcherCore.selectedIndex < 0 || !launcherCore.results[launcherCore.selectedIndex]) + return; + var provider = launcherCore.activeProvider; + if (provider && provider.getPreviewData) { + item.currentItem = provider.getPreviewData(launcherCore.results[launcherCore.selectedIndex]); + } else { + item.currentItem = launcherCore.results[launcherCore.selectedIndex]; + } + } + } + } + + // Update preview when selection changes + Connections { + target: launcherCore + function onSelectedIndexChanged() { + if (previewLoader.item) + previewLoader.updatePreviewItem(); + } + } } + } } diff --git a/Modules/Panels/Launcher/Providers/ApplicationsProvider.qml b/Modules/Panels/Launcher/Providers/ApplicationsProvider.qml index 34d84040e..3d4f67535 100644 --- a/Modules/Panels/Launcher/Providers/ApplicationsProvider.qml +++ b/Modules/Panels/Launcher/Providers/ApplicationsProvider.qml @@ -14,6 +14,7 @@ Item { property string supportedLayouts: "both" property bool isDefaultProvider: true // This provider handles empty search property bool ignoreDensity: false // Apps should scale with launcher density + property bool trackUsage: true // Track usage frequency for "most used" sorting // Category support property string selectedCategory: "all" @@ -60,11 +61,12 @@ Item { function init() { loadApplications(); + migrateLegacyUsageKeys(); } function onOpened() { - // Refresh apps when launcher opens - loadApplications(); + // Just update available categories in case pinned apps changed + updateAvailableCategories(); // Default to Pinned if there are pinned apps, otherwise all if (availableCategories.includes("Pinned")) { selectedCategory = "Pinned"; @@ -75,6 +77,15 @@ Item { showsCategories = true; } + // Reload applications when desktop entries change on disk + Connections { + target: typeof DesktopEntries !== 'undefined' ? DesktopEntries.applications : null + function onValuesChanged() { + Logger.d("ApplicationsProvider", "Desktop entries changed, reloading applications"); + loadApplications(); + } + } + function selectCategory(category) { selectedCategory = category; if (launcher) { @@ -507,6 +518,7 @@ Item { function createResultEntry(app, score) { return { "appId": getAppKey(app), + "usageKey": getAppKey(app), "name": app.name || "Unknown", "description": app.genericName || app.comment || "", "icon": app.icon || "application-x-executable", @@ -514,10 +526,6 @@ Item { "_score": (score !== undefined ? score : 0), "provider": root, "onActivate": function () { - if (Settings.data.appLauncher.sortByMostUsed) { - root.recordUsage(app); - } - // Close the launcher/SmartPanel immediately without any animations. // Ensures we are not preventing the future focusing of the app launcher.closeImmediately(); @@ -636,7 +644,18 @@ Item { return ShellState.getLauncherUsageCount(getAppKey(app)); } - function recordUsage(app) { - ShellState.recordLauncherUsage(getAppKey(app)); + // Migrate legacy command-based usage keys to canonical app-id keys at startup + function migrateLegacyUsageKeys() { + for (let i = 0; i < entries.length; i++) { + const app = entries[i]; + if (app && app.id && app.command && app.command.join) { + const key = getAppKey(app); + const legacyKey = app.command.join(" "); + if (legacyKey !== key && ShellState.getLauncherUsageCount(legacyKey) > 0) { + ShellState.migrateLauncherUsage(legacyKey, key); + Logger.d("ApplicationsProvider", `Migrated usage: "${legacyKey}" → "${key}"`); + } + } + } } } diff --git a/Modules/Panels/Launcher/Providers/SessionProvider.qml b/Modules/Panels/Launcher/Providers/SessionProvider.qml index 96ec479cf..13a0f7621 100644 --- a/Modules/Panels/Launcher/Providers/SessionProvider.qml +++ b/Modules/Panels/Launcher/Providers/SessionProvider.qml @@ -40,6 +40,18 @@ Item { "icon": iconMode === "tabler" ? "reboot" : "system-reboot", "keywords": ["reboot", "restart", "reload"] }, + { + "action": "rebootToUefi", + "labelKey": "common.reboot-to-uefi", + "icon": iconMode === "tabler" ? "reboot" : "system-reboot", + "keywords": ["reboot", "uefi", "firmware", "bios"] + }, + { + "action": "userspaceReboot", + "labelKey": "common.userspace-reboot", + "icon": iconMode === "tabler" ? "rotate" : "system-reboot", + "keywords": ["reboot", "restart", "soft", "userspace"] + }, { "action": "logout", "labelKey": "common.logout", @@ -141,39 +153,42 @@ Item { if (launcher) launcher.close(); + // Execute via Qt.callLater, but reference only singletons + // (root may be destroyed after launcher.close() unloads the panel) Qt.callLater(() => { - executeAction(action); + switch (action) { + case "lock": + if (PanelService.lockScreen && !PanelService.lockScreen.active) { + PanelService.lockScreen.active = true; + } + break; + case "suspend": + if (Settings.data.general.lockOnSuspend) { + CompositorService.lockAndSuspend(); + } else { + CompositorService.suspend(); + } + break; + case "hibernate": + CompositorService.hibernate(); + break; + case "reboot": + CompositorService.reboot(); + break; + case "rebootToUefi": + CompositorService.rebootToUefi(); + break; + case "userspaceReboot": + CompositorService.userspaceReboot(); + break; + case "logout": + CompositorService.logout(); + break; + case "shutdown": + CompositorService.shutdown(); + break; + } }); }; } - - function executeAction(action) { - // Default behavior or custom command handled by CompositorService - switch (action) { - case "lock": - if (PanelService.lockScreen && !PanelService.lockScreen.active) { - PanelService.lockScreen.active = true; - } - break; - case "suspend": - if (Settings.data.general.lockOnSuspend) { - CompositorService.lockAndSuspend(); - } else { - CompositorService.suspend(); - } - break; - case "hibernate": - CompositorService.hibernate(); - break; - case "reboot": - CompositorService.reboot(); - break; - case "logout": - CompositorService.logout(); - break; - case "shutdown": - CompositorService.shutdown(); - break; - } - } } diff --git a/Modules/Panels/Media/MediaPlayerPanel.qml b/Modules/Panels/Media/MediaPlayerPanel.qml index 246f9dff4..ddf82e829 100644 --- a/Modules/Panels/Media/MediaPlayerPanel.qml +++ b/Modules/Panels/Media/MediaPlayerPanel.qml @@ -49,24 +49,24 @@ SmartPanel { readonly property bool isSideBySide: root.compactMode && root.showAlbumArt - readonly property bool needsCava: root.showVisualizer && root.visualizerType !== "" && root.visualizerType !== "none" && root.isPanelOpen + readonly property bool needsSpectrum: root.showVisualizer && root.visualizerType !== "" && root.visualizerType !== "none" && root.isPanelOpen - onNeedsCavaChanged: { - if (root.needsCava) { - CavaService.registerComponent("mediaplayerpanel"); + onNeedsSpectrumChanged: { + if (root.needsSpectrum) { + SpectrumService.registerComponent("mediaplayerpanel"); } else { - CavaService.unregisterComponent("mediaplayerpanel"); + SpectrumService.unregisterComponent("mediaplayerpanel"); } } Component.onCompleted: { - if (root.needsCava) { - CavaService.registerComponent("mediaplayerpanel"); + if (root.needsSpectrum) { + SpectrumService.registerComponent("mediaplayerpanel"); } } Component.onDestruction: { - CavaService.unregisterComponent("mediaplayerpanel"); + SpectrumService.unregisterComponent("mediaplayerpanel"); } panelContent: Item { @@ -238,7 +238,7 @@ SmartPanel { Loader { anchors.fill: parent z: 0 - active: !!(root.needsCava && !root.showAlbumArt) + active: !!(root.needsSpectrum && !root.showAlbumArt) sourceComponent: visualizerSource } @@ -276,7 +276,7 @@ SmartPanel { anchors.fill: parent anchors.margins: Style.marginS z: 2 - active: !!(root.needsCava && root.showAlbumArt) + active: !!(root.needsSpectrum && root.showAlbumArt) sourceComponent: visualizerSource } } @@ -492,7 +492,7 @@ SmartPanel { NLinearSpectrum { width: parent.width - Style.marginS height: 20 - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 barPosition: Settings.getBarPositionForScreen(root.screen?.name) @@ -504,7 +504,7 @@ SmartPanel { NMirroredSpectrum { width: parent.width - Style.marginS height: parent.height - Style.marginS - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 } @@ -515,7 +515,7 @@ SmartPanel { NWaveSpectrum { width: parent.width - Style.marginS height: parent.height - Style.marginS - values: CavaService.values + values: SpectrumService.values fillColor: Color.mPrimary opacity: 0.4 } diff --git a/Modules/Panels/Network/WiFiNetworksList.qml b/Modules/Panels/Network/WiFiNetworksList.qml index 0f09234be..2a56cdc37 100644 --- a/Modules/Panels/Network/WiFiNetworksList.qml +++ b/Modules/Panels/Network/WiFiNetworksList.qml @@ -82,7 +82,7 @@ NBox { opacity: (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1.0 - color: modelData.connected ? Qt.alpha(Color.mPrimary, 0.15) : Color.mSurface + color: modelData.connected ? Qt.alpha(Color.mPrimary, Math.min(1.15 - Color.panelBackgroundOpacity, 0.75)) : Color.mSurface Behavior on opacity { NumberAnimation { diff --git a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml index d4459b3f3..42ccc0b70 100644 --- a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml +++ b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml @@ -562,6 +562,7 @@ SmartPanel { property int listIndex: index property string notificationId: model.id + property string appName: model.appName || "" property bool isExpanded: scrollView.expandedId === notificationId property bool canExpand: summaryText.truncated || bodyText.truncated property real swipeOffset: 0 @@ -777,6 +778,19 @@ SmartPanel { notificationDelegate.pendingLink = ""; return; } + + // Focus sender window (and invoke default action if available) + var actions = notificationDelegate.actionsList; + var hasDefault = actions.some(function (a) { + return a.identifier === "default"; + }); + if (hasDefault) { + NotificationService.focusSenderWindow(notificationDelegate.appName); + NotificationService.invokeAction(notificationDelegate.notificationId, "default"); + } else { + NotificationService.focusSenderWindow(notificationDelegate.appName); + } + root.close(); } onCanceled: { notificationDelegate.isSwiping = false; @@ -934,6 +948,8 @@ SmartPanel { // Capture modelData in a property to avoid reference errors property var actionData: modelData onClicked: { + NotificationService.focusSenderWindow(notificationDelegate.appName); + root.close(); NotificationService.invokeAction(notificationDelegate.notificationId, actionData.identifier); } } diff --git a/Modules/Panels/SessionMenu/SessionMenu.qml b/Modules/Panels/SessionMenu/SessionMenu.qml index 2ab3ea81e..7d508ad03 100644 --- a/Modules/Panels/SessionMenu/SessionMenu.qml +++ b/Modules/Panels/SessionMenu/SessionMenu.qml @@ -106,6 +106,11 @@ SmartPanel { "title": I18n.tr("common.reboot"), "isShutdown": false }, + "userspaceReboot": { + "icon": "rotate", + "title": I18n.tr("common.userspace-reboot"), + "isShutdown": false + }, "rebootToUefi": { "icon": "reboot", "title": I18n.tr("common.reboot-to-uefi"), @@ -243,6 +248,9 @@ SmartPanel { case "reboot": CompositorService.reboot(); break; + case "userspaceReboot": + CompositorService.userspaceReboot(); + break; case "rebootToUefi": CompositorService.rebootToUefi(); break; @@ -589,7 +597,7 @@ SmartPanel { id: timerText anchors.centerIn: parent text: I18n.tr("session-menu.action-in-seconds", { - "action": I18n.tr("common." + pendingAction), + "action": root.actionMetadata[pendingAction] ? root.actionMetadata[pendingAction].title : "", "seconds": Math.ceil(timeRemaining / 1000) }) font.weight: Style.fontWeightBold @@ -641,7 +649,7 @@ SmartPanel { visible: !largeButtonsStyle anchors.fill: parent anchors.margins: Style.marginL - color: Color.mSurface + color: Color.mSurfaceVariant ColumnLayout { anchors.fill: parent @@ -656,7 +664,7 @@ SmartPanel { NText { text: timerActive ? I18n.tr("session-menu.action-in-seconds", { - "action": I18n.tr("common." + pendingAction), + "action": root.actionMetadata[pendingAction] ? root.actionMetadata[pendingAction].title : "", "seconds": Math.ceil(timeRemaining / 1000) }) : I18n.tr("session-menu.title") font.weight: Style.fontWeightBold @@ -842,7 +850,7 @@ SmartPanel { width: labelText.implicitWidth + Style.margin2M height: labelText.height + Style.margin2XS radius: Math.min(Style.radiusM, height / 2) - color: (buttonRoot.isSelected || buttonRoot.effectiveHover) ? Color.mOnPrimary : Color.mSurfaceVariant + color: (buttonRoot.isSelected || buttonRoot.effectiveHover) ? Color.mOnPrimary : Color.mSurface border.width: Style.borderS border.color: (buttonRoot.isSelected || buttonRoot.effectiveHover) ? Color.mOnPrimary : Color.mOutline visible: Settings.data.sessionMenu.showKeybinds && (buttonRoot.keybind !== "") && !buttonRoot.pending @@ -852,7 +860,7 @@ SmartPanel { anchors.centerIn: parent text: buttonRoot.keybind pointSize: Style.fontSizeXS - color: (buttonRoot.isSelected || buttonRoot.effectiveHover) ? Color.mPrimary : Color.mOnSurfaceVariant + color: (buttonRoot.isSelected || buttonRoot.effectiveHover) ? Color.mPrimary : Color.mOnSurface Behavior on color { ColorAnimation { diff --git a/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml b/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml index 7cb47a8c4..fe4627e21 100644 --- a/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml +++ b/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml @@ -145,7 +145,7 @@ NBox { } } - Component.onCompleted: updateAvailableWidgetsModel() + Component.onCompleted: Qt.callLater(updateAvailableWidgetsModel) ListModel { id: availableWidgetsModel diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index ddf5b6138..b1fed1cdc 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -17,7 +17,7 @@ ColumnLayout { // Local state property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon - property string valueHideMode: "hidden" // Default to 'Hide When Empty' + property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode 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 @@ -65,6 +65,7 @@ ColumnLayout { root.valueHideMode = key; saveSettings(); } + defaultValue: widgetMetadata.hideMode } NColorChoice { @@ -74,6 +75,7 @@ ColumnLayout { valueTextColor = key; saveSettings(); } + defaultValue: widgetMetadata.textColor } NToggle { @@ -85,6 +87,7 @@ ColumnLayout { root.valueShowIcon = checked; saveSettings(); } + defaultValue: widgetMetadata.showIcon } NToggle { @@ -97,6 +100,7 @@ ColumnLayout { saveSettings(); } visible: root.valueShowIcon + defaultValue: widgetMetadata.colorizeIcons } NTextInput { @@ -107,6 +111,7 @@ ColumnLayout { placeholderText: widgetMetadata.maxWidth text: valueMaxWidth onEditingFinished: saveSettings() + defaultValue: String(widgetMetadata.maxWidth) } NToggle { @@ -118,6 +123,7 @@ ColumnLayout { valueUseFixedWidth = checked; saveSettings(); } + defaultValue: widgetMetadata.useFixedWidth } NComboBox { @@ -138,6 +144,7 @@ ColumnLayout { } ] currentKey: valueScrollingMode + defaultValue: widgetMetadata.scrollingMode onSelected: key => { valueScrollingMode = key; saveSettings(); diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml index fe58e12e1..ca474a1c3 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/AudioVisualizerSettings.qml @@ -35,6 +35,7 @@ ColumnLayout { text: widgetData.width || widgetMetadata.width placeholderText: I18n.tr("placeholders.enter-width-pixels") onEditingFinished: saveSettings() + defaultValue: String(widgetMetadata.width) } NColorChoice { @@ -46,6 +47,7 @@ ColumnLayout { root.valueColorName = key; saveSettings(); } + defaultValue: widgetMetadata.colorName } NToggle { @@ -56,5 +58,6 @@ ColumnLayout { valueHideWhenIdle = checked; saveSettings(); } + defaultValue: widgetMetadata.hideWhenIdle } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml index 679ed4281..7c59cd26f 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/BatterySettings.qml @@ -18,7 +18,7 @@ ColumnLayout { // Local state property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode - property string valueDeviceNativePath: widgetData.deviceNativePath !== undefined ? widgetData.deviceNativePath : "__default__" + property string valueDeviceNativePath: widgetData.deviceNativePath !== undefined ? widgetData.deviceNativePath : widgetMetadata.deviceNativePath property bool valueShowPowerProfiles: widgetData.showPowerProfiles !== undefined ? widgetData.showPowerProfiles : widgetMetadata.showPowerProfiles property bool valueShowNoctaliaPerformance: widgetData.showNoctaliaPerformance !== undefined ? widgetData.showNoctaliaPerformance : widgetMetadata.showNoctaliaPerformance property bool valueHideIfNotDetected: widgetData.hideIfNotDetected !== undefined ? widgetData.hideIfNotDetected : widgetMetadata.hideIfNotDetected @@ -50,6 +50,7 @@ ColumnLayout { root.valueDeviceNativePath = key; saveSettings(); } + defaultValue: widgetMetadata.deviceNativePath } NComboBox { @@ -84,6 +85,7 @@ ColumnLayout { root.valueDisplayMode = key; saveSettings(); } + defaultValue: widgetMetadata.displayMode } NToggle { @@ -94,6 +96,7 @@ ColumnLayout { valueHideIfNotDetected = checked; saveSettings(); } + defaultValue: widgetMetadata.hideIfNotDetected } NToggle { @@ -104,6 +107,7 @@ ColumnLayout { valueHideIfIdle = checked; saveSettings(); } + defaultValue: widgetMetadata.hideIfIdle } NDivider { @@ -118,6 +122,7 @@ ColumnLayout { valueShowPowerProfiles = checked; saveSettings(); } + defaultValue: widgetMetadata.showPowerProfiles } NToggle { @@ -128,5 +133,6 @@ ColumnLayout { valueShowNoctaliaPerformance = checked; saveSettings(); } + defaultValue: widgetMetadata.showNoctaliaPerformance } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/BluetoothSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/BluetoothSettings.qml index c308d3575..cd1c53d2b 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/BluetoothSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/BluetoothSettings.qml @@ -50,6 +50,7 @@ ColumnLayout { root.valueDisplayMode = key; saveSettings(); } + defaultValue: widgetMetadata.displayMode } NColorChoice { @@ -59,6 +60,7 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } NColorChoice { @@ -67,5 +69,6 @@ ColumnLayout { valueTextColor = key; saveSettings(); } + defaultValue: widgetMetadata.textColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml index 984c6d0ee..38a349452 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/BrightnessSettings.qml @@ -56,6 +56,7 @@ ColumnLayout { valueDisplayMode = key; saveSettings(); } + defaultValue: widgetMetadata.displayMode } NColorChoice { @@ -65,6 +66,7 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } NColorChoice { @@ -73,6 +75,7 @@ ColumnLayout { valueTextColor = key; saveSettings(); } + defaultValue: widgetMetadata.textColor } NToggle { diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml index 0f08a5d76..5ff3f715c 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/ClockSettings.qml @@ -20,10 +20,10 @@ ColumnLayout { // Local state property string valueClockColor: widgetData.clockColor !== undefined ? widgetData.clockColor : widgetMetadata.clockColor property bool valueUseCustomFont: widgetData.useCustomFont !== undefined ? widgetData.useCustomFont : widgetMetadata.useCustomFont - property string valueCustomFont: widgetData.customFont !== undefined ? widgetData.customFont : (widgetMetadata.customFont !== undefined ? widgetMetadata.customFont : "") - property string valueFormatHorizontal: widgetData.formatHorizontal !== undefined ? widgetData.formatHorizontal : (widgetMetadata.formatHorizontal !== undefined ? widgetMetadata.formatHorizontal : "") - property string valueFormatVertical: widgetData.formatVertical !== undefined ? widgetData.formatVertical : (widgetMetadata.formatVertical !== undefined ? widgetMetadata.formatVertical : "") - property string valueTooltipFormat: widgetData.tooltipFormat !== undefined ? widgetData.tooltipFormat : (widgetMetadata.tooltipFormat !== undefined ? widgetMetadata.tooltipFormat : "") + property string valueCustomFont: widgetData.customFont !== undefined ? widgetData.customFont : widgetMetadata.customFont + property string valueFormatHorizontal: widgetData.formatHorizontal !== undefined ? widgetData.formatHorizontal : widgetMetadata.formatHorizontal + property string valueFormatVertical: widgetData.formatVertical !== undefined ? widgetData.formatVertical : widgetMetadata.formatVertical + property string valueTooltipFormat: widgetData.tooltipFormat !== undefined ? widgetData.tooltipFormat : widgetMetadata.tooltipFormat readonly property color textColor: Color.resolveColorKey(valueClockColor) @@ -77,6 +77,7 @@ ColumnLayout { valueClockColor = key; saveSettings(); } + defaultValue: widgetMetadata.clockColor } NToggle { @@ -88,6 +89,7 @@ ColumnLayout { valueUseCustomFont = checked; saveSettings(); } + defaultValue: widgetMetadata.useCustomFont } NSearchableComboBox { @@ -105,6 +107,7 @@ ColumnLayout { valueCustomFont = key; saveSettings(); } + defaultValue: Settings.data.ui.fontDefault } NDivider { @@ -148,6 +151,7 @@ ColumnLayout { }); } } + defaultValue: widgetMetadata.formatHorizontal } Item { @@ -173,6 +177,7 @@ ColumnLayout { }); } } + defaultValue: widgetMetadata.formatVertical } NTextInput { @@ -193,6 +198,7 @@ ColumnLayout { }); } } + defaultValue: widgetMetadata.tooltipFormat } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml index 8ec884a34..d24602d90 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml @@ -42,6 +42,7 @@ ColumnLayout { valueUseDistroLogo = checked; saveSettings(); } + defaultValue: widgetMetadata.useDistroLogo } NToggle { @@ -52,6 +53,7 @@ ColumnLayout { valueEnableColorization = checked; saveSettings(); } + defaultValue: widgetMetadata.enableColorization } NColorChoice { @@ -63,6 +65,7 @@ ColumnLayout { valueColorizeSystemIcon = key; saveSettings(); } + defaultValue: widgetMetadata.colorizeSystemIcon } RowLayout { diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml index 94b7ef5ee..da099e41b 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/CustomButtonSettings.qml @@ -24,12 +24,12 @@ ColumnLayout { property int valueMaxTextLengthVertical: widgetData?.maxTextLength?.vertical ?? widgetMetadata?.maxTextLength?.vertical property string valueHideMode: (widgetData.hideMode !== undefined) ? widgetData.hideMode : widgetMetadata.hideMode property bool valueShowIcon: (widgetData.showIcon !== undefined) ? widgetData.showIcon : widgetMetadata.showIcon - property bool valueShowExecTooltip: widgetData.showExecTooltip !== undefined ? widgetData.showExecTooltip : (widgetMetadata.showExecTooltip !== undefined ? widgetMetadata.showExecTooltip : true) - property bool valueShowTextTooltip: widgetData.showTextTooltip !== undefined ? widgetData.showTextTooltip : (widgetMetadata.showTextTooltip !== undefined ? widgetMetadata.showTextTooltip : true) - property bool valueEnableColorization: widgetData.enableColorization || false - property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon || "none" - property string valueIpcIdentifier: widgetData.ipcIdentifier !== undefined ? widgetData.ipcIdentifier : widgetMetadata.ipcIdentifier || "" - property string valueGeneralTooltipText: widgetData.generalTooltipText !== undefined ? widgetData.generalTooltipText : widgetMetadata.generalTooltipText || "" + property bool valueShowExecTooltip: widgetData.showExecTooltip !== undefined ? widgetData.showExecTooltip : widgetMetadata.showExecTooltip + property bool valueShowTextTooltip: widgetData.showTextTooltip !== undefined ? widgetData.showTextTooltip : widgetMetadata.showTextTooltip + property bool valueEnableColorization: widgetData.enableColorization !== undefined ? widgetData.enableColorization : widgetMetadata.enableColorization + property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon + property string valueIpcIdentifier: widgetData.ipcIdentifier !== undefined ? widgetData.ipcIdentifier : widgetMetadata.ipcIdentifier + property string valueGeneralTooltipText: widgetData.generalTooltipText !== undefined ? widgetData.generalTooltipText : widgetMetadata.generalTooltipText function saveSettings() { var settings = Object.assign({}, widgetData || {}); @@ -107,6 +107,7 @@ ColumnLayout { saveSettings(); } visible: textCommandInput.text !== "" + defaultValue: widgetMetadata.showIcon } NToggle { @@ -117,6 +118,7 @@ ColumnLayout { valueEnableColorization = checked; saveSettings(); } + defaultValue: widgetMetadata.enableColorization } NColorChoice { @@ -128,6 +130,7 @@ ColumnLayout { valueColorizeSystemIcon = key; saveSettings(); } + defaultValue: widgetMetadata.colorizeSystemIcon } NTextInput { @@ -138,6 +141,7 @@ ColumnLayout { text: valueGeneralTooltipText onTextChanged: valueGeneralTooltipText = text onEditingFinished: saveSettings() + defaultValue: widgetMetadata.generalTooltipText } NToggle { @@ -149,6 +153,7 @@ ColumnLayout { valueShowExecTooltip = checked; saveSettings(); } + defaultValue: widgetMetadata.showExecTooltip } NToggle { @@ -160,6 +165,7 @@ ColumnLayout { valueShowTextTooltip = checked; saveSettings(); } + defaultValue: widgetMetadata.showTextTooltip } NTextInput { @@ -170,6 +176,7 @@ ColumnLayout { text: valueIpcIdentifier onTextChanged: valueIpcIdentifier = text onEditingFinished: saveSettings() + defaultValue: widgetMetadata.ipcIdentifier } RowLayout { @@ -183,6 +190,7 @@ ColumnLayout { placeholderText: I18n.tr("placeholders.enter-command") text: widgetData?.leftClickExec || widgetMetadata.leftClickExec onEditingFinished: saveSettings() + defaultValue: widgetMetadata.leftClickExec } NToggle { @@ -197,6 +205,7 @@ ColumnLayout { checked = isChecked; saveSettings(); } + defaultValue: widgetMetadata.leftClickUpdateText } } @@ -211,6 +220,7 @@ ColumnLayout { placeholderText: I18n.tr("placeholders.enter-command") text: widgetData?.rightClickExec || widgetMetadata.rightClickExec onEditingFinished: saveSettings() + defaultValue: widgetMetadata.rightClickExec } NToggle { @@ -225,6 +235,7 @@ ColumnLayout { checked = isChecked; saveSettings(); } + defaultValue: widgetMetadata.rightClickUpdateText } } @@ -239,6 +250,7 @@ ColumnLayout { placeholderText: I18n.tr("placeholders.enter-command") text: widgetData.middleClickExec || widgetMetadata.middleClickExec onEditingFinished: saveSettings() + defaultValue: widgetMetadata.middleClickExec } NToggle { @@ -253,6 +265,7 @@ ColumnLayout { checked = isChecked; saveSettings(); } + defaultValue: widgetMetadata.middleClickUpdateText } } @@ -268,6 +281,7 @@ ColumnLayout { internalChecked = checked; saveSettings(); } + defaultValue: widgetMetadata.wheelMode === "separate" } ColumnLayout { @@ -286,6 +300,7 @@ ColumnLayout { placeholderText: I18n.tr("placeholders.enter-command") text: widgetData?.wheelExec || widgetMetadata?.wheelExec onEditingFinished: saveSettings() + defaultValue: widgetMetadata.wheelExec } NToggle { @@ -300,6 +315,7 @@ ColumnLayout { checked = isChecked; saveSettings(); } + defaultValue: widgetMetadata.wheelUpdateText } } @@ -319,6 +335,7 @@ ColumnLayout { placeholderText: I18n.tr("placeholders.enter-command") text: widgetData?.wheelUpExec || widgetMetadata?.wheelUpExec onEditingFinished: saveSettings() + defaultValue: widgetMetadata.wheelUpExec } NToggle { @@ -333,6 +350,7 @@ ColumnLayout { checked = isChecked; saveSettings(); } + defaultValue: widgetMetadata.wheelUpUpdateText } } @@ -347,6 +365,7 @@ ColumnLayout { placeholderText: I18n.tr("placeholders.enter-command") text: widgetData?.wheelDownExec || widgetMetadata?.wheelDownExec onEditingFinished: saveSettings() + defaultValue: widgetMetadata.wheelDownExec } NToggle { @@ -361,6 +380,7 @@ ColumnLayout { checked = isChecked; saveSettings(); } + defaultValue: widgetMetadata.wheelDownUpdateText } } } @@ -384,6 +404,7 @@ ColumnLayout { valueMaxTextLengthHorizontal = value; saveSettings(); } + defaultValue: widgetMetadata.maxTextLength.horizontal } NSpinBox { @@ -396,6 +417,7 @@ ColumnLayout { valueMaxTextLengthVertical = value; saveSettings(); } + defaultValue: widgetMetadata.maxTextLength.vertical } NToggle { @@ -407,6 +429,7 @@ ColumnLayout { valueTextStream = checked; saveSettings(); } + defaultValue: widgetMetadata.textStream } NToggle { @@ -418,6 +441,7 @@ ColumnLayout { valueParseJson = checked; saveSettings(); } + defaultValue: widgetMetadata.parseJson } NTextInput { @@ -428,6 +452,7 @@ ColumnLayout { placeholderText: I18n.tr("placeholders.command-example") text: widgetData?.textCommand || widgetMetadata.textCommand onEditingFinished: saveSettings() + defaultValue: widgetMetadata.textCommand } NTextInput { @@ -439,6 +464,7 @@ ColumnLayout { placeholderText: I18n.tr("placeholders.enter-text-to-collapse") text: widgetData?.textCollapse || widgetMetadata.textCollapse onEditingFinished: saveSettings() + defaultValue: widgetMetadata.textCollapse } NTextInput { @@ -450,6 +476,7 @@ ColumnLayout { placeholderText: String(widgetMetadata.textIntervalMs) text: widgetData && widgetData.textIntervalMs !== undefined ? String(widgetData.textIntervalMs) : "" onEditingFinished: saveSettings() + defaultValue: String(widgetMetadata.textIntervalMs) } NComboBox { @@ -476,5 +503,6 @@ ColumnLayout { saveSettings(); } visible: textCommandInput.text !== "" && valueTextStream == true + defaultValue: widgetMetadata.hideMode } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/DarkModeSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/DarkModeSettings.qml index ec0584b61..f6546e836 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/DarkModeSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/DarkModeSettings.qml @@ -29,5 +29,6 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/KeepAwakeSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/KeepAwakeSettings.qml index 2132c15d3..fc942356a 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/KeepAwakeSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/KeepAwakeSettings.qml @@ -31,6 +31,7 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } NColorChoice { @@ -39,5 +40,6 @@ ColumnLayout { valueTextColor = key; saveSettings(); } + defaultValue: widgetMetadata.textColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml index 2c7e90e7f..9b40c80d0 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml @@ -54,6 +54,7 @@ ColumnLayout { valueDisplayMode = key; saveSettings(); } + defaultValue: widgetMetadata.displayMode } NToggle { @@ -64,6 +65,7 @@ ColumnLayout { valueShowIcon = checked; saveSettings(); } + defaultValue: widgetMetadata.showIcon } NColorChoice { @@ -73,6 +75,7 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } NColorChoice { @@ -81,5 +84,6 @@ ColumnLayout { valueTextColor = key; saveSettings(); } + defaultValue: widgetMetadata.textColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/LauncherSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/LauncherSettings.qml index c232d6405..ff91dc614 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/LauncherSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/LauncherSettings.qml @@ -42,6 +42,7 @@ ColumnLayout { valueUseDistroLogo = checked; saveSettings(); } + defaultValue: widgetMetadata.useDistroLogo } NToggle { @@ -52,6 +53,7 @@ ColumnLayout { valueEnableColorization = checked; saveSettings(); } + defaultValue: widgetMetadata.enableColorization } NColorChoice { @@ -63,6 +65,7 @@ ColumnLayout { valueColorizeSystemIcon = key; saveSettings(); } + defaultValue: widgetMetadata.colorizeSystemIcon } RowLayout { diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml index 21d78eb30..f5373a3fd 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/LockKeysSettings.qml @@ -49,6 +49,7 @@ ColumnLayout { valueShowCapsLock = checked; saveSettings(); } + defaultValue: widgetMetadata.showCapsLock } NIcon { @@ -86,6 +87,7 @@ ColumnLayout { valueShowNumLock = checked; saveSettings(); } + defaultValue: widgetMetadata.showNumLock } NIcon { @@ -123,6 +125,7 @@ ColumnLayout { valueShowScrollLock = checked; saveSettings(); } + defaultValue: widgetMetadata.showScrollLock } NIcon { @@ -162,5 +165,6 @@ ColumnLayout { valueHideWhenOff = checked; saveSettings(); } + defaultValue: widgetMetadata.hideWhenOff } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml index 4cab94297..b9000b132 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml @@ -16,20 +16,20 @@ ColumnLayout { signal settingsChanged(var settings) // Local state - property string valueHideMode: "hidden" // Default to 'Hide When Empty' + property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode // Deprecated: hideWhenIdle now folded into hideMode = "idle" - property bool valueHideWhenIdle: (widgetData && widgetData.hideWhenIdle !== undefined) ? widgetData.hideWhenIdle : (widgetMetadata && widgetMetadata.hideWhenIdle !== undefined ? widgetMetadata.hideWhenIdle : false) - property bool valueShowAlbumArt: (widgetData && widgetData.showAlbumArt !== undefined) ? widgetData.showAlbumArt : (widgetMetadata && widgetMetadata.showAlbumArt !== undefined ? widgetMetadata.showAlbumArt : false) - property bool valuePanelShowAlbumArt: (widgetData && widgetData.panelShowAlbumArt !== undefined) ? widgetData.panelShowAlbumArt : (widgetMetadata && widgetMetadata.panelShowAlbumArt !== undefined ? widgetMetadata.panelShowAlbumArt : true) - property bool valueShowArtistFirst: (widgetData && widgetData.showArtistFirst !== undefined) ? widgetData.showArtistFirst : (widgetMetadata && widgetMetadata.showArtistFirst !== undefined ? widgetMetadata.showArtistFirst : true) - property bool valueShowVisualizer: (widgetData && widgetData.showVisualizer !== undefined) ? widgetData.showVisualizer : (widgetMetadata && widgetMetadata.showVisualizer !== undefined ? widgetMetadata.showVisualizer : false) - property string valueVisualizerType: (widgetData && widgetData.visualizerType) || (widgetMetadata && widgetMetadata.visualizerType) || "linear" - property string valueScrollingMode: (widgetData && widgetData.scrollingMode) || (widgetMetadata && widgetMetadata.scrollingMode) || "hover" - property int valueMaxWidth: (widgetData && widgetData.maxWidth !== undefined) ? widgetData.maxWidth : (widgetMetadata && widgetMetadata.maxWidth !== undefined ? widgetMetadata.maxWidth : 145) - property bool valueUseFixedWidth: (widgetData && widgetData.useFixedWidth !== undefined) ? widgetData.useFixedWidth : (widgetMetadata && widgetMetadata.useFixedWidth !== undefined ? widgetMetadata.useFixedWidth : false) - property bool valueShowProgressRing: (widgetData && widgetData.showProgressRing !== undefined) ? widgetData.showProgressRing : (widgetMetadata && widgetMetadata.showProgressRing !== undefined ? widgetMetadata.showProgressRing : true) + property bool valueHideWhenIdle: widgetData.hideWhenIdle !== undefined ? widgetData.hideWhenIdle : widgetMetadata.hideWhenIdle + property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt + property bool valuePanelShowAlbumArt: widgetData.panelShowAlbumArt !== undefined ? widgetData.panelShowAlbumArt : widgetMetadata.panelShowAlbumArt + property bool valueShowArtistFirst: widgetData.showArtistFirst !== undefined ? widgetData.showArtistFirst : widgetMetadata.showArtistFirst + property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer + property string valueVisualizerType: widgetData.visualizerType !== undefined ? widgetData.visualizerType : widgetMetadata.visualizerType + property string valueScrollingMode: widgetData.scrollingMode !== undefined ? widgetData.scrollingMode : widgetMetadata.scrollingMode + property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth + property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth + property bool valueShowProgressRing: widgetData.showProgressRing !== undefined ? widgetData.showProgressRing : widgetMetadata.showProgressRing property bool valueCompactMode: widgetData.compactMode !== undefined ? widgetData.compactMode : widgetMetadata.compactMode - property string valueTextColor: (widgetData && widgetData.textColor !== undefined) ? widgetData.textColor : (widgetMetadata && widgetMetadata.textColor !== undefined ? widgetMetadata.textColor : "none") + property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor Component.onCompleted: { if (widgetData && widgetData.hideMode !== undefined) { @@ -82,6 +82,7 @@ ColumnLayout { root.valueHideMode = key; saveSettings(); } + defaultValue: widgetMetadata.hideMode } NToggle { @@ -92,6 +93,7 @@ ColumnLayout { valueShowAlbumArt = checked; saveSettings(); } + defaultValue: widgetMetadata.showAlbumArt } NToggle { @@ -102,6 +104,7 @@ ColumnLayout { valueShowArtistFirst = checked; saveSettings(); } + defaultValue: widgetMetadata.showArtistFirst } NToggle { @@ -112,6 +115,7 @@ ColumnLayout { valueShowVisualizer = checked; saveSettings(); } + defaultValue: widgetMetadata.showVisualizer } NComboBox { @@ -138,6 +142,7 @@ ColumnLayout { saveSettings(); } minimumWidth: 200 + defaultValue: widgetMetadata.visualizerType } NTextInput { @@ -148,6 +153,7 @@ ColumnLayout { placeholderText: widgetMetadata.maxWidth text: valueMaxWidth onEditingFinished: saveSettings() + defaultValue: String(widgetMetadata.maxWidth) } NToggle { @@ -158,6 +164,7 @@ ColumnLayout { valueUseFixedWidth = checked; saveSettings(); } + defaultValue: widgetMetadata.useFixedWidth } NToggle { @@ -168,6 +175,7 @@ ColumnLayout { valueShowProgressRing = checked; saveSettings(); } + defaultValue: widgetMetadata.showProgressRing } NColorChoice { @@ -176,6 +184,7 @@ ColumnLayout { valueTextColor = key; saveSettings(); } + defaultValue: widgetMetadata.textColor } NComboBox { @@ -201,6 +210,7 @@ ColumnLayout { saveSettings(); } minimumWidth: 200 + defaultValue: widgetMetadata.scrollingMode } NDivider { @@ -222,6 +232,7 @@ ColumnLayout { valuePanelShowAlbumArt = checked; saveSettings(); } + defaultValue: widgetMetadata.panelShowAlbumArt } NToggle { @@ -232,5 +243,6 @@ ColumnLayout { valueCompactMode = checked; saveSettings(); } + defaultValue: widgetMetadata.compactMode } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml index b07dc4c41..fdc79ebda 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/MicrophoneSettings.qml @@ -53,6 +53,7 @@ ColumnLayout { valueDisplayMode = key; saveSettings(); } + defaultValue: widgetMetadata.displayMode } NColorChoice { @@ -62,6 +63,7 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } NColorChoice { @@ -70,6 +72,7 @@ ColumnLayout { valueTextColor = key; saveSettings(); } + defaultValue: widgetMetadata.textColor } // Middle click command @@ -80,5 +83,6 @@ ColumnLayout { text: valueMiddleClickCommand onTextChanged: valueMiddleClickCommand = text onEditingFinished: saveSettings() + defaultValue: widgetMetadata.middleClickCommand } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/NetworkSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/NetworkSettings.qml index c308d3575..cd1c53d2b 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/NetworkSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/NetworkSettings.qml @@ -50,6 +50,7 @@ ColumnLayout { root.valueDisplayMode = key; saveSettings(); } + defaultValue: widgetMetadata.displayMode } NColorChoice { @@ -59,6 +60,7 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } NColorChoice { @@ -67,5 +69,6 @@ ColumnLayout { valueTextColor = key; saveSettings(); } + defaultValue: widgetMetadata.textColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/NightLightSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/NightLightSettings.qml index ec0584b61..f6546e836 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/NightLightSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/NightLightSettings.qml @@ -29,5 +29,6 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/NoctaliaPerformanceSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/NoctaliaPerformanceSettings.qml index ec0584b61..f6546e836 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/NoctaliaPerformanceSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/NoctaliaPerformanceSettings.qml @@ -29,5 +29,6 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml index 14df75eb0..4a59f1ac8 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml @@ -40,6 +40,7 @@ ColumnLayout { valueShowUnreadBadge = checked; saveSettings(); } + defaultValue: widgetMetadata.showUnreadBadge } NColorChoice { @@ -49,6 +50,7 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } NColorChoice { @@ -60,6 +62,7 @@ ColumnLayout { saveSettings(); } visible: valueShowUnreadBadge + defaultValue: widgetMetadata.unreadBadgeColor } NToggle { @@ -70,7 +73,8 @@ ColumnLayout { valueHideWhenZero = checked; saveSettings(); } - visible: !valueHideWhenZeroUnread + enabled: !valueHideWhenZeroUnread + defaultValue: widgetMetadata.hideWhenZero } NToggle { @@ -81,5 +85,6 @@ ColumnLayout { valueHideWhenZeroUnread = checked; saveSettings(); } + defaultValue: widgetMetadata.hideWhenZeroUnread } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/PowerProfileSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/PowerProfileSettings.qml index ec0584b61..f6546e836 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/PowerProfileSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/PowerProfileSettings.qml @@ -29,5 +29,6 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/SessionMenuSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/SessionMenuSettings.qml index cf4088ce4..258d0abc7 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/SessionMenuSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/SessionMenuSettings.qml @@ -18,7 +18,7 @@ ColumnLayout { signal settingsChanged(var settings) // Local state - property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : (widgetData.colorName !== undefined ? widgetData.colorName : widgetMetadata.iconColor) + property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor function saveSettings() { var settings = Object.assign({}, widgetData || {}); @@ -33,5 +33,6 @@ ColumnLayout { root.valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/SettingsSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/SettingsSettings.qml index 1b381411e..d5f54b1a7 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/SettingsSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/SettingsSettings.qml @@ -30,5 +30,6 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + valueDefault: widgetMetadata.iconColor } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml index 73143d3e4..8909d12e3 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/SpacerSettings.qml @@ -29,5 +29,6 @@ ColumnLayout { text: widgetData.width || widgetMetadata.width placeholderText: I18n.tr("placeholders.enter-width-pixels") onEditingFinished: saveSettings() + defaultValue: String(widgetMetadata.width) } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml index 171593589..9d4f1e996 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml @@ -26,6 +26,7 @@ ColumnLayout { property bool valueUseMonospaceFont: widgetData.useMonospaceFont !== undefined ? widgetData.useMonospaceFont : widgetMetadata.useMonospaceFont property bool valueUsePadding: widgetData.usePadding !== undefined ? widgetData.usePadding : widgetMetadata.usePadding property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage + property bool valueShowCpuCores: widgetData.showCpuCores !== undefined ? widgetData.showCpuCores : widgetMetadata.showCpuCores property bool valueShowCpuFreq: widgetData.showCpuFreq !== undefined ? widgetData.showCpuFreq : widgetMetadata.showCpuFreq property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp property bool valueShowGpuTemp: widgetData.showGpuTemp !== undefined ? widgetData.showGpuTemp : widgetMetadata.showGpuTemp @@ -47,6 +48,7 @@ ColumnLayout { settings.useMonospaceFont = valueUseMonospaceFont; settings.usePadding = valueUsePadding; settings.showCpuUsage = valueShowCpuUsage; + settings.showCpuCores = valueShowCpuCores; settings.showCpuFreq = valueShowCpuFreq; settings.showCpuTemp = valueShowCpuTemp; settings.showGpuTemp = valueShowGpuTemp; @@ -72,6 +74,7 @@ ColumnLayout { valueCompactMode = checked; saveSettings(); } + defaultValue: widgetMetadata.compactMode } NColorChoice { @@ -81,6 +84,7 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } NColorChoice { @@ -90,6 +94,7 @@ ColumnLayout { saveSettings(); } visible: !valueCompactMode + defaultValue: widgetMetadata.textColor } NToggle { @@ -102,6 +107,7 @@ ColumnLayout { saveSettings(); } visible: !valueCompactMode + defaultValue: widgetMetadata.useMonospaceFont } NToggle { @@ -115,6 +121,7 @@ ColumnLayout { } visible: !valueCompactMode enabled: !isVerticalBar && valueUseMonospaceFont + defaultValue: widgetMetadata.usePadding } NDivider { @@ -131,6 +138,20 @@ ColumnLayout { valueShowCpuUsage = checked; saveSettings(); } + defaultValue: widgetMetadata.showCpuUsage + } + + NToggle { + id: showCpuCores + Layout.fillWidth: true + label: I18n.tr("bar.system-monitor.cpu-cores-label") + description: I18n.tr("bar.system-monitor.cpu-cores-description") + checked: valueShowCpuCores + onToggled: checked => { + valueShowCpuCores = checked; + saveSettings(); + } + visible: valueCompactMode } NToggle { @@ -143,6 +164,7 @@ ColumnLayout { valueShowCpuFreq = checked; saveSettings(); } + defaultValue: widgetMetadata.showCpuFreq } NToggle { @@ -155,6 +177,7 @@ ColumnLayout { valueShowCpuTemp = checked; saveSettings(); } + defaultValue: widgetMetadata.showCpuTemp } NToggle { @@ -167,6 +190,7 @@ ColumnLayout { valueShowLoadAverage = checked; saveSettings(); } + defaultValue: widgetMetadata.showLoadAverage } NToggle { @@ -180,6 +204,7 @@ ColumnLayout { saveSettings(); } visible: SystemStatService.gpuAvailable + defaultValue: widgetMetadata.showGpuTemp } NToggle { @@ -192,6 +217,7 @@ ColumnLayout { valueShowMemoryUsage = checked; saveSettings(); } + defaultValue: widgetMetadata.showMemoryUsage } NToggle { @@ -205,6 +231,7 @@ ColumnLayout { saveSettings(); } visible: valueShowMemoryUsage + defaultValue: widgetMetadata.showMemoryAsPercent } NToggle { @@ -217,6 +244,7 @@ ColumnLayout { valueShowSwapUsage = checked; saveSettings(); } + defaultValue: widgetMetadata.showSwapUsage } NToggle { @@ -229,6 +257,7 @@ ColumnLayout { valueShowNetworkStats = checked; saveSettings(); } + defaultValue: widgetMetadata.showNetworkStats } NDivider { @@ -245,6 +274,7 @@ ColumnLayout { valueShowDiskUsage = checked; saveSettings(); } + defaultValue: widgetMetadata.showDiskUsage } NToggle { @@ -257,6 +287,7 @@ ColumnLayout { valueShowDiskUsageAsPercent = checked; saveSettings(); } + defaultValue: widgetMetadata.showDiskUsageAsPercent } NToggle { @@ -269,6 +300,7 @@ ColumnLayout { valueShowDiskAvailable = checked; saveSettings(); } + defaultValue: widgetMetadata.showDiskAvailable } NComboBox { @@ -288,5 +320,6 @@ ColumnLayout { valueDiskPath = key; saveSettings(); } + defaultValue: widgetMetadata.diskPath } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml index 2ff8faac8..8d3b81594 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/TaskbarSettings.qml @@ -18,7 +18,7 @@ ColumnLayout { readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" // Local state - property string valueHideMode: "hidden" + property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode property bool valueOnlyActiveWorkspaces: widgetData.onlyActiveWorkspaces !== undefined ? widgetData.onlyActiveWorkspaces : widgetMetadata.onlyActiveWorkspaces property bool valueOnlySameOutput: widgetData.onlySameOutput !== undefined ? widgetData.onlySameOutput : widgetMetadata.onlySameOutput property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons @@ -75,6 +75,7 @@ ColumnLayout { root.valueHideMode = key; saveSettings(); } + defaultValue: widgetMetadata.hideMode } NToggle { @@ -86,6 +87,7 @@ ColumnLayout { root.valueOnlySameOutput = checked; saveSettings(); } + defaultValue: widgetMetadata.onlySameOutput } NToggle { @@ -97,6 +99,7 @@ ColumnLayout { root.valueOnlyActiveWorkspaces = checked; saveSettings(); } + defaultValue: widgetMetadata.onlyActiveWorkspaces } NToggle { @@ -108,6 +111,7 @@ ColumnLayout { root.valueColorizeIcons = checked; saveSettings(); } + defaultValue: widgetMetadata.colorizeIcons } NToggle { @@ -119,6 +123,7 @@ ColumnLayout { root.valueShowPinnedApps = checked; saveSettings(); } + defaultValue: widgetMetadata.showPinnedApps } NValueSlider { @@ -128,7 +133,9 @@ ColumnLayout { from: 0.5 to: 1 stepSize: 0.01 + showReset: true value: root.valueIconScale + defaultValue: widgetMetadata.iconScale onMoved: value => { root.valueIconScale = value; saveSettings(); @@ -146,6 +153,7 @@ ColumnLayout { saveSettings(); } enabled: !isVerticalBar + defaultValue: widgetMetadata.showTitle } NTextInput { @@ -157,6 +165,7 @@ ColumnLayout { text: widgetData.titleWidth || widgetMetadata.titleWidth placeholderText: I18n.tr("placeholders.enter-width-pixels") onEditingFinished: saveSettings() + defaultValue: String(widgetMetadata.titleWidth) } NToggle { @@ -169,6 +178,7 @@ ColumnLayout { root.valueSmartWidth = checked; saveSettings(); } + defaultValue: widgetMetadata.smartWidth } NValueSlider { @@ -179,7 +189,9 @@ ColumnLayout { from: 10 to: 100 stepSize: 5 + showReset: true value: root.valueMaxTaskbarWidth + defaultValue: widgetMetadata.maxTaskbarWidth onMoved: value => { root.valueMaxTaskbarWidth = Math.round(value); saveSettings(); diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/TraySettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/TraySettings.qml index fd566a82d..e3fb99b01 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/TraySettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/TraySettings.qml @@ -48,6 +48,7 @@ ColumnLayout { root.valueDrawerEnabled = checked; saveSettings(); } + defaultValue: widgetMetadata.drawerEnabled } NColorChoice { @@ -59,6 +60,7 @@ ColumnLayout { saveSettings(); } visible: root.valueDrawerEnabled + defaultValue: widgetMetadata.chevronColor } NToggle { @@ -70,6 +72,7 @@ ColumnLayout { root.valueColorizeIcons = checked; saveSettings(); } + defaultValue: widgetMetadata.colorizeIcons } NToggle { @@ -81,6 +84,7 @@ ColumnLayout { root.valueHidePassive = checked; saveSettings(); } + defaultValue: widgetMetadata.hidePassive } ColumnLayout { diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml index b07dc4c41..fdc79ebda 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/VolumeSettings.qml @@ -53,6 +53,7 @@ ColumnLayout { valueDisplayMode = key; saveSettings(); } + defaultValue: widgetMetadata.displayMode } NColorChoice { @@ -62,6 +63,7 @@ ColumnLayout { valueIconColor = key; saveSettings(); } + defaultValue: widgetMetadata.iconColor } NColorChoice { @@ -70,6 +72,7 @@ ColumnLayout { valueTextColor = key; saveSettings(); } + defaultValue: widgetMetadata.textColor } // Middle click command @@ -80,5 +83,6 @@ ColumnLayout { text: valueMiddleClickCommand onTextChanged: valueMiddleClickCommand = text onEditingFinished: saveSettings() + defaultValue: widgetMetadata.middleClickCommand } } diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml index daa8e1530..5a3ac254e 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/WorkspaceSettings.qml @@ -33,6 +33,7 @@ ColumnLayout { property string valueEmptyColor: widgetData.emptyColor !== undefined ? widgetData.emptyColor : widgetMetadata.emptyColor property bool valueShowBadge: widgetData.showBadge !== undefined ? widgetData.showBadge : widgetMetadata.showBadge property real valuePillSize: widgetData.pillSize !== undefined ? widgetData.pillSize : widgetMetadata.pillSize + property string valueFontWeight: widgetData.fontWeight !== undefined ? widgetData.fontWeight : widgetMetadata.fontWeight function saveSettings() { var settings = Object.assign({}, widgetData || {}); @@ -52,6 +53,7 @@ ColumnLayout { settings.emptyColor = valueEmptyColor; settings.showBadge = valueShowBadge; settings.pillSize = valuePillSize; + settings.fontWeight = valueFontWeight; settingsChanged(settings); } @@ -115,6 +117,36 @@ ColumnLayout { visible: !valueShowApplications } + NComboBox { + id: fontWeightCombo + label: I18n.tr("bar.workspace.font-weight-label") + description: I18n.tr("bar.workspace.font-weight-description") + model: [ + { + "key": "regular", + "name": I18n.tr("common.font-weight-regular") + }, + { + "key": "medium", + "name": I18n.tr("common.font-weight-medium") + }, + { + "key": "semibold", + "name": I18n.tr("common.font-weight-semibold") + }, + { + "key": "bold", + "name": I18n.tr("common.font-weight-bold") + }, + ] + currentKey: widgetData.fontWeight || widgetMetadata.fontWeight + onSelected: key => { + valueFontWeight = key; + saveSettings(); + } + minimumWidth: 200 + } + NToggle { label: I18n.tr("bar.workspace.hide-unoccupied-label") description: I18n.tr("bar.workspace.hide-unoccupied-description") @@ -197,7 +229,9 @@ ColumnLayout { from: 0 to: 1 stepSize: 0.01 + showReset: true value: valueUnfocusedIconsOpacity + defaultValue: widgetMetadata.unfocusedIconsOpacity onMoved: value => { valueUnfocusedIconsOpacity = value; saveSettings(); @@ -212,7 +246,9 @@ ColumnLayout { from: 0 to: 1 stepSize: 0.01 + showReset: true value: valueGroupedBorderOpacity + defaultValue: widgetMetadata.groupedBorderOpacity onMoved: value => { valueGroupedBorderOpacity = value; saveSettings(); @@ -227,7 +263,9 @@ ColumnLayout { from: 0.5 to: 1 stepSize: 0.01 + showReset: true value: valueIconScale + defaultValue: widgetMetadata.iconScale onMoved: value => { valueIconScale = value; saveSettings(); diff --git a/Modules/Panels/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml b/Modules/Panels/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml index 7bee751b0..df0641732 100644 --- a/Modules/Panels/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Panels/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml @@ -20,15 +20,15 @@ ColumnLayout { QtObject { id: _settings - property string icon: (widgetData && widgetData.icon !== undefined) ? widgetData.icon : (widgetMetadata && widgetMetadata.icon ? widgetMetadata.icon : "") - property string onClicked: (widgetData && widgetData.onClicked !== undefined) ? widgetData.onClicked : (widgetMetadata && widgetMetadata.onClicked ? widgetMetadata.onClicked : "") - property string onRightClicked: (widgetData && widgetData.onRightClicked !== undefined) ? widgetData.onRightClicked : (widgetMetadata && widgetMetadata.onRightClicked ? widgetMetadata.onRightClicked : "") - property string onMiddleClicked: (widgetData && widgetData.onMiddleClicked !== undefined) ? widgetData.onMiddleClicked : (widgetMetadata && widgetMetadata.onMiddleClicked ? widgetMetadata.onMiddleClicked : "") + property string icon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon + property string onClicked: widgetData.onClicked !== undefined ? widgetData.onClicked : widgetMetadata.onClicked + property string onRightClicked: widgetData.onRightClicked !== undefined ? widgetData.onRightClicked : widgetMetadata.onRightClicked + property string onMiddleClicked: widgetData.onMiddleClicked !== undefined ? widgetData.onMiddleClicked : widgetMetadata.onMiddleClicked property ListModel _stateChecksListModel: ListModel {} property string stateChecksJson: "[]" - property string generalTooltipText: (widgetData && widgetData.generalTooltipText !== undefined) ? widgetData.generalTooltipText : (widgetMetadata && widgetMetadata.generalTooltipText ? widgetMetadata.generalTooltipText : "") - property bool enableOnStateLogic: (widgetData && widgetData.enableOnStateLogic !== undefined) ? widgetData.enableOnStateLogic : (widgetMetadata && widgetMetadata.enableOnStateLogic !== undefined ? widgetMetadata.enableOnStateLogic : false) - property bool showExecTooltip: (widgetData && widgetData.showExecTooltip !== undefined) ? widgetData.showExecTooltip : (widgetMetadata && widgetMetadata.showExecTooltip !== undefined ? widgetMetadata.showExecTooltip : true) + property string generalTooltipText: widgetData.generalTooltipText !== undefined ? widgetData.generalTooltipText : widgetMetadata.generalTooltipText + property bool enableOnStateLogic: widgetData.enableOnStateLogic !== undefined ? widgetData.enableOnStateLogic : widgetMetadata.enableOnStateLogic + property bool showExecTooltip: widgetData.showExecTooltip !== undefined ? widgetData.showExecTooltip : widgetMetadata.showExecTooltip function populateStateChecks() { try { @@ -143,6 +143,7 @@ ColumnLayout { _settings.generalTooltipText = text; saveSettings(); } + defaultValue: widgetMetadata.generalTooltipText } NToggle { @@ -154,6 +155,7 @@ ColumnLayout { _settings.showExecTooltip = checked; saveSettings(); } + defaultValue: widgetMetadata.showExecTooltip } NTextInput { @@ -166,6 +168,7 @@ ColumnLayout { _settings.onClicked = text; saveSettings(); } + defaultValue: widgetMetadata.onClicked } NTextInput { @@ -178,6 +181,7 @@ ColumnLayout { _settings.onRightClicked = text; saveSettings(); } + defaultValue: widgetMetadata.onRightClicked } NTextInput { @@ -190,6 +194,7 @@ ColumnLayout { _settings.onMiddleClicked = text; saveSettings(); } + defaultValue: widgetMetadata.onMiddleClicked } NDivider {} @@ -204,6 +209,7 @@ ColumnLayout { _settings.enableOnStateLogic = checked; saveSettings(); } + defaultValue: widgetMetadata.enableOnStateLogic } ColumnLayout { diff --git a/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml b/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml index f5b32a09e..73f80a7d5 100644 --- a/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml +++ b/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml @@ -63,7 +63,7 @@ Popup { NText { text: I18n.tr("system.widget-settings-title", { - "widget": root.widgetId + "widget": DesktopWidgetRegistry.getWidgetDisplayName(root.widgetId) }) pointSize: Style.fontSizeL font.weight: Style.fontWeightBold @@ -136,6 +136,40 @@ Popup { } } + // Mouse area for a draggable popup + MouseArea { + x: titleRow.x + y: titleRow.y + width: titleRow.width + height: titleRow.height + z: -1 + + cursorShape: Qt.OpenHandCursor + + property real pressX: 0 + property real pressY: 0 + + onPressed: mouse => { + pressX = mouse.x; + pressY = mouse.y; + cursorShape = Qt.ClosedHandCursor; + } + + onReleased: { + cursorShape = Qt.OpenHandCursor; + } + + onPositionChanged: mouse => { + if (pressed) { + var deltaX = mouse.x - pressX; + var deltaY = mouse.y - pressY; + + root.x += deltaX; + root.y += deltaY; + } + } + } + Timer { id: saveTimer running: false @@ -154,6 +188,12 @@ Popup { saveTimer.start(); } } + + function onSettingsSaved(newSettings) { + if (newSettings) { + root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings); + } + } } function saveAndClose() { @@ -172,19 +212,42 @@ Popup { var pluginId = widgetId.replace("plugin:", ""); var manifest = PluginRegistry.getPluginManifest(pluginId); - if (!manifest || !manifest.entryPoints || !manifest.entryPoints.settings) { - Logger.w("DesktopWidgetSettingsDialog", "Plugin does not have settings:", pluginId); - return; - } - var pluginDir = PluginRegistry.getPluginDir(pluginId); - var settingsPath = "file://" + pluginDir + "/" + manifest.entryPoints.settings; var loadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0; var api = PluginService.getPluginAPI(pluginId); - settingsLoader.setSource(settingsPath + "?v=" + loadVersion, { - "pluginApi": api - }); + var settingsPath; + if (manifest && manifest.entryPoints && manifest.entryPoints.desktopWidgetSettings) { + settingsPath = "file://" + pluginDir + "/" + manifest.entryPoints.desktopWidgetSettings; + + var widgetSettings = {}; + widgetSettings.data = widgetData || {}; + widgetSettings.metadata = DesktopWidgetRegistry.widgetMetadata[widgetId] || {}; + widgetSettings.save = function () { + var newSettings = Object.assign({}, widgetSettings.data); + root.settingsCache = newSettings; + saveTimer.start(); + }; + + settingsLoader.setSource(settingsPath + "?v=" + loadVersion, { + "pluginApi": api, + "widgetSettings": widgetSettings + }); + } else { + Logger.w("DesktopWidgetSettingsDialog", "Plugin does not have desktop widget settings:", pluginId); + + // Fallback to the plugin settings + if (manifest && manifest.entryPoints && manifest.entryPoints.settings) { + settingsPath = "file://" + pluginDir + "/" + manifest.entryPoints.settings; + + settingsLoader.setSource(settingsPath + "?v=" + loadVersion, { + "pluginApi": api + }); + } else { + Logger.w("DesktopWidgetSettingsDialog", "Plugin does not have settings:", pluginId); + } + } + return; } diff --git a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/AudioVisualizerSettings.qml b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/AudioVisualizerSettings.qml new file mode 100644 index 000000000..ea65462e8 --- /dev/null +++ b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/AudioVisualizerSettings.qml @@ -0,0 +1,154 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginM + + property var widgetData: null + property var widgetMetadata: null + + signal settingsChanged(var settings) + + property int valueWidth: widgetData.width !== undefined ? widgetData.width : widgetMetadata.width + property int valueHeight: widgetData.height !== undefined ? widgetData.height : widgetMetadata.height + property string valueVisualizerType: widgetData.visualizerType !== undefined ? widgetData.visualizerType : widgetMetadata.visualizerType + property string valueColorName: widgetData.colorName !== undefined ? widgetData.colorName : widgetMetadata.colorName + property bool valueHideWhenIdle: widgetData.hideWhenIdle !== undefined ? widgetData.hideWhenIdle : widgetMetadata.hideWhenIdle + property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}); + settings.width = valueWidth; + settings.height = valueHeight; + settings.visualizerType = valueVisualizerType; + settings.colorName = valueColorName; + settings.hideWhenIdle = valueHideWhenIdle; + settings.showBackground = valueShowBackground; + settings.roundedCorners = valueRoundedCorners; + settingsChanged(settings); + } + + NTextInput { + id: widthInput + Layout.fillWidth: true + label: I18n.tr("common.width") + description: I18n.tr("bar.audio-visualizer.width-description") + text: String(valueWidth) + placeholderText: I18n.tr("placeholders.enter-width-pixels") + inputMethodHints: Qt.ImhDigitsOnly + onEditingFinished: { + const parsed = parseInt(text); + if (!isNaN(parsed) && parsed > 0) { + valueWidth = parsed; + saveSettings(); + } else { + text = String(valueWidth); + } + } + defaultValue: String(widgetMetadata.width) + } + + NTextInput { + id: heightInput + Layout.fillWidth: true + label: I18n.tr("common.height") + description: I18n.tr("bar.audio-visualizer.height-description") + text: String(valueHeight) + placeholderText: I18n.tr("placeholders.enter-width-pixels") + inputMethodHints: Qt.ImhDigitsOnly + onEditingFinished: { + const parsed = parseInt(text); + if (!isNaN(parsed) && parsed > 0) { + valueHeight = parsed; + saveSettings(); + } else { + text = String(valueHeight); + } + } + defaultValue: String(widgetMetadata.height) + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.audio.visualizer-type-label") + description: I18n.tr("panels.desktop-widgets.media-player-visualizer-type-description") + model: [ + { + "key": "linear", + "name": I18n.tr("options.visualizer-types.linear") + }, + { + "key": "mirrored", + "name": I18n.tr("options.visualizer-types.mirrored") + }, + { + "key": "wave", + "name": I18n.tr("options.visualizer-types.wave") + } + ] + currentKey: valueVisualizerType + onSelected: key => { + valueVisualizerType = key; + saveSettings(); + } + defaultValue: widgetMetadata.visualizerType + } + + NColorChoice { + Layout.fillWidth: true + label: I18n.tr("bar.audio-visualizer.color-name-label") + description: I18n.tr("bar.audio-visualizer.color-name-description") + currentKey: valueColorName + onSelected: key => { + valueColorName = key; + saveSettings(); + } + defaultValue: widgetMetadata.colorName + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.audio-visualizer.hide-when-idle-label") + description: I18n.tr("bar.audio-visualizer.hide-when-idle-description") + checked: valueHideWhenIdle + onToggled: checked => { + valueHideWhenIdle = checked; + saveSettings(); + } + defaultValue: widgetMetadata.hideWhenIdle + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.desktop-widgets.clock-show-background-label") + description: I18n.tr("panels.desktop-widgets.media-player-show-background-description") + checked: valueShowBackground + onToggled: checked => { + valueShowBackground = checked; + saveSettings(); + } + defaultValue: widgetMetadata.showBackground + } + + NToggle { + Layout.fillWidth: true + visible: valueShowBackground + label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label") + description: I18n.tr("panels.desktop-widgets.media-player-rounded-corners-description") + checked: valueRoundedCorners + onToggled: checked => { + valueRoundedCorners = checked; + saveSettings(); + } + defaultValue: widgetMetadata.roundedCorners + } +} diff --git a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/ClockSettings.qml b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/ClockSettings.qml index 409fa846f..3103ba839 100644 --- a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/ClockSettings.qml +++ b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/ClockSettings.qml @@ -16,11 +16,11 @@ ColumnLayout { signal settingsChanged(var settings) property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground - property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : true + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners property string valueClockStyle: widgetData.clockStyle !== undefined ? widgetData.clockStyle : widgetMetadata.clockStyle property string valueClockColor: widgetData.clockColor !== undefined ? widgetData.clockColor : widgetMetadata.clockColor property bool valueUseCustomFont: widgetData.useCustomFont !== undefined ? widgetData.useCustomFont : widgetMetadata.useCustomFont - property string valueCustomFont: widgetData.customFont !== undefined ? widgetData.customFont : "" + property string valueCustomFont: widgetData.customFont !== undefined ? widgetData.customFont : widgetMetadata.custonFont property string valueFormat: widgetData.format !== undefined ? widgetData.format : widgetMetadata.format // Track the currently focused input field @@ -97,39 +97,18 @@ ColumnLayout { valueClockStyle = key; saveSettings(); } + defaultValue: widgetMetadata.clockStyle } - NComboBox { + NColorChoice { label: I18n.tr("common.select-color") description: I18n.tr("common.select-color-description") - model: [ - { - "name": I18n.tr("common.none"), - "key": "none" - }, - { - "key": "primary", - "name": I18n.tr("common.primary") - }, - { - "key": "secondary", - "name": I18n.tr("common.secondary") - }, - { - "key": "tertiary", - "name": I18n.tr("common.tertiary") - }, - { - "key": "error", - "name": I18n.tr("common.error") - } - ] currentKey: valueClockColor onSelected: key => { valueClockColor = key; saveSettings(); } - minimumWidth: 200 + defaultValue: widgetMetadata.clockColor } NToggle { @@ -141,6 +120,7 @@ ColumnLayout { valueUseCustomFont = checked; saveSettings(); } + defaultValue: widgetMetadata.useCustomFont } NSearchableComboBox { @@ -158,6 +138,8 @@ ColumnLayout { valueCustomFont = key; saveSettings(); } + enabled: valueClockStyle === "minimal" + defaultValue: Settings.data.ui.fontDefault } NDivider { @@ -203,6 +185,7 @@ ColumnLayout { }); } } + defaultValue: widgetMetadata.format } } @@ -290,6 +273,7 @@ ColumnLayout { valueShowBackground = checked; saveSettings(); } + defaultValue: widgetMetadata.showBackground } NToggle { @@ -302,5 +286,6 @@ ColumnLayout { valueRoundedCorners = checked; saveSettings(); } + defaultValue: widgetMetadata.roundedCorners } } diff --git a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml index a3f8c15e7..9bd9a6c85 100644 --- a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml +++ b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml @@ -14,12 +14,12 @@ ColumnLayout { signal settingsChanged(var settings) property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground - property string valueVisualizerType: (widgetData.visualizerType && widgetData.visualizerType !== "") ? widgetData.visualizerType : (widgetMetadata.visualizerType || "linear") + property string valueVisualizerType: widgetData.visualizerType ? widgetData.visualizerType : widgetMetadata.visualizerType property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode - property bool valueShowButtons: widgetData.showButtons !== undefined ? widgetData.showButtons : (widgetMetadata.showButtons !== undefined ? widgetMetadata.showButtons : true) - property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : (widgetMetadata.showAlbumArt !== undefined ? widgetMetadata.showAlbumArt : true) - property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : (widgetMetadata.showVisualizer !== undefined ? widgetMetadata.showVisualizer : true) - property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : (widgetMetadata.roundedCorners !== undefined ? widgetMetadata.roundedCorners : true) + property bool valueShowButtons: widgetData.showButtons !== undefined ? widgetData.showButtons : widgetMetadata.showButtons + property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt + property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners function saveSettings() { var settings = Object.assign({}, widgetData || {}); @@ -42,6 +42,7 @@ ColumnLayout { valueShowBackground = checked; saveSettings(); } + defaultValue: widgetMetadata.showBackground } NToggle { @@ -53,6 +54,7 @@ ColumnLayout { valueRoundedCorners = checked; saveSettings(); } + defaultValue: widgetMetadata.roundedCorners } NToggle { @@ -64,6 +66,7 @@ ColumnLayout { valueShowAlbumArt = checked; saveSettings(); } + defaultValue: widgetMetadata.showAlbumArt } NToggle { @@ -75,6 +78,7 @@ ColumnLayout { valueShowVisualizer = checked; saveSettings(); } + defaultValue: widgetMetadata.showVisualizer } NToggle { @@ -86,6 +90,7 @@ ColumnLayout { valueShowButtons = checked; saveSettings(); } + defaultValue: widgetMetadata.showButtons } NComboBox { @@ -112,6 +117,7 @@ ColumnLayout { valueVisualizerType = key; saveSettings(); } + defaultValue: widgetMetadata.visualizerType } NComboBox { @@ -137,5 +143,6 @@ ColumnLayout { valueHideMode = key; saveSettings(); } + defaultValue: widgetMetadata.hideMode } } diff --git a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/SystemStatSettings.qml b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/SystemStatSettings.qml index 508fc44ad..fe7e13589 100644 --- a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/SystemStatSettings.qml +++ b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/SystemStatSettings.qml @@ -18,8 +18,8 @@ ColumnLayout { property string valueStatType: widgetData.statType !== undefined ? widgetData.statType : widgetMetadata.statType property string valueDiskPath: widgetData.diskPath !== undefined ? widgetData.diskPath : widgetMetadata.diskPath property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground - property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : (widgetMetadata.roundedCorners !== undefined ? widgetMetadata.roundedCorners : true) - property string valueLayout: widgetData.layout !== undefined ? widgetData.layout : (widgetMetadata.layout !== undefined ? widgetMetadata.layout : "side") + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners + property string valueLayout: widgetData.layout !== undefined ? widgetData.layout : widgetMetadata.layout function saveSettings() { var settings = Object.assign({}, widgetData || {}); @@ -67,6 +67,7 @@ ColumnLayout { valueStatType = key; saveSettings(); } + defaultValue: widgetMetadata.statType } NComboBox { @@ -86,6 +87,7 @@ ColumnLayout { valueDiskPath = key; saveSettings(); } + defaultValue: widgetMetadata.diskPath } NDivider { @@ -101,6 +103,7 @@ ColumnLayout { valueShowBackground = checked; saveSettings(); } + defaultValue: widgetMetadata.showBackground } NToggle { @@ -113,6 +116,7 @@ ColumnLayout { valueRoundedCorners = checked; saveSettings(); } + defaultValue: widgetMetadata.roundedCorners } NDivider { @@ -139,5 +143,6 @@ ColumnLayout { valueLayout = key; saveSettings(); } + defaultValue: widgetMetadata.layout } } diff --git a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/WeatherSettings.qml b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/WeatherSettings.qml index 5fb14a6ba..4406921a0 100644 --- a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/WeatherSettings.qml +++ b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/WeatherSettings.qml @@ -14,10 +14,12 @@ ColumnLayout { signal settingsChanged(var settings) property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners function saveSettings() { var settings = Object.assign({}, widgetData || {}); settings.showBackground = valueShowBackground; + settings.roundedCorners = valueRoundedCorners; settingsChanged(settings); } @@ -30,5 +32,19 @@ ColumnLayout { valueShowBackground = checked; saveSettings(); } + defaultValue: widgetMetadata.showBackground + } + + NToggle { + Layout.fillWidth: true + visible: valueShowBackground + label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label") + description: I18n.tr("panels.desktop-widgets.clock-rounded-corners-description") + checked: valueRoundedCorners + onToggled: checked => { + valueRoundedCorners = checked; + saveSettings(); + } + defaultValue: widgetMetadata.roundedCorners } } diff --git a/Modules/Panels/Settings/SettingsContent.qml b/Modules/Panels/Settings/SettingsContent.qml index 4e63d28dc..957d42883 100644 --- a/Modules/Panels/Settings/SettingsContent.qml +++ b/Modules/Panels/Settings/SettingsContent.qml @@ -398,6 +398,15 @@ Item { } } + // Clear highlight when the user scrolls so the outline doesn't stay in place + Connections { + target: root.activeScrollView ? root.activeScrollView.contentItem : null + enabled: root.highlightLabelKey !== "" && !highlightScrollTimer.running + function onContentYChanged() { + root.clearHighlightImmediately(); + } + } + // Save sidebar state when it changes onSidebarExpandedChanged: { ShellState.setSettingsSidebarExpanded(sidebarExpanded); @@ -586,7 +595,7 @@ Item { }, { "id": SettingsPanel.Tab.Idle, - "label": "common.idle", + "label": "panels.idle.title", "icon": "settings-idle", "source": idleTab }, @@ -615,8 +624,8 @@ Item { "source": regionTab }, { - "id": SettingsPanel.Tab.SystemMonitor, - "label": "system-monitor.title", + "id": SettingsPanel.Tab.System, + "label": "panels.system.title", "icon": "settings-system-monitor", "source": systemMonitorTab }, diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index af3ef58e2..71220b1c4 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -25,12 +25,12 @@ SmartPanel { readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0 forceAttachToBar: attachToBar - panelAnchorHorizontalCenter: attachToBar ? (barPosition === "top" || barPosition === "bottom") : true - panelAnchorVerticalCenter: attachToBar ? (barPosition === "left" || barPosition === "right") : true - panelAnchorTop: attachToBar && barPosition === "top" - panelAnchorBottom: attachToBar && barPosition === "bottom" - panelAnchorLeft: attachToBar && barPosition === "left" - panelAnchorRight: attachToBar && barPosition === "right" + panelAnchorHorizontalCenter: !root.useButtonPosition && (attachToBar ? (barPosition === "top" || barPosition === "bottom") : true) + panelAnchorVerticalCenter: !root.useButtonPosition && (attachToBar ? (barPosition === "left" || barPosition === "right") : true) + panelAnchorTop: !root.useButtonPosition && attachToBar && barPosition === "top" + panelAnchorBottom: !root.useButtonPosition && attachToBar && barPosition === "bottom" + panelAnchorLeft: !root.useButtonPosition && attachToBar && barPosition === "left" + panelAnchorRight: !root.useButtonPosition && attachToBar && barPosition === "right" onAttachToBarChanged: { if (isPanelOpen) { @@ -89,7 +89,7 @@ SmartPanel { Notifications, Plugins, SessionMenu, - SystemMonitor, + System, UserInterface, Wallpaper } @@ -129,7 +129,15 @@ SmartPanel { // Panel mode: replicate SmartPanel.open() logic if (!buttonItem && buttonName) { - buttonItem = BarService.lookupWidget(buttonName, screen.name); + if (typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined) { + root.buttonItem = null; + root.buttonPosition = buttonName; + root.buttonWidth = 0; + root.buttonHeight = 0; + root.useButtonPosition = true; + } else { + buttonItem = BarService.lookupWidget(buttonName, screen.name); + } } if (buttonItem) { @@ -139,7 +147,7 @@ SmartPanel { root.buttonWidth = buttonItem.width; root.buttonHeight = buttonItem.height; root.useButtonPosition = true; - } else { + } else if (!(buttonName && typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined)) { root.buttonItem = null; root.useButtonPosition = false; } diff --git a/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml b/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml index efdfa03d3..e497e02d8 100644 --- a/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml +++ b/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml @@ -31,21 +31,35 @@ ColumnLayout { property string latestVersion: GitHubService.latestVersion property string currentVersion: UpdateService.currentVersion property string commitInfo: "" + property string qsVersion: "" + property string qsRevision: "" readonly property bool isGitVersion: root.currentVersion.endsWith("-git") - readonly property int giga: (1024 * 1024 * 1024) + readonly property int gigaB: (1024 * 1024 * 1024) + readonly property int gigaD: (1000 * 1000 * 1000) - // Update status: compare versions (strip -git suffix for comparison) - readonly property string installedBase: root.currentVersion.replace("-git", "") + // Update status: compare versions readonly property bool updateAvailable: { - if (!root.latestVersion || !root.installedBase) + if (!root.latestVersion || !root.currentVersion || root.latestVersion === I18n.tr("common.unknown")) return false; - return root.latestVersion !== root.installedBase && !root.isGitVersion; + return UpdateService.compareVersions(root.latestVersion, root.currentVersion) > 0 && !root.isGitVersion; } readonly property bool isUpToDate: { - if (!root.latestVersion || !root.installedBase) + if (!root.latestVersion || !root.currentVersion || root.latestVersion === I18n.tr("common.unknown")) return false; - return root.latestVersion === root.installedBase; + return UpdateService.compareVersions(root.latestVersion, root.currentVersion) <= 0; + } + + readonly property bool qsUpdateAvailable: { + if (!GitHubService.latestQSVersion || !root.qsVersion || GitHubService.latestQSVersion === I18n.tr("common.unknown")) + return false; + return UpdateService.compareVersions(GitHubService.latestQSVersion, root.qsVersion) > 0; + } + + readonly property bool qsIsUpToDate: { + if (!GitHubService.latestQSVersion || !root.qsVersion || GitHubService.latestQSVersion === I18n.tr("common.unknown")) + return false; + return UpdateService.compareVersions(GitHubService.latestQSVersion, root.qsVersion) <= 0; } // System info properties @@ -96,7 +110,7 @@ ColumnLayout { version: UpdateService.currentVersion, compositor: TelemetryService.getCompositorType(), os: HostService.osPretty || "Unknown", - ramGb: Math.round((root.getModule("Memory")?.result?.total || 0) / root.giga), + ramGb: Math.round((root.getModule("Memory")?.result?.total || 0) / root.gigaB), monitors: monitors, ui: { scaleRatio: Settings.data.general.scaleRatio, @@ -114,12 +128,21 @@ ColumnLayout { } function copyInfoToClipboard() { - let info = "Noctalia Shell\n"; - info += "==============\n"; - info += "Installed version: " + root.currentVersion + "\n"; + let info = "Noctalia Shell: " + root.currentVersion; if (root.isGitVersion && root.commitInfo) { - info += "Git commit: " + root.commitInfo + "\n"; + info += " (" + root.commitInfo + ")"; } + info += "\n"; + + if (root.qsVersion) { + let qsV = root.qsVersion.startsWith("v") ? root.qsVersion : "v" + root.qsVersion; + info += "Noctalia QS: " + qsV; + if (root.qsRevision) { + info += " (" + root.qsRevision + ")"; + } + info += "\n"; + } + info += "\nSystem Information\n"; info += "==================\n"; if (root.systemInfo) { @@ -127,6 +150,7 @@ ColumnLayout { const kernel = root.getModule("Kernel"); const title = root.getModule("Title"); const product = root.getModule("Host"); + const board = root.getModule("Board"); const cpu = root.getModule("CPU"); const gpu = root.getModule("GPU"); const mem = root.getModule("Memory"); @@ -135,12 +159,13 @@ ColumnLayout { info += "Kernel: " + (kernel?.result?.release || "N/A") + "\n"; info += "Host: " + (title?.result?.hostName || "N/A") + "\n"; info += "Product: " + (product?.result?.name || "N/A") + "\n"; + info += "Board: " + (board?.result?.name || "N/A") + "\n"; info += "CPU: " + (cpu?.result?.cpu || "N/A") + "\n"; if (gpu?.result && Array.isArray(gpu.result) && gpu.result.length > 0) { info += "GPU: " + gpu.result.map(g => g.name || "Unknown").join(", ") + "\n"; } if (mem?.result) { - info += "Memory: " + SystemStatService.formatGigabytes(mem.result.total / root.giga) + "\n"; + info += "Memory: " + (mem.result.total / root.gigaB).toFixed(1) + " GB \n"; } if (wm?.result) { info += "WM: " + (wm.result.prettyName || wm.result.processName || "N/A") + "\n"; @@ -162,6 +187,7 @@ ColumnLayout { Component.onCompleted: { // Check if fastfetch is available before trying to run it checkFastfetchProcess.running = true; + qsVersionProcess.running = true; Logger.d("VersionSubTab", "Current version:", root.currentVersion); Logger.d("VersionSubTab", "Is git version:", root.isGitVersion); @@ -184,90 +210,11 @@ ColumnLayout { Logger.d("VersionSubTab", "Component.onCompleted - Could not extract commit from NixOS path, trying fallback"); } } - fetchGitCommit(); - return; - } else { - // On non-NixOS systems, check for pacman first. - whichPacmanProcess.running = true; - return; } + fetchGitCommit(); } } - Timer { - id: gitFallbackTimer - interval: 500 - running: false - onTriggered: { - if (!root.commitInfo) { - fetchGitCommit(); - } - } - } - - Process { - id: whichPacmanProcess - command: ["sh", "-c", "command -v pacman"] - running: false - onExited: function (exitCode) { - if (exitCode === 0) { - Logger.d("VersionSubTab", "whichPacmanProcess - pacman found, starting query"); - pacmanProcess.running = true; - gitFallbackTimer.start(); - } else { - Logger.d("VersionSubTab", "whichPacmanProcess - pacman not found, falling back to git"); - fetchGitCommit(); - } - } - } - - Process { - id: pacmanProcess - command: ["pacman", "-Q", "noctalia-shell-git"] - running: false - - onStarted: { - gitFallbackTimer.stop(); - } - - onExited: function (exitCode) { - gitFallbackTimer.stop(); - Logger.d("VersionSubTab", "pacmanProcess - Process exited with code:", exitCode); - if (exitCode === 0) { - var output = stdout.text.trim(); - Logger.d("VersionSubTab", "pacmanProcess - Output:", output); - var match = output.match(/noctalia-shell-git\s+(.+)/); - if (match && match[1]) { - // For Arch packages, the version format might be like: 3.4.0.r112.g3f00bec8-1 - // Extract just the commit hash part if it exists - var version = match[1]; - var commitMatch = version.match(/\.g([0-9a-f]{7,})/i); - if (commitMatch && commitMatch[1]) { - // Show short hash (first 7 characters) - root.commitInfo = commitMatch[1].substring(0, 7); - Logger.d("VersionSubTab", "pacmanProcess - Set commitInfo from Arch package:", root.commitInfo); - return; // Successfully got commit hash from Arch package - } else { - // If no commit hash in version format, still try git repo - Logger.d("VersionSubTab", "pacmanProcess - No commit hash in version, trying git"); - fetchGitCommit(); - } - } else { - // Unexpected output format, try git - Logger.d("VersionSubTab", "pacmanProcess - Unexpected output format, trying git"); - fetchGitCommit(); - } - } else { - // If not on Arch, try to get git commit from repository - Logger.d("VersionSubTab", "pacmanProcess - Package not found, trying git"); - fetchGitCommit(); - } - } - - stdout: StdioCollector {} - stderr: StdioCollector {} - } - function fetchGitCommit() { var shellDir = Quickshell.shellDir || ""; Logger.d("VersionSubTab", "fetchGitCommit - shellDir:", shellDir); @@ -303,6 +250,28 @@ ColumnLayout { stderr: StdioCollector {} } + Process { + id: qsVersionProcess + command: ["qs", "--version"] + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + var output = stdout.text.trim(); + // Format: "noctalia-qs 0.3.0, revision abc12345, distributed by: ..." + // Only set if this is actually noctalia-qs; leave empty for upstream quickshell + var match = output.match(/noctalia-qs\s+(\S+),\s+revision\s*([0-9a-f]*)/i); + if (match) { + root.qsVersion = match[1]; + root.qsRevision = match[2]; + } + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + // Check if fastfetch is available before attempting to run it Process { id: checkFastfetchProcess @@ -405,8 +374,7 @@ ColumnLayout { ColumnLayout { NHeader { - label: I18n.tr("panels.about.noctalia-title") - // description: I18n.tr("panels.about.noctalia-desc") + label: "Noctalia Shell" } // Versions @@ -415,20 +383,9 @@ ColumnLayout { rowSpacing: Style.marginXS columnSpacing: Style.marginM + // Installed Version (Shell) NText { - text: I18n.tr("panels.about.noctalia-latest-version") - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - } - - NText { - text: root.latestVersion - color: Color.mOnSurface - font.weight: Style.fontWeightBold - } - - NText { - text: I18n.tr("panels.about.noctalia-installed-version") + text: "Noctalia Shell:" color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignRight | Qt.AlignVCenter } @@ -442,6 +399,34 @@ ColumnLayout { font.weight: Style.fontWeightBold } + // Git commit in parentheses + NText { + id: commitText + visible: root.isGitVersion + text: "(" + (root.commitInfo || I18n.tr("common.loading")) + ")" + color: commitMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant + pointSize: Style.fontSizeXS + font.underline: commitMouseArea.containsMouse && root.commitInfo + + MouseArea { + id: commitMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: root.commitInfo ? Qt.PointingHandCursor : Qt.ArrowCursor + onEntered: { + if (root.commitInfo) { + TooltipService.show(commitText, I18n.tr("panels.about.view-commit")); + } + } + onExited: TooltipService.hide() + onClicked: { + if (root.commitInfo) { + Quickshell.execDetached(["xdg-open", "https://github.com/noctalia-dev/noctalia-shell/commit/" + root.commitInfo]); + } + } + } + } + // Update status indicator NIcon { id: upToDateIcon @@ -474,39 +459,113 @@ ColumnLayout { } } + // Latest Version (Shell) NText { - visible: root.isGitVersion - text: I18n.tr("panels.about.noctalia-git-commit") + visible: root.updateAvailable + text: I18n.tr("panels.about.noctalia-available") color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignRight | Qt.AlignVCenter } - // Clickable git commit NText { - id: commitText - visible: root.isGitVersion - text: root.commitInfo || I18n.tr("common.loading") - color: root.commitInfo ? Color.mPrimary : Color.mOnSurface - pointSize: Style.fontSizeXS - font.underline: commitMouseArea.containsMouse && root.commitInfo + visible: root.updateAvailable + text: root.latestVersion + color: Color.mOnSurface + font.weight: Style.fontWeightBold + } - MouseArea { - id: commitMouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: root.commitInfo ? Qt.PointingHandCursor : Qt.ArrowCursor - onEntered: { - if (root.commitInfo) { - TooltipService.show(commitText, I18n.tr("panels.about.view-commit")); - } - } - onExited: TooltipService.hide() - onClicked: { - if (root.commitInfo) { - Quickshell.execDetached(["xdg-open", "https://github.com/noctalia-dev/noctalia-shell/commit/" + root.commitInfo]); + // Divider-like spacing + Item { + visible: root.qsUpdateAvailable || root.updateAvailable + Layout.columnSpan: 2 + Layout.preferredHeight: Style.marginXS + } + + // Quickshell Version + NText { + visible: root.qsVersion !== "" + text: "Noctalia QS:" + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + } + + RowLayout { + visible: root.qsVersion !== "" + spacing: Style.marginS + + NText { + text: root.qsVersion.startsWith("v") ? root.qsVersion : "v" + root.qsVersion + color: Color.mOnSurface + font.weight: Style.fontWeightBold + } + + // Git revision in parentheses + NText { + id: qsRevisionText + visible: root.qsRevision !== "" + text: "(" + root.qsRevision + ")" + color: qsRevisionMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant + pointSize: Style.fontSizeXS + font.underline: qsRevisionMouseArea.containsMouse + + MouseArea { + id: qsRevisionMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(qsRevisionText, I18n.tr("panels.about.view-commit")) + onExited: TooltipService.hide() + onClicked: { + Quickshell.execDetached(["xdg-open", "https://github.com/noctalia-dev/noctalia-qs/commit/" + root.qsRevision]); } } } + + // Update status indicator + NIcon { + id: qsUpToDateIcon + visible: root.qsIsUpToDate + icon: "circle-check" + pointSize: Style.fontSizeM + color: Color.mPrimary + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(qsUpToDateIcon, I18n.tr("panels.about.up-to-date")) + onExited: TooltipService.hide() + } + } + + NIcon { + id: qsUpdateAvailableIcon + visible: root.qsUpdateAvailable + icon: "arrow-up-circle" + pointSize: Style.fontSizeS + color: Color.mPrimary + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: TooltipService.show(qsUpdateAvailableIcon, I18n.tr("panels.about.update-available")) + onExited: TooltipService.hide() + } + } + } + + // Latest Quickshell Version + NText { + visible: root.qsUpdateAvailable + text: I18n.tr("panels.about.noctalia-available") + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + } + + NText { + visible: root.qsUpdateAvailable + text: GitHubService.latestQSVersion + color: Color.mOnSurface + font.weight: Style.fontWeightBold } } } @@ -557,6 +616,14 @@ ColumnLayout { } } + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.about.changelog-on-startup") + description: I18n.tr("panels.about.changelog-on-startup-desc") + checked: Settings.data.general.showChangelogOnStartup + onToggled: checked => Settings.data.general.showChangelogOnStartup = checked + } + // System Information Section NDivider { Layout.fillWidth: true @@ -663,6 +730,23 @@ ColumnLayout { wrapMode: Text.Wrap } + // Board name + NText { + text: I18n.tr("panels.about.system-board") + color: Color.mOnSurfaceVariant + pointSize: sysInfo.textSize + } + NText { + text: { + const title = root.getModule("Board"); + return title?.result?.name || "N/A"; + } + color: Color.mOnSurface + pointSize: sysInfo.textSize + Layout.fillWidth: true + wrapMode: Text.Wrap + } + // Uptime NText { text: I18n.tr("panels.about.system-uptime") @@ -734,9 +818,9 @@ ColumnLayout { const mem = root.getModule("Memory"); if (!mem?.result) return "N/A"; - const used = SystemStatService.formatGigabytes(mem.result.used / root.giga); - const total = SystemStatService.formatGigabytes(mem.result.total / root.giga); - return used + " / " + total; + const used = (mem.result.used / root.gigaB).toFixed(1); + const total = (mem.result.total / root.gigaB).toFixed(1); + return used + " GiB / " + total + " GiB"; } color: Color.mOnSurface pointSize: sysInfo.textSize @@ -758,9 +842,9 @@ ColumnLayout { const rootDisk = disk.result.find(d => d.mountpoint === "/"); if (!rootDisk?.bytes) return "N/A"; - const used = SystemStatService.formatGigabytes(rootDisk.bytes.used / root.giga); - const total = SystemStatService.formatGigabytes(rootDisk.bytes.total / root.giga); - return used + " / " + total + " (" + rootDisk.filesystem + ")"; + const used = (rootDisk.bytes.used / root.gigaD).toFixed(1); + const total = (rootDisk.bytes.total / root.gigaD).toFixed(1); + return used + " GB / " + total + " GB" + " (" + rootDisk.filesystem + ")"; } color: Color.mOnSurface pointSize: sysInfo.textSize diff --git a/Modules/Panels/Settings/Tabs/Audio/VisualizerSubTab.qml b/Modules/Panels/Settings/Tabs/Audio/VisualizerSubTab.qml index ab50c8549..fda4fa58e 100644 --- a/Modules/Panels/Settings/Tabs/Audio/VisualizerSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Audio/VisualizerSubTab.qml @@ -82,8 +82,8 @@ ColumnLayout { }) } ] - currentKey: Settings.data.audio.cavaFrameRate - defaultValue: Settings.getDefaultValue("audio.cavaFrameRate") - onSelected: key => Settings.data.audio.cavaFrameRate = key + currentKey: Settings.data.audio.spectrumFrameRate + defaultValue: Settings.getDefaultValue("audio.spectrumFrameRate") + onSelected: key => Settings.data.audio.spectrumFrameRate = key } } diff --git a/Modules/Panels/Settings/Tabs/Bar/AppearanceSubTab.qml b/Modules/Panels/Settings/Tabs/Bar/AppearanceSubTab.qml index 0482dea3c..1586f6935 100644 --- a/Modules/Panels/Settings/Tabs/Bar/AppearanceSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Bar/AppearanceSubTab.qml @@ -226,6 +226,15 @@ ColumnLayout { text: Math.floor(Settings.data.bar.capsuleOpacity * 100) + "%" } + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-enable-exclusion-zone-inset-label") + description: I18n.tr("panels.bar.appearance-enable-exclusion-zone-inset-description") + checked: Settings.data.bar.enableExclusionZoneInset + defaultValue: Settings.getDefaultValue("bar.enableExclusionZoneInset") + onToggled: checked => Settings.data.bar.enableExclusionZoneInset = checked + } + NToggle { Layout.fillWidth: true visible: CompositorService.isNiri @@ -290,43 +299,33 @@ ColumnLayout { ColumnLayout { visible: Settings.data.bar.barType === "floating" - spacing: Style.marginS + spacing: Style.marginL Layout.fillWidth: true - NLabel { - label: I18n.tr("panels.bar.appearance-margins-label") - description: I18n.tr("panels.bar.appearance-margins-description") + NDivider { + Layout.fillWidth: true } - RowLayout { - Layout.fillWidth: true - spacing: Style.marginL + NSpinBox { + label: I18n.tr("panels.bar.appearance-margins-vertical") + description: I18n.tr("panels.bar.appearance-margins-description") + from: 0 + to: 500 + suffix: "px" + value: Settings.data.bar.marginVertical + defaultValue: Settings.getDefaultValue("bar.marginVertical") + onValueChanged: Settings.data.bar.marginVertical = value + } - NValueSlider { - Layout.fillWidth: true - label: I18n.tr("panels.bar.appearance-margins-vertical") - from: 0 - to: 18 - stepSize: 1 - showReset: true - value: Settings.data.bar.marginVertical - defaultValue: Settings.getDefaultValue("bar.marginVertical") - onMoved: value => Settings.data.bar.marginVertical = value - text: Settings.data.bar.marginVertical + "px" - } - - NValueSlider { - Layout.fillWidth: true - label: I18n.tr("panels.bar.appearance-margins-horizontal") - from: 0 - to: 18 - stepSize: 1 - showReset: true - value: Settings.data.bar.marginHorizontal - defaultValue: Settings.getDefaultValue("bar.marginHorizontal") - onMoved: value => Settings.data.bar.marginHorizontal = value - text: Settings.data.bar.marginHorizontal + "px" - } + NSpinBox { + label: I18n.tr("panels.bar.appearance-margins-horizontal") + description: I18n.tr("panels.bar.appearance-margins-description") + from: 0 + to: 500 + suffix: "px" + value: Settings.data.bar.marginHorizontal + defaultValue: Settings.getDefaultValue("bar.marginHorizontal") + onValueChanged: Settings.data.bar.marginHorizontal = value } } diff --git a/Modules/Panels/Settings/Tabs/Bar/BarTab.qml b/Modules/Panels/Settings/Tabs/Bar/BarTab.qml index 1044f27aa..3cdf719ca 100644 --- a/Modules/Panels/Settings/Tabs/Bar/BarTab.qml +++ b/Modules/Panels/Settings/Tabs/Bar/BarTab.qml @@ -154,7 +154,7 @@ ColumnLayout { } Component.onCompleted: { - updateAvailableWidgetsModel(); + Qt.callLater(updateAvailableWidgetsModel); } Connections { @@ -189,10 +189,15 @@ ColumnLayout { checked: subTabBar.currentIndex === 1 } NTabButton { - text: I18n.tr("common.monitors") + text: I18n.tr("common.behavior") tabIndex: 2 checked: subTabBar.currentIndex === 2 } + NTabButton { + text: I18n.tr("common.monitors") + tabIndex: 3 + checked: subTabBar.currentIndex === 3 + } } Item { @@ -214,6 +219,7 @@ ColumnLayout { moveWidgetBetweenSections: root._moveWidgetBetweenSections onOpenPluginSettings: manifest => pluginSettingsDialog.openPluginSettings(manifest) } + BehaviorSubTab {} MonitorsSubTab { addMonitor: root.addMonitor removeMonitor: root.removeMonitor diff --git a/Modules/Panels/Settings/Tabs/Bar/BehaviorSubTab.qml b/Modules/Panels/Settings/Tabs/Bar/BehaviorSubTab.qml new file mode 100644 index 000000000..7ff6ee77f --- /dev/null +++ b/Modules/Panels/Settings/Tabs/Bar/BehaviorSubTab.qml @@ -0,0 +1,168 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services.Compositor +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + readonly property string effectiveWheelAction: Settings.data.bar.mouseWheelAction || "none" + readonly property string effectiveMiddleClickAction: Settings.data.bar.middleClickAction || "none" + readonly property string effectiveRightClickAction: Settings.data.bar.rightClickAction || "controlCenter" + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-workspace-scroll-label") + description: I18n.tr("panels.bar.behavior-workspace-scroll-description") + model: { + var items = [ + { + "key": "none", + "name": I18n.tr("common.none") + }, + { + "key": "workspace", + "name": I18n.tr("panels.bar.behavior-workspace-scroll-option-workspace") + } + ]; + if (CompositorService.isNiri) { + items.push({ + "key": "content", + "name": I18n.tr("panels.bar.behavior-workspace-scroll-option-content") + }); + } + return items; + } + currentKey: root.effectiveWheelAction + defaultValue: Settings.getDefaultValue("bar.mouseWheelAction") + onSelected: key => Settings.data.bar.mouseWheelAction = key + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.general.reverse-scrolling-label") + description: I18n.tr("panels.general.reverse-scrolling-description") + checked: Settings.data.bar.reverseScroll + defaultValue: Settings.getDefaultValue("bar.reverseScroll") + onToggled: checked => Settings.data.bar.reverseScroll = checked + visible: Settings.data.bar.mouseWheelAction !== "none" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-wheel-wrap-label") + description: I18n.tr("panels.bar.behavior-wheel-wrap-description") + checked: Settings.data.bar.mouseWheelWrap + defaultValue: Settings.getDefaultValue("bar.mouseWheelWrap") + onToggled: checked => Settings.data.bar.mouseWheelWrap = checked + visible: Settings.data.bar.mouseWheelAction === "workspace" + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-middle-click-label") + description: I18n.tr("panels.bar.behavior-middle-click-description") + model: [ + { + "key": "none", + "name": I18n.tr("common.none") + }, + { + "key": "controlCenter", + "name": I18n.tr("tooltips.open-control-center") + }, + { + "key": "settings", + "name": I18n.tr("tooltips.open-settings") + }, + { + "key": "launcherPanel", + "name": I18n.tr("actions.open-launcher") + }, + { + "key": "command", + "name": I18n.tr("actions.run-custom-command") + } + ] + currentKey: root.effectiveMiddleClickAction + defaultValue: Settings.getDefaultValue("bar.middleClickAction") + onSelected: key => Settings.data.bar.middleClickAction = key + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-middle-click-command-label") + description: I18n.tr("panels.bar.behavior-middle-click-command-description") + placeholderText: I18n.tr("panels.bar.behavior-middle-click-command-placeholder") + text: Settings.data.bar.middleClickCommand + fontFamily: Settings.data.ui.fontFixed + onTextChanged: Settings.data.bar.middleClickCommand = text + visible: Settings.data.bar.middleClickAction === "command" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-middle-click-follow-mouse-label") + description: I18n.tr("panels.bar.behavior-middle-click-follow-mouse-description") + checked: Settings.data.bar.middleClickFollowMouse + defaultValue: Settings.getDefaultValue("bar.middleClickFollowMouse") + onToggled: checked => Settings.data.bar.middleClickFollowMouse = checked + visible: Settings.data.bar.middleClickAction !== "none" && Settings.data.bar.middleClickAction !== "command" && !(Settings.data.bar.middleClickAction === "settings" && Settings.data.ui.settingsPanelMode === "window") + } + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-right-click-label") + description: I18n.tr("panels.bar.behavior-right-click-description") + model: [ + { + "key": "none", + "name": I18n.tr("common.none") + }, + { + "key": "controlCenter", + "name": I18n.tr("tooltips.open-control-center") + }, + { + "key": "settings", + "name": I18n.tr("tooltips.open-settings") + }, + { + "key": "launcherPanel", + "name": I18n.tr("actions.open-launcher") + }, + { + "key": "command", + "name": I18n.tr("actions.run-custom-command") + } + ] + currentKey: root.effectiveRightClickAction + defaultValue: Settings.getDefaultValue("bar.rightClickAction") + onSelected: key => Settings.data.bar.rightClickAction = key + } + + NTextInput { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-right-click-command-label") + description: I18n.tr("panels.bar.behavior-right-click-command-description") + placeholderText: I18n.tr("panels.bar.behavior-right-click-command-placeholder") + text: Settings.data.bar.rightClickCommand + fontFamily: Settings.data.ui.fontFixed + onTextChanged: Settings.data.bar.rightClickCommand = text + visible: Settings.data.bar.rightClickAction === "command" + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.behavior-right-click-follow-mouse-label") + description: I18n.tr("panels.bar.behavior-right-click-follow-mouse-description") + checked: Settings.data.bar.rightClickFollowMouse + defaultValue: Settings.getDefaultValue("bar.rightClickFollowMouse") + onToggled: checked => Settings.data.bar.rightClickFollowMouse = checked + visible: Settings.data.bar.rightClickAction !== "none" && Settings.data.bar.rightClickAction !== "command" && !(Settings.data.bar.rightClickAction === "settings" && Settings.data.ui.settingsPanelMode === "window") + } +} diff --git a/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml b/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml index 1ec6c73b6..c6ad3806c 100644 --- a/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml @@ -33,6 +33,10 @@ ColumnLayout { required property var modelData readonly property string screenName: modelData.name || "Unknown" + readonly property real compositorScale: { + const info = CompositorService.displayScales[screenName]; + return (info && info.scale) ? info.scale : 1.0; + } readonly property bool barEnabled: (Settings.data.bar.monitors || []).indexOf(screenName) !== -1 readonly property bool hasOverride: Settings.hasScreenOverride(screenName) @@ -67,12 +71,11 @@ ColumnLayout { NText { text: { - const compositorScale = CompositorService.getDisplayScale(monitorCard.screenName); return I18n.tr("system.monitor-description", { "model": monitorCard.modelData.model || I18n.tr("common.unknown"), - "width": Math.round(monitorCard.modelData.width * compositorScale), - "height": Math.round(monitorCard.modelData.height * compositorScale), - "scale": compositorScale + "width": Math.round(monitorCard.modelData.width * monitorCard.compositorScale), + "height": Math.round(monitorCard.modelData.height * monitorCard.compositorScale), + "scale": monitorCard.compositorScale }); } pointSize: Style.fontSizeS diff --git a/Modules/Panels/Settings/Tabs/ColorScheme/TemplatesSubTab.qml b/Modules/Panels/Settings/Tabs/ColorScheme/TemplatesSubTab.qml index ae81cffdd..f69329867 100644 --- a/Modules/Panels/Settings/Tabs/ColorScheme/TemplatesSubTab.qml +++ b/Modules/Panels/Settings/Tabs/ColorScheme/TemplatesSubTab.qml @@ -47,6 +47,18 @@ ColumnLayout { paths.push(app.outputs[k].path); } path = paths.join("\n"); + } else if (app.id === "emacs") { + // Emacs clients are detected dynamically by ProgramCheckerService + var emacsClients = ProgramCheckerService.availableEmacsClients; + if (emacsClients && emacsClients.length > 0) { + var emacsPaths = []; + for (var k = 0; k < emacsClients.length; k++) { + emacsPaths.push(emacsClients[k].path); + } + path = emacsPaths.join("\n"); + } else { + path = I18n.tr("panels.color-scheme.templates-none-detected"); + } } else if (app.clients && app.clients.length > 0) { var validClients = []; for (var k = 0; k < app.clients.length; k++) { @@ -267,7 +279,7 @@ ColumnLayout { onClicked: root.toggleTemplate(chip.modelData.id) onEntered: { if (chip.modelData.tooltip) { - TooltipService.show(chip, chip.modelData.tooltip, "auto"); + TooltipService.show(chip, chip.modelData.tooltip, "bottom"); } } onExited: { diff --git a/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml b/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml index 4cf2cf75c..7db8d6319 100644 --- a/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Connections/BluetoothSubTab.qml @@ -198,6 +198,7 @@ Item { Layout.fillWidth: true Layout.preferredHeight: connectedDevicesCol.implicitHeight + Style.margin2M border.color: showOnlyLists ? Style.boxBorderColor : "transparent" + color: showOnlyLists ? Color.mSurfaceVariant : "transparent" ColumnLayout { id: connectedDevicesCol @@ -228,6 +229,7 @@ Item { Layout.fillWidth: true Layout.preferredHeight: pairedDevicesCol.implicitHeight + Style.margin2M border.color: showOnlyLists ? Style.boxBorderColor : "transparent" + color: showOnlyLists ? Color.mSurfaceVariant : "transparent" ColumnLayout { id: pairedDevicesCol @@ -258,6 +260,7 @@ Item { Layout.fillWidth: true Layout.preferredHeight: availableDevicesCol.implicitHeight + Style.margin2M border.color: "transparent" + color: showOnlyLists ? Color.mSurfaceVariant : "transparent" ColumnLayout { id: availableDevicesCol @@ -313,6 +316,13 @@ Item { anchors.margins: Style.marginXL spacing: Style.marginM + NToggle { + label: I18n.tr("panels.connections.bluetooth-auto-connect-label") + description: I18n.tr("panels.connections.bluetooth-auto-connect-description") + checked: Settings.data.network.bluetoothAutoConnect + onToggled: checked => Settings.data.network.bluetoothAutoConnect = checked + } + NToggle { label: I18n.tr("panels.connections.hide-unnamed-devices-label") description: I18n.tr("panels.connections.hide-unnamed-devices-description") @@ -379,7 +389,7 @@ Item { radius: Style.radiusM clip: true - color: (modelData.connected && modelData.state !== BluetoothDeviceState.Disconnecting) ? Qt.alpha(Color.mPrimary, 0.15) : Color.mSurface + color: (modelData.connected && modelData.state !== BluetoothDeviceState.Disconnecting) ? Qt.alpha(Color.mPrimary, Math.min(1.15 - Color.panelBackgroundOpacity, 0.75)) : Color.mSurface ColumnLayout { id: deviceColumn @@ -546,10 +556,13 @@ Item { columnSpacing: Style.marginM rowSpacing: Style.marginXS + // --- Item 1: Signal Strength --- RowLayout { Layout.fillWidth: true Layout.preferredWidth: 1 spacing: Style.marginXS + Layout.row: detailsGrid ? 0 : 0 + Layout.column: 0 NIcon { icon: BluetoothService.getSignalIcon(modelData) pointSize: Style.fontSizeXS @@ -562,9 +575,13 @@ Item { Layout.fillWidth: true } } + + // --- Item 2: Battery --- RowLayout { Layout.fillWidth: true Layout.preferredWidth: 1 + Layout.row: detailsGrid ? 0 : 1 + Layout.column: detailsGrid ? 1 : 0 spacing: Style.marginXS NIcon { icon: { @@ -584,8 +601,12 @@ Item { Layout.fillWidth: true } } + // --- Item 3: Pair state --- RowLayout { Layout.fillWidth: true + Layout.preferredWidth: 1 + Layout.row: detailsGrid ? 1 : 2 + Layout.column: 0 spacing: Style.marginXS NIcon { icon: "link" @@ -599,8 +620,12 @@ Item { Layout.fillWidth: true } } + // --- Item 4: Trust state --- RowLayout { Layout.fillWidth: true + Layout.preferredWidth: 1 + Layout.row: detailsGrid ? 1 : 3 + Layout.column: detailsGrid ? 1 : 0 spacing: Style.marginXS NIcon { icon: "shield-check" @@ -614,9 +639,12 @@ Item { Layout.fillWidth: true } } + // --- Item 5: Address --- RowLayout { Layout.fillWidth: true - Layout.columnSpan: infoColumn.columns === 2 ? 2 : 1 + Layout.preferredWidth: 1 + Layout.row: detailsGrid ? 2 : 4 + Layout.column: 0 spacing: Style.marginXS NIcon { icon: "hash" @@ -630,6 +658,39 @@ Item { Layout.fillWidth: true } } + // --- Item 6: Auto-connect --- + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 1 + Layout.row: detailsGrid ? 2 : 5 + Layout.column: detailsGrid ? 1 : 0 + spacing: Style.marginXS + visible: Settings.data.network.bluetoothAutoConnect + + NIcon { + icon: BluetoothService.getDeviceAutoConnect(modelData.address) ? "repeat" : "repeat-off" + pointSize: Style.fontSizeXS + color: BluetoothService.getDeviceAutoConnect(modelData.address) ? Color.mPrimary : Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + } + + NText { + text: I18n.tr("common.auto-connect") + pointSize: Style.fontSizeXS + color: BluetoothService.getDeviceAutoConnect(modelData.address) ? Color.mOnSurface : Color.mOnSurfaceVariant + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: TooltipService.show(parent, BluetoothService.getDeviceAutoConnect(modelData.address) ? I18n.tr("tooltips.bluetooth-auto-connect-on") : I18n.tr("tooltips.bluetooth-auto-connect-off")) + onExited: TooltipService.hide() + onClicked: BluetoothService.setDeviceAutoConnect(modelData, !BluetoothService.getDeviceAutoConnect(modelData.address)) + } + } + } } } } diff --git a/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml b/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml index fae2072f6..ae1fa10e7 100644 --- a/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml +++ b/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml @@ -80,11 +80,14 @@ ColumnLayout { NSectionEditor { required property var modelData + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } Layout.fillWidth: true sectionName: modelData.name sectionSubtitle: { - var compositorScale = CompositorService.getDisplayScale(modelData.name); // Format scale to 2 decimal places to prevent overly long text var formattedScale = compositorScale.toFixed(2); return "(" + modelData.width + "x" + modelData.height + " @ " + formattedScale + "x)"; @@ -266,6 +269,9 @@ ColumnLayout { } else if (widgetId === "MediaPlayer") { newWidget.x = 100; newWidget.y = 200; + } else if (widgetId === "AudioVisualizer") { + newWidget.x = 120; + newWidget.y = 280; } else if (widgetId === "Weather") { newWidget.x = 100; newWidget.y = 300; diff --git a/Modules/Panels/Settings/Tabs/Display/BrightnessSubTab.qml b/Modules/Panels/Settings/Tabs/Display/BrightnessSubTab.qml index 17d3b5fa4..63e6725c3 100644 --- a/Modules/Panels/Settings/Tabs/Display/BrightnessSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Display/BrightnessSubTab.qml @@ -107,8 +107,11 @@ ColumnLayout { NText { Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } text: { - const compositorScale = CompositorService.getDisplayScale(modelData.name); I18n.tr("system.monitor-description", { "model": modelData.model, "width": modelData.width * compositorScale, diff --git a/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml b/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml index 472e9fed7..dd4e573d3 100644 --- a/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml @@ -106,12 +106,46 @@ ColumnLayout { NToggle { Layout.fillWidth: true - visible: Settings.data.dock.dockType === "static" && Settings.data.bar.barType === "framed" - label: I18n.tr("panels.dock.appearance-frame-indicator-label") - description: I18n.tr("panels.dock.appearance-frame-indicator-description") - checked: Settings.data.dock.showFrameIndicator - defaultValue: Settings.getDefaultValue("dock.showFrameIndicator") - onToggled: checked => Settings.data.dock.showFrameIndicator = checked + label: I18n.tr("panels.dock.appearance-dock-indicator-label") + description: I18n.tr("panels.dock.appearance-dock-indicator-description") + checked: Settings.data.dock.showDockIndicator + defaultValue: Settings.getDefaultValue("dock.showDockIndicator") + onToggled: checked => Settings.data.dock.showDockIndicator = checked + } + + NToggle { + Layout.fillWidth: true + visible: Settings.data.dock.showDockIndicator + label: I18n.tr("panels.dock.appearance-indicator-thickness-label") + description: I18n.tr("panels.dock.appearance-indicator-thickness-description") + checked: (Settings.data.dock.indicatorThickness || 3) >= 6 + defaultValue: (Settings.getDefaultValue("dock.indicatorThickness") || 3) >= 6 + onToggled: checked => Settings.data.dock.indicatorThickness = checked ? 6 : 3 + } + + NColorChoice { + Layout.fillWidth: true + visible: Settings.data.dock.showDockIndicator + label: I18n.tr("panels.dock.appearance-indicator-color-label") + description: I18n.tr("panels.dock.appearance-indicator-color-description") + currentKey: Settings.data.dock.indicatorColor || "primary" + defaultValue: Settings.getDefaultValue("dock.indicatorColor") + onSelected: key => Settings.data.dock.indicatorColor = key + } + + NValueSlider { + Layout.fillWidth: true + visible: Settings.data.dock.showDockIndicator + label: I18n.tr("panels.dock.appearance-indicator-opacity-label") + description: I18n.tr("panels.dock.appearance-indicator-opacity-description") + from: 0.1 + to: 1 + stepSize: 0.01 + showReset: true + value: Settings.data.dock.indicatorOpacity + defaultValue: Settings.getDefaultValue("dock.indicatorOpacity") + onMoved: value => Settings.data.dock.indicatorOpacity = value + text: Math.floor(Settings.data.dock.indicatorOpacity * 100) + "%" } NValueSlider { diff --git a/Modules/Panels/Settings/Tabs/Dock/MonitorsSubTab.qml b/Modules/Panels/Settings/Tabs/Dock/MonitorsSubTab.qml index 96be22ada..a0e08d947 100644 --- a/Modules/Panels/Settings/Tabs/Dock/MonitorsSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Dock/MonitorsSubTab.qml @@ -35,9 +35,12 @@ ColumnLayout { model: Quickshell.screens || [] delegate: NCheckbox { Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } label: modelData.name || "Unknown" description: { - const compositorScale = CompositorService.getDisplayScale(modelData.name); I18n.tr("system.monitor-description", { "model": modelData.model, "width": modelData.width * compositorScale, diff --git a/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml b/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml index 20ff51e79..c26bb5577 100644 --- a/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Idle/BehaviorSubTab.qml @@ -53,7 +53,34 @@ ColumnLayout { Layout.fillWidth: true } - // Timeout spinboxes (disabled when idle is off) + IdleCommandEditPopup { + id: editPopup + parent: Overlay.overlay + } + + function openEdit(actionName, cmdVal, resumeCmdVal, onSaveCmd, onSaveResume) { + editPopup.editIndex = -1; + editPopup.showCommand = true; + editPopup.showTimeout = false; + editPopup.titleText = I18n.tr("common.edit") + " " + actionName; + editPopup.timeoutValue = 0; + editPopup.commandValue = cmdVal; + editPopup.resumeCommandValue = resumeCmdVal; + + try { + editPopup.saved.disconnect(editPopup._savedSlot); + } catch (e) {} + + editPopup._savedSlot = function (timeout, cmd, resumeCmd, name) { + onSaveCmd(cmd); + onSaveResume(resumeCmd); + }; + + editPopup.saved.connect(editPopup._savedSlot); + editPopup.open(); + } + + // Timeout spinboxes and resume commands ColumnLayout { Layout.fillWidth: true spacing: Style.marginL @@ -64,37 +91,58 @@ ColumnLayout { description: I18n.tr("panels.idle.timeouts-description") } - NSpinBox { - label: I18n.tr("panels.idle.screen-off-label") - description: I18n.tr("panels.idle.screen-off-description") - from: 0 - to: 86400 - suffix: "s" - value: Settings.data.idle.screenOffTimeout - defaultValue: 0 - onValueChanged: Settings.data.idle.screenOffTimeout = value + DefaultActionRow { + actionName: I18n.tr("panels.idle.screen-off-label") + actionDescription: I18n.tr("panels.idle.screen-off-description") + timeoutValue: Settings.data.idle.screenOffTimeout + defaultValue: Settings.getDefaultValue("idle.screenOffTimeout") + command: Settings.data.idle.screenOffCommand + resumeCommand: Settings.data.idle.resumeScreenOffCommand + onActionTimeoutChanged: val => Settings.data.idle.screenOffTimeout = val + onActionCommandChanged: cmd => { + Settings.data.idle.screenOffCommand = cmd; + Settings.saveImmediate(); + } + onActionResumeCommandChanged: cmd => { + Settings.data.idle.resumeScreenOffCommand = cmd; + Settings.saveImmediate(); + } } - NSpinBox { - label: I18n.tr("panels.idle.lock-label") - description: I18n.tr("panels.idle.lock-description") - from: 0 - to: 86400 - suffix: "s" - value: Settings.data.idle.lockTimeout - defaultValue: 0 - onValueChanged: Settings.data.idle.lockTimeout = value + DefaultActionRow { + actionName: I18n.tr("panels.idle.lock-label") + actionDescription: I18n.tr("panels.idle.lock-description") + timeoutValue: Settings.data.idle.lockTimeout + defaultValue: Settings.getDefaultValue("idle.lockTimeout") + command: Settings.data.idle.lockCommand + resumeCommand: Settings.data.idle.resumeLockCommand + onActionTimeoutChanged: val => Settings.data.idle.lockTimeout = val + onActionCommandChanged: cmd => { + Settings.data.idle.lockCommand = cmd; + Settings.saveImmediate(); + } + onActionResumeCommandChanged: cmd => { + Settings.data.idle.resumeLockCommand = cmd; + Settings.saveImmediate(); + } } - NSpinBox { - label: I18n.tr("common.suspend") - description: I18n.tr("panels.idle.suspend-description") - from: 0 - to: 86400 - suffix: "s" - value: Settings.data.idle.suspendTimeout - defaultValue: 0 - onValueChanged: Settings.data.idle.suspendTimeout = value + DefaultActionRow { + actionName: I18n.tr("common.suspend") + actionDescription: I18n.tr("panels.idle.suspend-description") + timeoutValue: Settings.data.idle.suspendTimeout + defaultValue: Settings.getDefaultValue("idle.suspendTimeout") + command: Settings.data.idle.suspendCommand + resumeCommand: Settings.data.idle.resumeSuspendCommand + onActionTimeoutChanged: val => Settings.data.idle.suspendTimeout = val + onActionCommandChanged: cmd => { + Settings.data.idle.suspendCommand = cmd; + Settings.saveImmediate(); + } + onActionResumeCommandChanged: cmd => { + Settings.data.idle.resumeSuspendCommand = cmd; + Settings.saveImmediate(); + } } NDivider { @@ -112,4 +160,40 @@ ColumnLayout { onValueChanged: Settings.data.idle.fadeDuration = value } } + + component DefaultActionRow: RowLayout { + id: rowRoot + Layout.fillWidth: true + spacing: Style.marginM + + property string actionName + property string actionDescription + property alias timeoutValue: spinBox.value + property int defaultValue + property string command + property string resumeCommand + + signal actionTimeoutChanged(int newValue) + signal actionCommandChanged(string newCmd) + signal actionResumeCommandChanged(string newCmd) + + NSpinBox { + id: spinBox + Layout.fillWidth: true + label: rowRoot.actionName + description: rowRoot.actionDescription + from: 0 + to: 86400 + suffix: "s" + defaultValue: rowRoot.defaultValue + onValueChanged: rowRoot.actionTimeoutChanged(value) + } + + NIconButton { + Layout.alignment: Qt.AlignVCenter + icon: "settings" + tooltipText: I18n.tr("common.edit") + onClicked: root.openEdit(rowRoot.actionName, rowRoot.command, rowRoot.resumeCommand, rowRoot.actionCommandChanged, rowRoot.actionResumeCommandChanged) + } + } } diff --git a/Modules/Panels/Settings/Tabs/Idle/CustomSubTab.qml b/Modules/Panels/Settings/Tabs/Idle/CustomSubTab.qml index 02ea04047..211519fb6 100644 --- a/Modules/Panels/Settings/Tabs/Idle/CustomSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Idle/CustomSubTab.qml @@ -29,8 +29,10 @@ ColumnLayout { } for (var i = 0; i < entries.length; i++) { entriesModel.append({ + "name": String(entries[i].name || ""), "timeout": parseInt(entries[i].timeout) || 60, - "command": String(entries[i].command || "") + "command": String(entries[i].command || ""), + "resumeCommand": String(entries[i].resumeCommand || "") }); } } @@ -41,15 +43,22 @@ ColumnLayout { for (var i = 0; i < entriesModel.count; i++) { var item = entriesModel.get(i); arr.push({ + "name": item.name, "timeout": item.timeout, - "command": item.command + "command": item.command, + "resumeCommand": item.resumeCommand }); } Settings.data.idle.customCommands = JSON.stringify(arr); _saving = false; } - Component.onCompleted: _loadToModel() + function _removeEntry(index) { + entriesModel.remove(index, 1); + _saveFromModel(); + } + + Component.onCompleted: Qt.callLater(_loadToModel) Connections { target: Settings.data.idle @@ -58,6 +67,45 @@ ColumnLayout { } } + // Shared Edit Popup + IdleCommandEditPopup { + id: editPopup + parent: Overlay.overlay + } + + function openEdit(index, nameVal, timeoutVal, cmdVal, resumeCmdVal) { + editPopup.editIndex = index; + editPopup.nameValue = nameVal; + editPopup.timeoutValue = timeoutVal; + editPopup.commandValue = cmdVal; + editPopup.resumeCommandValue = resumeCmdVal; + editPopup.showName = true; + + try { + editPopup.saved.disconnect(editPopup._savedSlot); + } catch (e) {} + + editPopup._savedSlot = function (timeout, cmd, resumeCmd, name) { + if (index >= 0 && index < entriesModel.count) { + entriesModel.setProperty(index, "name", name); + entriesModel.setProperty(index, "timeout", timeout); + entriesModel.setProperty(index, "command", cmd); + entriesModel.setProperty(index, "resumeCommand", resumeCmd); + } else { + entriesModel.append({ + "name": name, + "timeout": timeout, + "command": cmd, + "resumeCommand": resumeCmd + }); + } + root._saveFromModel(); + }; + + editPopup.saved.connect(editPopup._savedSlot); + editPopup.open(); + } + NLabel { label: I18n.tr("panels.idle.custom-label") description: I18n.tr("panels.idle.custom-description") @@ -72,73 +120,34 @@ ColumnLayout { Repeater { model: entriesModel - delegate: ColumnLayout { + delegate: RowLayout { id: entryDelegate required property int index + required property string name required property int timeout required property string command + required property string resumeCommand spacing: Style.marginM Layout.fillWidth: true - property bool _initialized: false - - Component.onCompleted: { - commandInput.text = entryDelegate.command; - _initialized = false; - timeoutSpinBox.value = entryDelegate.timeout; - _initialized = true; + NLabel { + Layout.fillWidth: true + label: entryDelegate.name || I18n.tr("panels.idle.custom-entry-unnamed") + description: I18n.trp("common.second", entryDelegate.timeout) + labelColor: (entryDelegate.command || entryDelegate.resumeCommand) ? Color.mPrimary : Color.mOnSurface } - RowLayout { - Layout.fillWidth: true - spacing: Style.marginM - - NSpinBox { - id: timeoutSpinBox - Layout.fillWidth: true - label: I18n.tr("panels.idle.custom-entry-timeout") - from: 1 - to: 86400 - suffix: "s" - onValueChanged: { - if (entryDelegate._initialized && !root._saving) { - entriesModel.setProperty(entryDelegate.index, "timeout", value); - root._saveFromModel(); - } - } - } - - NIconButton { - icon: "trash" - tooltipText: I18n.tr("panels.idle.custom-entry-delete") - Layout.alignment: Qt.AlignBottom - onClicked: { - entriesModel.remove(entryDelegate.index, 1); - root._saveFromModel(); - } - } + NIconButton { + icon: "settings" + tooltipText: I18n.tr("common.edit") + onClicked: root.openEdit(entryDelegate.index, entryDelegate.name, entryDelegate.timeout, entryDelegate.command, entryDelegate.resumeCommand) } - NTextInput { - id: commandInput - Layout.fillWidth: true - label: I18n.tr("panels.idle.custom-entry-command") - placeholderText: "notify-send \"Idle\"" - fontFamily: Settings.data.ui.fontFixed - onTextChanged: { - if (entryDelegate._initialized && !root._saving) { - entriesModel.setProperty(entryDelegate.index, "command", text); - root._saveFromModel(); - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginS - Layout.bottomMargin: Style.marginS - visible: entryDelegate.index < entriesModel.count - 1 + NIconButton { + icon: "trash" + tooltipText: I18n.tr("panels.idle.custom-entry-delete") + onClicked: root._removeEntry(entryDelegate.index) } } } @@ -148,11 +157,7 @@ ColumnLayout { icon: "add" enabled: Settings.data.idle.enabled onClicked: { - entriesModel.append({ - "timeout": 60, - "command": "" - }); - root._saveFromModel(); + root.openEdit(-1, "", 60, "", ""); } } } diff --git a/Modules/Panels/Settings/Tabs/Idle/IdleCommandEditPopup.qml b/Modules/Panels/Settings/Tabs/Idle/IdleCommandEditPopup.qml new file mode 100644 index 000000000..b772775e6 --- /dev/null +++ b/Modules/Panels/Settings/Tabs/Idle/IdleCommandEditPopup.qml @@ -0,0 +1,137 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +Popup { + id: root + modal: true + dim: true + anchors.centerIn: parent + + width: Math.min(600 * Style.uiScaleRatio, parent.width * 0.9) + height: Math.min(contentLayout.implicitHeight + padding * 2, parent.height * 0.9) + padding: Style.marginL + + property int editIndex: -1 + property int timeoutValue: 60 + property string commandValue: "" + property string resumeCommandValue: "" + property string nameValue: "" + property bool showCommand: true + property bool showTimeout: true + property bool showName: false + property string titleText: root.editIndex >= 0 ? I18n.tr("panels.idle.custom-entry-edit") : I18n.tr("panels.idle.custom-entry-new") + + signal saved(int timeout, string command, string resumeCommand, string name) + + property var _savedSlot: null + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mOutline + border.width: Style.borderS + } + + onOpened: { + nameInput.text = nameValue; + timeoutSpinBox.value = timeoutValue; + commandInput.text = commandValue; + resumeCommandInput.text = resumeCommandValue; + if (showName) { + nameInput.forceActiveFocus(); + } else { + timeoutSpinBox.forceActiveFocus(); + } + } + + contentItem: ColumnLayout { + id: contentLayout + spacing: Style.marginL + + // Header + RowLayout { + Layout.fillWidth: true + NText { + text: root.titleText + font.weight: Style.fontWeightBold + pointSize: Style.fontSizeL + Layout.fillWidth: true + } + NIconButton { + icon: "close" + onClicked: root.close() + } + } + + // Input Area + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NTextInput { + id: nameInput + visible: root.showName + Layout.fillWidth: true + label: I18n.tr("panels.idle.custom-entry-name") + placeholderText: I18n.tr("panels.idle.custom-entry-name-placeholder") + } + + NSpinBox { + id: timeoutSpinBox + visible: root.showTimeout + Layout.fillWidth: true + label: I18n.tr("panels.idle.custom-entry-timeout") + from: 0 + to: 86400 + suffix: "s" + } + + NTextInput { + id: commandInput + visible: root.showCommand + Layout.fillWidth: true + label: I18n.tr("panels.idle.custom-entry-command") + placeholderText: "notify-send \"Idle\"" + fontFamily: Settings.data.ui.fontFixed + } + + NTextInput { + id: resumeCommandInput + Layout.fillWidth: true + label: I18n.tr("panels.idle.resume-command-label") + placeholderText: "notify-send \"Welcome back!\"" + fontFamily: Settings.data.ui.fontFixed + } + } + + // Action Buttons + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Item { + Layout.fillWidth: true + } // Spacer + + NButton { + text: I18n.tr("common.cancel") + outlined: true + onClicked: root.close() + } + + NButton { + text: I18n.tr("common.save") + icon: "check" + backgroundColor: Color.mPrimary + textColor: Color.mOnPrimary + onClicked: { + root.saved(timeoutSpinBox.value, commandInput.text, resumeCommandInput.text, nameInput.text); + root.close(); + } + } + } + } +} diff --git a/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml b/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml index dec6995b5..545b04785 100644 --- a/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml +++ b/Modules/Panels/Settings/Tabs/LockScreen/AppearanceSubTab.qml @@ -76,6 +76,15 @@ ColumnLayout { defaultValue: Settings.getDefaultValue("general.compactLockScreen") } + NToggle { + label: I18n.tr("panels.lock-screen.enable-lockscreen-media-controls-label") + description: I18n.tr("panels.lock-screen.enable-lockscreen-media-controls-description") + checked: Settings.data.general.enableLockScreenMediaControls + onToggled: checked => Settings.data.general.enableLockScreenMediaControls = checked + visible: !Settings.data.general.compactLockScreen + defaultValue: Settings.getDefaultValue("general.enableLockScreenMediaControls") + } + NToggle { label: I18n.tr("panels.lock-screen.lock-screen-animations-label") description: I18n.tr("panels.lock-screen.lock-screen-animations-description") diff --git a/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml b/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml index d85346ed3..13239ab54 100644 --- a/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml +++ b/Modules/Panels/Settings/Tabs/LockScreen/MonitorsSubTab.qml @@ -35,9 +35,12 @@ ColumnLayout { model: Quickshell.screens || [] delegate: NCheckbox { Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } label: modelData.name || "Unknown" description: { - const compositorScale = CompositorService.getDisplayScale(modelData.name); I18n.tr("system.monitor-description", { "model": modelData.model, "width": modelData.width * compositorScale, diff --git a/Modules/Panels/Settings/Tabs/Notifications/GeneralSubTab.qml b/Modules/Panels/Settings/Tabs/Notifications/GeneralSubTab.qml index 73b5c1dba..3d48cd867 100644 --- a/Modules/Panels/Settings/Tabs/Notifications/GeneralSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Notifications/GeneralSubTab.qml @@ -122,9 +122,12 @@ ColumnLayout { model: Quickshell.screens || [] delegate: NCheckbox { Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } label: modelData.name || I18n.tr("common.unknown") description: { - const compositorScale = CompositorService.getDisplayScale(modelData.name); I18n.tr("system.monitor-description", { "model": modelData.model, "width": modelData.width * compositorScale, diff --git a/Modules/Panels/Settings/Tabs/Osd/GeneralSubTab.qml b/Modules/Panels/Settings/Tabs/Osd/GeneralSubTab.qml index 22fb65a7b..3b9ff786f 100644 --- a/Modules/Panels/Settings/Tabs/Osd/GeneralSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Osd/GeneralSubTab.qml @@ -114,9 +114,12 @@ ColumnLayout { model: Quickshell.screens || [] delegate: NCheckbox { Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } label: modelData.name || I18n.tr("common.unknown") description: { - const compositorScale = CompositorService.getDisplayScale(modelData.name); I18n.tr("system.monitor-description", { "model": modelData.model, "width": modelData.width * compositorScale, diff --git a/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuEntrySettingsDialog.qml b/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuEntrySettingsDialog.qml index d8b67fd90..a6ce4ec89 100644 --- a/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuEntrySettingsDialog.qml +++ b/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuEntrySettingsDialog.qml @@ -24,7 +24,8 @@ Popup { "reboot": "systemctl reboot || loginctl reboot", "rebootToUefi": "systemctl reboot --firmware-setup || loginctl reboot --firmware-setup", "logout": I18n.tr("panels.session-menu.entry-settings-default-command-logout"), - "shutdown": "systemctl poweroff || loginctl poweroff" + "shutdown": "systemctl poweroff || loginctl poweroff", + "userspaceReboot": "systemctl soft-reboot" } readonly property string defaultCommand: defaultCommands[entryId] || "" diff --git a/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuTab.qml b/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuTab.qml index e6a96b958..89375a765 100644 --- a/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuTab.qml +++ b/Modules/Panels/Settings/Tabs/SessionMenu/SessionMenuTab.qml @@ -37,6 +37,12 @@ ColumnLayout { "enabled": true, "required": false }, + { + "id": "userspaceReboot", + "text": I18n.tr("common.userspace-reboot"), + "enabled": false, + "required": false + }, { "id": "rebootToUefi", "text": I18n.tr("common.reboot-to-uefi"), @@ -59,15 +65,42 @@ ColumnLayout { function saveEntries() { var toSave = []; + var enabledNumber = 1; + for (var i = 0; i < entriesModel.length; i++) { + var keybind = entriesModel[i].keybind || ""; + + if (entriesModel[i].enabled) { + // For enabled entries with numeric or empty keybinds, assign sequential number + if (keybind === "" || /^\d+$/.test(keybind)) { + keybind = String(enabledNumber); + } + enabledNumber++; + } else { + // For disabled entries with numeric keybinds, clear them + if (/^\d+$/.test(keybind)) { + keybind = ""; + } + } + toSave.push({ "action": entriesModel[i].id, "enabled": entriesModel[i].enabled, "countdownEnabled": entriesModel[i].countdownEnabled !== undefined ? entriesModel[i].countdownEnabled : true, "command": entriesModel[i].command || "", - "keybind": entriesModel[i].keybind || "" + "keybind": keybind }); } + + // Update local model with renumbered keybinds + var newModel = []; + for (var i = 0; i < entriesModel.length; i++) { + newModel.push(Object.assign({}, entriesModel[i], { + "keybind": toSave[i].keybind + })); + } + entriesModel = newModel; + Settings.data.sessionMenu.powerOptions = toSave; } diff --git a/Modules/Panels/Settings/Tabs/SystemMonitor/PerformanceSubTab.qml b/Modules/Panels/Settings/Tabs/SystemMonitor/PerformanceSubTab.qml new file mode 100644 index 000000000..b461c6f37 --- /dev/null +++ b/Modules/Panels/Settings/Tabs/SystemMonitor/PerformanceSubTab.qml @@ -0,0 +1,29 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + spacing: Style.marginL + Layout.fillWidth: true + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.system.noctalia-performance-disable-wallpaper-label") + description: I18n.tr("panels.system.noctalia-performance-disable-wallpaper-description") + checked: !Settings.data.noctaliaPerformance.disableWallpaper + defaultValue: !Settings.getDefaultValue("noctaliaPerformance.disableWallpaper") + onToggled: checked => Settings.data.noctaliaPerformance.disableWallpaper = !checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.system.noctalia-performance-disable-desktop-widgets-label") + description: I18n.tr("panels.system.noctalia-performance-disable-desktop-widgets-description") + checked: !Settings.data.noctaliaPerformance.disableDesktopWidgets + defaultValue: !Settings.getDefaultValue("noctaliaPerformance.disableDesktopWidgets") + onToggled: checked => Settings.data.noctaliaPerformance.disableDesktopWidgets = !checked + } +} diff --git a/Modules/Panels/Settings/Tabs/SystemMonitor/SystemMonitorTab.qml b/Modules/Panels/Settings/Tabs/SystemMonitor/SystemMonitorTab.qml index a3f9d17b7..e5418ca08 100644 --- a/Modules/Panels/Settings/Tabs/SystemMonitor/SystemMonitorTab.qml +++ b/Modules/Panels/Settings/Tabs/SystemMonitor/SystemMonitorTab.qml @@ -18,7 +18,7 @@ ColumnLayout { currentIndex: tabView.currentIndex NTabButton { - text: I18n.tr("common.general") + text: I18n.tr("system-monitor.title") tabIndex: 0 checked: subTabBar.currentIndex === 0 } @@ -27,6 +27,11 @@ ColumnLayout { tabIndex: 1 checked: subTabBar.currentIndex === 1 } + NTabButton { + text: I18n.tr("common.performance") + tabIndex: 2 + checked: subTabBar.currentIndex === 2 + } } Item { @@ -42,5 +47,6 @@ ColumnLayout { screen: root.screen } ThresholdsSubTab {} + PerformanceSubTab {} } } diff --git a/Modules/Panels/Settings/Tabs/UserInterface/AppearanceSubTab.qml b/Modules/Panels/Settings/Tabs/UserInterface/AppearanceSubTab.qml index 2eb102ad6..76f6fa7a9 100644 --- a/Modules/Panels/Settings/Tabs/UserInterface/AppearanceSubTab.qml +++ b/Modules/Panels/Settings/Tabs/UserInterface/AppearanceSubTab.qml @@ -25,6 +25,14 @@ ColumnLayout { onToggled: checked => Settings.data.ui.boxBorderEnabled = checked } + NToggle { + label: I18n.tr("panels.user-interface.scrollbar-always-visible-label") + description: I18n.tr("panels.user-interface.scrollbar-always-visible-description") + checked: Settings.data.ui.scrollbarAlwaysVisible + defaultValue: Settings.getDefaultValue("ui.scrollbarAlwaysVisible") + onToggled: checked => Settings.data.ui.scrollbarAlwaysVisible = checked + } + NToggle { label: I18n.tr("panels.user-interface.shadows-label") description: I18n.tr("panels.user-interface.shadows-description") @@ -33,6 +41,22 @@ ColumnLayout { onToggled: checked => Settings.data.general.enableShadows = checked } + NToggle { + label: I18n.tr("panels.user-interface.blur-behind-label") + description: I18n.tr("panels.user-interface.blur-behind-description") + checked: Settings.data.general.enableBlurBehind + defaultValue: Settings.getDefaultValue("general.enableBlurBehind") + onToggled: checked => Settings.data.general.enableBlurBehind = checked + } + + NToggle { + label: I18n.tr("panels.user-interface.translucent-widgets-label") + description: I18n.tr("panels.user-interface.translucent-widgets-description") + checked: Settings.data.ui.translucentWidgets + defaultValue: Settings.getDefaultValue("ui.translucentWidgets") + onToggled: checked => Settings.data.ui.translucentWidgets = checked + } + NComboBox { visible: Settings.data.general.enableShadows label: I18n.tr("panels.user-interface.shadows-direction-label") @@ -169,36 +193,18 @@ ColumnLayout { Layout.fillWidth: true visible: !Settings.data.general.animationDisabled - RowLayout { - spacing: Style.marginL + NValueSlider { Layout.fillWidth: true - - NValueSlider { - Layout.fillWidth: true - label: I18n.tr("panels.user-interface.animation-speed-label") - description: I18n.tr("panels.user-interface.animation-speed-description") - from: 0 - to: 2.0 - stepSize: 0.01 - value: Settings.data.general.animationSpeed - defaultValue: Settings.getDefaultValue("general.animationSpeed") - onMoved: value => Settings.data.general.animationSpeed = Math.max(value, 0.05) - text: Math.round(Settings.data.general.animationSpeed * 100) + "%" - } - - Item { - Layout.preferredWidth: 30 * Style.uiScaleRatio - Layout.preferredHeight: 30 * Style.uiScaleRatio - - NIconButton { - icon: "restore" - baseSize: Style.baseWidgetSize * 0.8 - tooltipText: I18n.tr("panels.user-interface.animation-speed-reset") - onClicked: Settings.data.general.animationSpeed = Settings.getDefaultValue("general.animationSpeed") - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } - } + label: I18n.tr("panels.user-interface.animation-speed-label") + description: I18n.tr("panels.user-interface.animation-speed-description") + from: 0 + to: 2.0 + stepSize: 0.01 + showReset: true + value: Settings.data.general.animationSpeed + defaultValue: Settings.getDefaultValue("general.animationSpeed") + onMoved: value => Settings.data.general.animationSpeed = Math.max(value, 0.05) + text: Math.round(Settings.data.general.animationSpeed * 100) + "%" } } } diff --git a/Modules/Panels/Settings/Tabs/UserInterface/ScreenCornersSubTab.qml b/Modules/Panels/Settings/Tabs/UserInterface/ScreenCornersSubTab.qml index fa356b083..1607abc34 100644 --- a/Modules/Panels/Settings/Tabs/UserInterface/ScreenCornersSubTab.qml +++ b/Modules/Panels/Settings/Tabs/UserInterface/ScreenCornersSubTab.qml @@ -29,36 +29,18 @@ ColumnLayout { spacing: Style.marginXXS Layout.fillWidth: true - RowLayout { - spacing: Style.marginL + NValueSlider { Layout.fillWidth: true - - NValueSlider { - Layout.fillWidth: true - label: I18n.tr("panels.general.screen-corners-radius-label") - description: I18n.tr("panels.general.screen-corners-radius-description") - from: 0 - to: 2 - stepSize: 0.01 - value: Settings.data.general.screenRadiusRatio - defaultValue: Settings.getDefaultValue("general.screenRadiusRatio") - onMoved: value => Settings.data.general.screenRadiusRatio = value - text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%" - } - - Item { - Layout.preferredWidth: 30 * Style.uiScaleRatio - Layout.preferredHeight: 30 * Style.uiScaleRatio - - NIconButton { - icon: "restore" - baseSize: Style.baseWidgetSize * 0.8 - tooltipText: I18n.tr("panels.general.screen-corners-radius-reset") - onClicked: Settings.data.general.screenRadiusRatio = Settings.getDefaultValue("general.screenRadiusRatio") - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } - } + label: I18n.tr("panels.general.screen-corners-radius-label") + description: I18n.tr("panels.general.screen-corners-radius-description") + from: 0 + to: 2 + stepSize: 0.01 + showReset: true + value: Settings.data.general.screenRadiusRatio + defaultValue: Settings.getDefaultValue("general.screenRadiusRatio") + onMoved: value => Settings.data.general.screenRadiusRatio = value + text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%" } } } diff --git a/Modules/Panels/Settings/Tabs/Wallpaper/LookAndFeelSubTab.qml b/Modules/Panels/Settings/Tabs/Wallpaper/LookAndFeelSubTab.qml index e33aa23b2..72039c0bf 100644 --- a/Modules/Panels/Settings/Tabs/Wallpaper/LookAndFeelSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Wallpaper/LookAndFeelSubTab.qml @@ -59,6 +59,7 @@ ColumnLayout { from: 500 to: 10000 stepSize: 100 + showReset: true value: Settings.data.wallpaper.transitionDuration onMoved: value => Settings.data.wallpaper.transitionDuration = value text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(1) + "s" @@ -71,6 +72,7 @@ ColumnLayout { description: I18n.tr("panels.wallpaper.look-feel-edge-smoothness-description") from: 0.0 to: 1.0 + showReset: true value: Settings.data.wallpaper.transitionEdgeSmoothness onMoved: value => Settings.data.wallpaper.transitionEdgeSmoothness = value text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%" diff --git a/Modules/Panels/SetupWizard/SetupDockStep.qml b/Modules/Panels/SetupWizard/SetupDockStep.qml index 3edc85fa2..103e47f81 100644 --- a/Modules/Panels/SetupWizard/SetupDockStep.qml +++ b/Modules/Panels/SetupWizard/SetupDockStep.qml @@ -177,10 +177,13 @@ ColumnLayout { model: Quickshell.screens || [] delegate: NCheckbox { Layout.fillWidth: true + readonly property real compositorScale: { + const info = CompositorService.displayScales[modelData.name]; + return (info && info.scale) ? info.scale : 1.0; + } label: modelData.name || "Unknown" visible: Settings.data.dock.enabled description: { - const compositorScale = CompositorService.getDisplayScale(modelData.name); I18n.tr("system.monitor-description", { "model": modelData.model, "width": modelData.width * compositorScale, diff --git a/Modules/Panels/SetupWizard/SetupWizard.qml b/Modules/Panels/SetupWizard/SetupWizard.qml index 490e2a337..849e4dd16 100644 --- a/Modules/Panels/SetupWizard/SetupWizard.qml +++ b/Modules/Panels/SetupWizard/SetupWizard.qml @@ -347,23 +347,23 @@ SmartPanel { model: [ { "icon": "sparkles", - "label": "Welcome" + "label": I18n.tr("setup.welcome") }, { "icon": "image", - "label": "Wallpaper" + "label": I18n.tr("common.wallpaper") }, { "icon": "palette", - "label": "Appearance" + "label": I18n.tr("common.appearance") }, { "icon": "settings", - "label": "Customize" + "label": I18n.tr("common.customize") }, { "icon": "device-desktop", - "label": "Dock" + "label": I18n.tr("panels.dock.title") } ] delegate: RowLayout { diff --git a/Modules/Panels/SystemStats/SystemStatsPanel.qml b/Modules/Panels/SystemStats/SystemStatsPanel.qml index 6461dee98..49acd9a05 100644 --- a/Modules/Panels/SystemStats/SystemStatsPanel.qml +++ b/Modules/Panels/SystemStats/SystemStatsPanel.qml @@ -80,6 +80,7 @@ SmartPanel { ColumnLayout { anchors.fill: parent anchors.margins: Style.marginS + anchors.bottomMargin: Style.radiusM * 0.5 spacing: Style.marginXS RowLayout { @@ -127,9 +128,6 @@ SmartPanel { NGraph { Layout.fillWidth: true Layout.fillHeight: true - Layout.leftMargin: -Style.marginS - Layout.rightMargin: -Style.marginS - Layout.bottomMargin: 2 values: SystemStatService.cpuHistory values2: SystemStatService.cpuTempHistory minValue: 0 @@ -138,6 +136,7 @@ SmartPanel { maxValue2: Math.max(SystemStatService.cpuTempHistoryMax + 5, 1) color: Color.mPrimary color2: Color.mSecondary + strokeWidth: 1.5 * Style.uiScaleRatio fill: true fillOpacity: 0.15 updateInterval: SystemStatService.cpuUsageIntervalMs @@ -153,6 +152,7 @@ SmartPanel { ColumnLayout { anchors.fill: parent anchors.margins: Style.marginS + anchors.bottomMargin: Style.radiusM * 0.5 spacing: Style.marginXS RowLayout { @@ -166,7 +166,7 @@ SmartPanel { } NText { - text: `${Math.round(SystemStatService.memPercent)}% (${SystemStatService.formatGigabytes(SystemStatService.memGb).replace(/[^0-9.]/g, "")} GB)` + text: `${Math.round(SystemStatService.memPercent)}% (${(SystemStatService.memGb).toFixed(1)} GiB)` pointSize: Style.fontSizeXS color: Color.mPrimary font.family: Settings.data.ui.fontFixed @@ -186,13 +186,11 @@ SmartPanel { NGraph { Layout.fillWidth: true Layout.fillHeight: true - Layout.leftMargin: -Style.marginS - Layout.rightMargin: -Style.marginS - Layout.bottomMargin: 2 values: SystemStatService.memHistory minValue: 0 maxValue: 100 color: Color.mPrimary + strokeWidth: 1.5 * Style.uiScaleRatio fill: true fillOpacity: 0.15 updateInterval: SystemStatService.memIntervalMs @@ -208,6 +206,7 @@ SmartPanel { ColumnLayout { anchors.fill: parent anchors.margins: Style.marginS + anchors.bottomMargin: Style.radiusM * 0.5 spacing: Style.marginXS RowLayout { @@ -255,9 +254,6 @@ SmartPanel { NGraph { Layout.fillWidth: true Layout.fillHeight: true - Layout.leftMargin: -Style.marginS - Layout.rightMargin: -Style.marginS - Layout.bottomMargin: 2 values: SystemStatService.rxSpeedHistory values2: SystemStatService.txSpeedHistory minValue: 0 @@ -266,6 +262,7 @@ SmartPanel { maxValue2: SystemStatService.txMaxSpeed color: Color.mPrimary color2: Color.mSecondary + strokeWidth: 1.5 * Style.uiScaleRatio fill: true fillOpacity: 0.15 updateInterval: SystemStatService.networkIntervalMs @@ -392,7 +389,7 @@ SmartPanel { } NText { - text: `${SystemStatService.formatGigabytes(SystemStatService.swapGb).replace(/[^0-9.]/g, "")} / ${SystemStatService.formatGigabytes(SystemStatService.swapTotalGb).replace(/[^0-9.]/g, "")} GB` + text: `${(SystemStatService.swapGb).toFixed(1)} / ${(SystemStatService.swapTotalGb).toFixed(1)} GiB` pointSize: Style.fontSizeXS color: Color.mOnSurface Layout.fillWidth: true diff --git a/Modules/Panels/Wallpaper/WallpaperPanel.qml b/Modules/Panels/Wallpaper/WallpaperPanel.qml index ff53731de..b7ef62586 100644 --- a/Modules/Panels/Wallpaper/WallpaperPanel.qml +++ b/Modules/Panels/Wallpaper/WallpaperPanel.qml @@ -950,14 +950,14 @@ SmartPanel { } tooltipText: { if (sortOrder === "date_desc") - return "Sort: Newest First"; + return I18n.tr("wallpaper.panel.sort-date-desc"); if (sortOrder === "date_asc") - return "Sort: Oldest First"; + return I18n.tr("wallpaper.panel.sort-date-asc"); if (sortOrder === "name_desc") - return "Sort: Name (Z-A)"; + return I18n.tr("wallpaper.panel.sort-name-desc"); if (sortOrder === "random") - return "Sort: Random"; - return "Sort: Name (A-Z)"; + return I18n.tr("wallpaper.panel.sort-random"); + return I18n.tr("wallpaper.panel.sort-name-asc"); } baseSize: Style.baseWidgetSize * 0.8 onClicked: { diff --git a/Modules/Toast/Toast.qml b/Modules/Toast/Toast.qml index 05a8b1b5f..eda85fc5e 100644 --- a/Modules/Toast/Toast.qml +++ b/Modules/Toast/Toast.qml @@ -19,7 +19,8 @@ Item { signal hidden - readonly property int notificationWidth: Math.round(440 * Style.uiScaleRatio) + readonly property bool isCompact: Settings.data.notifications?.density === "compact" + readonly property int notificationWidth: Math.round((isCompact ? 320 : 440) * Style.uiScaleRatio) readonly property int shadowPadding: Style.shadowBlurMax + Style.marginL width: notificationWidth + shadowPadding * 2 @@ -282,11 +283,11 @@ Item { RowLayout { id: contentLayout anchors.fill: background - anchors.topMargin: Style.marginM - anchors.bottomMargin: Style.marginM - anchors.leftMargin: Style.margin2M - anchors.rightMargin: Style.margin2M - spacing: Style.marginL + anchors.topMargin: isCompact ? Style.marginS : Style.marginM + anchors.bottomMargin: isCompact ? Style.marginS : Style.marginM + anchors.leftMargin: isCompact ? Style.marginM : Style.margin2M + anchors.rightMargin: isCompact ? Style.marginM : Style.margin2M + spacing: isCompact ? Style.marginM : Style.marginL // Icon NIcon { @@ -309,7 +310,7 @@ Item { return Color.mOnSurface; } } - pointSize: Style.fontSizeXXL * 1.5 + pointSize: isCompact ? Style.fontSizeXL : Style.fontSizeXXL * 1.5 Layout.alignment: Qt.AlignVCenter } @@ -323,7 +324,7 @@ Item { Layout.fillWidth: true text: root.title color: Color.mOnSurface - pointSize: Style.fontSizeL + pointSize: isCompact ? Style.fontSizeM : Style.fontSizeL font.weight: Style.fontWeightBold wrapMode: Text.WordWrap visible: text.length > 0 @@ -333,8 +334,10 @@ Item { Layout.fillWidth: true text: root.description color: Color.mOnSurface - pointSize: Style.fontSizeM + pointSize: isCompact ? Style.fontSizeS : Style.fontSizeM wrapMode: Text.WordWrap + maximumLineCount: isCompact ? 2 : 20 + elide: isCompact ? Text.ElideRight : Text.ElideNone visible: text.length > 0 } diff --git a/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml index 703fb6690..e0717ae8d 100644 --- a/Modules/Tooltip/Tooltip.qml +++ b/Modules/Tooltip/Tooltip.qml @@ -639,7 +639,7 @@ PopupWindow { Rectangle { anchors.fill: parent - anchors.margins: border.width / 2 + anchors.margins: border.width color: Color.mSurface border.color: Color.mOutline border.width: Style.borderS diff --git a/Plugins/WaylandEffects/.gitignore b/Plugins/WaylandEffects/.gitignore deleted file mode 100644 index 567609b12..000000000 --- a/Plugins/WaylandEffects/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ diff --git a/Plugins/WaylandEffects/BackgroundBlur.cpp b/Plugins/WaylandEffects/BackgroundBlur.cpp deleted file mode 100644 index e07f81bbc..000000000 --- a/Plugins/WaylandEffects/BackgroundBlur.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include "BackgroundBlur.h" -#include "BackgroundEffectManager.h" - -#include -#include -#include -#include - -// --- wl_compositor singleton via registry --- - -static struct wl_compositor *s_compositor = nullptr; - -static void registry_global(void */*data*/, struct wl_registry *registry, - uint32_t name, const char *interface, uint32_t version) -{ - if (strcmp(interface, wl_compositor_interface.name) == 0) - s_compositor = static_cast( - wl_registry_bind(registry, name, &wl_compositor_interface, qMin(version, 6u))); -} - -static void registry_global_remove(void *, struct wl_registry *, uint32_t) {} - -static const struct wl_registry_listener s_registry_listener = { - registry_global, - registry_global_remove, -}; - -static struct wl_compositor *ensureCompositor() -{ - if (s_compositor) - return s_compositor; - - auto *native = QGuiApplication::platformNativeInterface(); - auto *display = static_cast( - native->nativeResourceForIntegration("wl_display")); - if (!display) - return nullptr; - - auto *registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, &s_registry_listener, nullptr); - wl_display_roundtrip(display); - wl_registry_destroy(registry); - - return s_compositor; -} - -// --- BackgroundBlur implementation --- - -BackgroundBlur::BackgroundBlur(QQuickItem *parent) - : QQuickItem(parent) -{ - auto *mgr = BackgroundEffectManager::instance(); - m_managerActiveConn = connect(mgr, &QWaylandClientExtension::activeChanged, - this, &BackgroundBlur::tryAttach); -} - -BackgroundBlur::~BackgroundBlur() -{ - detach(); - disconnect(m_managerActiveConn); -} - -void BackgroundBlur::setRegions(const QList ®ions) -{ - if (m_regions == regions) - return; - m_regions = regions; - Q_EMIT regionsChanged(); - updateBlurRegion(); -} - -void BackgroundBlur::componentComplete() -{ - QQuickItem::componentComplete(); - tryAttach(); -} - -void BackgroundBlur::itemChange(ItemChange change, const ItemChangeData &data) -{ - QQuickItem::itemChange(change, data); - - if (change != ItemSceneChange) - return; - - detach(); - - if (!data.window) - return; - - if (data.window->isVisible()) { - QMetaObject::invokeMethod(this, &BackgroundBlur::tryAttach, Qt::QueuedConnection); - } else { - m_windowVisibleConn = connect(data.window, &QWindow::visibleChanged, - this, [this](bool visible) { - if (visible) - tryAttach(); - }); - } -} - -void BackgroundBlur::tryAttach() -{ - if (m_effect) - return; - - auto *mgr = BackgroundEffectManager::instance(); - if (!mgr->isActive()) - return; - - auto *surface = nativeSurface(); - if (!surface) - return; - - auto *raw = mgr->get_background_effect(surface); - if (!raw) - return; - - m_effect = new QtWayland::ext_background_effect_surface_v1(raw); - Q_EMIT activeChanged(); - updateBlurRegion(); -} - -void BackgroundBlur::detach() -{ - if (m_windowVisibleConn) { - disconnect(m_windowVisibleConn); - m_windowVisibleConn = {}; - } - - if (!m_effect) - return; - - m_effect->destroy(); - delete m_effect; - m_effect = nullptr; - - if (auto *w = window()) - w->requestUpdate(); - - Q_EMIT activeChanged(); -} - -void BackgroundBlur::updateBlurRegion() -{ - if (!m_effect) - return; - - if (m_regions.isEmpty()) { - m_effect->set_blur_region(nullptr); - } else { - auto *compositor = ensureCompositor(); - if (!compositor) - return; - - auto *region = wl_compositor_create_region(compositor); - for (const auto &rect : m_regions) { - wl_region_add(region, - qRound(rect.x()), qRound(rect.y()), - qRound(rect.width()), qRound(rect.height())); - } - m_effect->set_blur_region(region); - wl_region_destroy(region); - } - - if (auto *w = window()) - w->requestUpdate(); -} - -struct wl_surface *BackgroundBlur::nativeSurface() -{ - auto *w = window(); - if (!w) - return nullptr; - auto *native = QGuiApplication::platformNativeInterface(); - return static_cast( - native->nativeResourceForWindow("surface", w)); -} - -struct wl_compositor *BackgroundBlur::nativeCompositor() -{ - return ensureCompositor(); -} diff --git a/Plugins/WaylandEffects/BackgroundBlur.h b/Plugins/WaylandEffects/BackgroundBlur.h deleted file mode 100644 index 19ce55870..000000000 --- a/Plugins/WaylandEffects/BackgroundBlur.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include "qwayland-ext-background-effect-v1.h" - -class BackgroundBlur : public QQuickItem -{ - Q_OBJECT - QML_ELEMENT - - /// List of rectangles (in surface-local coordinates) to blur. - /// An empty list removes the blur effect. - Q_PROPERTY(QList regions READ regions WRITE setRegions NOTIFY regionsChanged) - - /// Whether the blur effect is currently active on the surface. - Q_PROPERTY(bool active READ active NOTIFY activeChanged) - -public: - explicit BackgroundBlur(QQuickItem *parent = nullptr); - ~BackgroundBlur() override; - - QList regions() const { return m_regions; } - void setRegions(const QList ®ions); - - bool active() const { return m_effect != nullptr; } - -Q_SIGNALS: - void regionsChanged(); - void activeChanged(); - -protected: - void componentComplete() override; - void itemChange(ItemChange change, const ItemChangeData &data) override; - -private: - void tryAttach(); - void detach(); - void updateBlurRegion(); - - struct wl_surface *nativeSurface(); - struct wl_compositor *nativeCompositor(); - - QList m_regions; - QtWayland::ext_background_effect_surface_v1 *m_effect = nullptr; - QMetaObject::Connection m_windowVisibleConn; - QMetaObject::Connection m_managerActiveConn; -}; diff --git a/Plugins/WaylandEffects/BackgroundEffectManager.cpp b/Plugins/WaylandEffects/BackgroundEffectManager.cpp deleted file mode 100644 index 4ee729eea..000000000 --- a/Plugins/WaylandEffects/BackgroundEffectManager.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "BackgroundEffectManager.h" - -BackgroundEffectManager::BackgroundEffectManager() - : QWaylandClientExtensionTemplate(/* version */ 1) -{ - initialize(); -} - -BackgroundEffectManager *BackgroundEffectManager::instance() -{ - static auto *mgr = new BackgroundEffectManager; - return mgr; -} - -void BackgroundEffectManager::ext_background_effect_manager_v1_capabilities(uint32_t capabilities) -{ - const bool blur = capabilities & capability_blur; - if (m_hasBlur != blur) { - m_hasBlur = blur; - Q_EMIT blurSupportChanged(); - } -} diff --git a/Plugins/WaylandEffects/BackgroundEffectManager.h b/Plugins/WaylandEffects/BackgroundEffectManager.h deleted file mode 100644 index 0d4100e88..000000000 --- a/Plugins/WaylandEffects/BackgroundEffectManager.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include "qwayland-ext-background-effect-v1.h" - -class BackgroundEffectManager - : public QWaylandClientExtensionTemplate - , public QtWayland::ext_background_effect_manager_v1 -{ - Q_OBJECT - -public: - static BackgroundEffectManager *instance(); - - bool hasBlur() const { return m_hasBlur; } - -Q_SIGNALS: - void blurSupportChanged(); - -protected: - void ext_background_effect_manager_v1_capabilities(uint32_t capabilities) override; - -private: - explicit BackgroundEffectManager(); - - bool m_hasBlur = false; -}; diff --git a/Plugins/WaylandEffects/CMakeLists.txt b/Plugins/WaylandEffects/CMakeLists.txt deleted file mode 100644 index e0729ba95..000000000 --- a/Plugins/WaylandEffects/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(noctalia-wayland-effects VERSION 1.0 LANGUAGES C CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_AUTOMOC ON) - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick WaylandClient) -find_package(Qt6GuiPrivate REQUIRED) -find_package(PkgConfig REQUIRED) -pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) - -qt_add_library(noctaliawayland SHARED) - -qt_add_qml_module(noctaliawayland - URI Noctalia.Wayland - VERSION 1.0 - OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Noctalia/Wayland" - SOURCES - BackgroundEffectManager.h BackgroundEffectManager.cpp - BackgroundBlur.h BackgroundBlur.cpp -) - -qt_generate_wayland_protocol_client_sources(noctaliawayland - FILES - "${CMAKE_CURRENT_SOURCE_DIR}/protocols/ext-background-effect-v1.xml" -) - -# The Qt macro generates the C protocol file but doesn't compile it. -# Add it manually so the interface structs are defined. -target_sources(noctaliawayland PRIVATE - "${CMAKE_CURRENT_BINARY_DIR}/wayland-ext-background-effect-v1-protocol.c" -) - -target_link_libraries(noctaliawayland PRIVATE - Qt6::Core - Qt6::Gui - Qt6::GuiPrivate - Qt6::Quick - Qt6::WaylandClient - ${WAYLAND_CLIENT_LIBRARIES} -) - -target_include_directories(noctaliawayland PRIVATE - ${WAYLAND_CLIENT_INCLUDE_DIRS} - "${CMAKE_CURRENT_BINARY_DIR}" -) - -install( - DIRECTORY "${CMAKE_BINARY_DIR}/Noctalia/" - DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/qt6/qml/Noctalia" -) diff --git a/Plugins/WaylandEffects/protocols/ext-background-effect-v1.xml b/Plugins/WaylandEffects/protocols/ext-background-effect-v1.xml deleted file mode 100644 index fa6d7116a..000000000 --- a/Plugins/WaylandEffects/protocols/ext-background-effect-v1.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - Copyright (C) 2015 Martin Gräßlin - Copyright (C) 2015 Marco Martin - Copyright (C) 2020 Vlad Zahorodnii - Copyright (C) 2024 Xaver Hugl - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - - This protocol provides a way to improve visuals of translucent surfaces - by applying effects like blur to the background behind them. - - The capabilities are send when the global is bound, and every time they - change. Note that when the capability goes away, the corresponding effect - is no longer applied by the compositor, even if it was set before. - - Warning! The protocol described in this file is currently in the testing - phase. Backward compatible changes may be added together with the - corresponding interface version bump. Backward incompatible changes can - only be done by creating a new major version of the extension. - - - - - Informs the server that the client will no longer be using this - protocol object. Existing objects created by this object are not - affected. - - - - - - - - - - - - - - - - - - - Instantiate an interface extension for the given wl_surface to add - effects like blur for the background behind it. - - If the given wl_surface already has a ext_background_effect_surface_v1 - object associated, the background_effect_exists protocol error will be - raised. - - - - - - - - - The background effect object provides a way to specify a region behind - a surface that should have background effects like blur applied. - - If the wl_surface associated with the ext_background_effect_surface_v1 - object has been destroyed, this object becomes inert. - - - - - Informs the server that the client will no longer be using this protocol - object. The effect regions will be removed on the next commit. - - - - - - - - - - This request sets the region of the surface that will have its - background blurred. - - The blur region is specified in the surface-local coordinates, and - clipped by the compositor to the surface size. - - The initial value for the blur region is empty. Setting the pending - blur region has copy semantics, and the wl_region object can be - destroyed immediately. A NULL wl_region removes the effect. - - The blur region is double-buffered state, and will be applied on - the next wl_surface.commit. - - The blur algorithm is subject to compositor policies. - - If the associated surface has been destroyed, the surface_destroyed - error will be raised. - - - - - - diff --git a/README.md b/README.md index c2a3b98b2..7259f79e3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Noctalia shell +# Noctalia Shell **_quiet by design_** diff --git a/Scripts/bash/template-apply.sh b/Scripts/bash/template-apply.sh index 3b600c9dc..6e425a575 100755 --- a/Scripts/bash/template-apply.sh +++ b/Scripts/bash/template-apply.sh @@ -241,7 +241,7 @@ cava) # Reload cava if it's running, but only if it's not using stdin config if pgrep -f cava >/dev/null; then - # Check if Cava is running with -p /dev/stdin (managed by CavaService) + # Check if Cava is running with -p /dev/stdin (standalone cava) if ! pgrep -af cava | grep -q -- "-p.*stdin"; then pkill -USR1 cava fi diff --git a/Scripts/dev/build-settings-search-index.py b/Scripts/dev/build-settings-search-index.py index eeee74aed..50f82a420 100755 --- a/Scripts/dev/build-settings-search-index.py +++ b/Scripts/dev/build-settings-search-index.py @@ -32,6 +32,7 @@ WIDGET_TYPES = ( "NTextInput", "NCheckbox", "NLabel", + "NColorChoice", "HookRow", ) diff --git a/Scripts/python/src/calendar/calendar-events.py b/Scripts/python/src/calendar/calendar-events.py index 2212aaf79..e080eb82f 100755 --- a/Scripts/python/src/calendar/calendar-events.py +++ b/Scripts/python/src/calendar/calendar-events.py @@ -48,14 +48,16 @@ def safe_get_time(ical_time): except: return None, False -def add_event(summary, calendar_name, start_ts, end_ts, location="", description="", all_day=False): +def add_event(summary, calendar_name, start_ts, end_ts, location="", description="", all_day=False, calendar_uid="", uid=""): all_events.append({ 'calendar': calendar_name, 'summary': summary, 'start': start_ts, 'end': end_ts, 'location': location, - 'description': description + 'description': description, + 'calendar_uid': calendar_uid, + 'uid': uid }) registry = EDataServer.SourceRegistry.new_sync(None) @@ -69,7 +71,7 @@ for source in sources: print(f"\nProcessing calendar: {calendar_name}", file=sys.stderr) try: - client = ECal.Client.connect_sync(source, ECal.ClientSourceType.EVENTS, 30, None) + client = ECal.Client.connect_sync(source, ECal.ClientSourceType.EVENTS, 5, None) start_dt = datetime.fromtimestamp(start_time) end_dt = datetime.fromtimestamp(end_time) @@ -101,15 +103,21 @@ for source in sources: summary = getattr(obj, "get_summary", lambda: "(No title)")() dtstart = getattr(obj, "get_dtstart", lambda: None)() dtend = getattr(obj, "get_dtend", lambda: None)() + location = getattr(obj, "get_location", lambda: "")() or "" + description = getattr(obj, "get_description", lambda: "")() or "" start_ts, all_day = safe_get_time(dtstart) end_ts, _ = safe_get_time(dtend) if start_ts: if end_ts is None: end_ts = start_ts + 3600 - add_event(summary, calendar_name, start_ts, end_ts) + event_uid = getattr(obj, "get_uid", lambda: "")() or "" + add_event(summary, calendar_name, start_ts, end_ts, location, description, + calendar_uid=source.get_uid(), uid=event_uid) continue summary = getattr(comp, "get_summary", lambda: "(No title)")() + location = getattr(comp, "get_location", lambda: "")() or "" + description = getattr(comp, "get_description", lambda: "")() or "" dtstart = getattr(comp, "get_dtstart", lambda: None)() dtend = getattr(comp, "get_dtend", lambda: None)() start_ts, all_day = safe_get_time(dtstart) @@ -138,7 +146,8 @@ for source in sources: # --- normal event --- if not rrule_prop and not rdates: - add_event(summary, calendar_name, start_ts, end_ts) + add_event(summary, calendar_name, start_ts, end_ts, location, description, + calendar_uid=source.get_uid(), uid=comp.get_uid() or "") continue # --- recurrent events --- @@ -221,7 +230,8 @@ for source in sources: # --- add occurences to all_events --- for occ_start, occ_end in occurrences: - add_event(summary, calendar_name, occ_start, occ_end) + add_event(summary, calendar_name, occ_start, occ_end, location, description, + calendar_uid=source.get_uid(), uid=comp.get_uid() or "") except Exception as e: diff --git a/Scripts/python/src/calendar/khal-events.py b/Scripts/python/src/calendar/khal-events.py index fc5abca76..6b545425d 100755 --- a/Scripts/python/src/calendar/khal-events.py +++ b/Scripts/python/src/calendar/khal-events.py @@ -55,6 +55,7 @@ def main(): '--json', 'calendar', '--json', 'description', '--json', 'location', + '--json', 'repeat-pattern', khal_start, duration ] diff --git a/Scripts/python/src/theming/gtk-refresh.py b/Scripts/python/src/theming/gtk-refresh.py index 18f409d6d..436bf91f0 100644 --- a/Scripts/python/src/theming/gtk-refresh.py +++ b/Scripts/python/src/theming/gtk-refresh.py @@ -40,40 +40,53 @@ def theme_exists(theme_name: str) -> bool: return False +GTK_IMPORT = '@import url("noctalia.css");' + + +def ensure_gtk_css_import(gtk_css: Path, colors_file: Path, label: str) -> bool: + """ + Append the noctalia.css import to gtk.css if not already present. + If gtk.css doesn't exist, create it with the import. + Does not overwrite user modifications (similar to niri template). + """ + if not colors_file.exists(): + print(f"Error: {label} noctalia.css not found at {colors_file}", file=sys.stderr) + return False + + # If gtk.css is a symlink, replace it with a regular file + if gtk_css.is_symlink(): + gtk_css.unlink() + + if gtk_css.exists(): + content = gtk_css.read_text() + # Already has the import (flexible: allow optional whitespace / different quoting) + if "noctalia.css" in content and "@import" in content: + return True + # Append import to the end + new_content = content.rstrip() + if new_content and not new_content.endswith("\n"): + new_content += "\n" + new_content += "\n" + GTK_IMPORT + "\n" + gtk_css.write_text(new_content) + print(f"Appended {label} noctalia.css import to gtk.css") + else: + gtk_css.write_text(GTK_IMPORT + "\n") + print(f"Created {label} gtk.css with noctalia.css import") + return True + + async def apply_gtk3_colors(config_dir: Path): gtk3_dir = config_dir / "gtk-3.0" colors_file = gtk3_dir / "noctalia.css" gtk_css = gtk3_dir / "gtk.css" - - if not colors_file.exists(): - print(f"Error: noctalia.css not found at {colors_file}", file=sys.stderr) - return False - - if gtk_css.is_symlink(): - gtk_css.unlink() - elif gtk_css.exists(): - backup_name = f"gtk.css.backup.{int(os.path.getmtime(gtk_css))}" - gtk_css.rename(gtk3_dir / backup_name) - print(f"Backed up existing gtk.css to {backup_name}") - - gtk_css.symlink_to("noctalia.css") - print(f"Created symlink: {gtk_css} -> noctalia.css") - return True + return ensure_gtk_css_import(gtk_css, colors_file, "GTK3") async def apply_gtk4_colors(config_dir: Path): gtk4_dir = config_dir / "gtk-4.0" colors_file = gtk4_dir / "noctalia.css" gtk_css = gtk4_dir / "gtk.css" - gtk4_import = '@import url("noctalia.css");' - - if not colors_file.exists(): - print(f"Error: GTK4 noctalia.css not found at {colors_file}", file=sys.stderr) - return False - - gtk_css.write_text(gtk4_import) - print("Updated GTK4 CSS import") - return True + return ensure_gtk_css_import(gtk_css, colors_file, "GTK4") async def refresh_theme(): diff --git a/Scripts/python/src/theming/lib/__init__.py b/Scripts/python/src/theming/lib/__init__.py index f5e621368..644a55a94 100644 --- a/Scripts/python/src/theming/lib/__init__.py +++ b/Scripts/python/src/theming/lib/__init__.py @@ -18,8 +18,7 @@ from .palette import extract_palette from .quantizer import extract_source_color, source_color_to_rgb from .theme import generate_theme from .renderer import TemplateRenderer -from .scheme import expand_predefined_scheme -from .terminal import TerminalColors, TerminalGenerator +from .scheme import expand_predefined_scheme, inject_terminal_colors __all__ = [ # Color @@ -55,7 +54,5 @@ __all__ = [ "TemplateRenderer", # Scheme "expand_predefined_scheme", - # Terminal - "TerminalColors", - "TerminalGenerator", + "inject_terminal_colors", ] diff --git a/Scripts/python/src/theming/lib/renderer.py b/Scripts/python/src/theming/lib/renderer.py index 7c2995f61..8e82295d5 100644 --- a/Scripts/python/src/theming/lib/renderer.py +++ b/Scripts/python/src/theming/lib/renderer.py @@ -509,6 +509,13 @@ class TemplateRenderer: base = parts[0].strip() filters = [p.strip() for p in parts[1:]] + # Handle {{mode}} tag - resolves to current theme mode + if base == "mode": + result_str = self.default_mode + for filter_str in filters: + result_str = self._apply_string_or_color_filter(result_str, filter_str, expr) + return result_str + # Try scope resolution first resolved = self._resolve_from_scope(base, scope) if resolved is not None: diff --git a/Scripts/python/src/theming/lib/scheme.py b/Scripts/python/src/theming/lib/scheme.py index 0c6524e42..6ea0d625e 100644 --- a/Scripts/python/src/theming/lib/scheme.py +++ b/Scripts/python/src/theming/lib/scheme.py @@ -308,3 +308,44 @@ def expand_predefined_scheme(scheme_data: dict[str, str], mode: ThemeMode) -> di "background": background.to_hex(), "on_background": on_background.to_hex(), } + + +def inject_terminal_colors(result: dict[str, str], scheme_mode_data: dict) -> dict[str, str]: + """Flatten scheme's terminal section into template-ready color keys. + + Adds keys like terminal_foreground, terminal_normal_black, terminal_bright_red, etc. + so predefined terminal templates can reference them as + {{colors.terminal_foreground.default.hex_stripped}}. + + Args: + result: Expanded color palette dict to augment. + scheme_mode_data: Raw scheme JSON mode data (e.g., scheme_data["dark"]). + + Returns: + The same result dict with terminal_ keys added. + """ + terminal = scheme_mode_data.get("terminal") + if not terminal: + return result + + # Map of JSON keys to flattened key names + direct_keys = { + "foreground": "terminal_foreground", + "background": "terminal_background", + "cursor": "terminal_cursor", + "cursorText": "terminal_cursor_text", + "selectionFg": "terminal_selection_fg", + "selectionBg": "terminal_selection_bg", + } + + for json_key, flat_key in direct_keys.items(): + if json_key in terminal: + result[flat_key] = terminal[json_key] + + # ANSI normal/bright color groups + for group in ("normal", "bright"): + if group in terminal: + for name, hex_val in terminal[group].items(): + result[f"terminal_{group}_{name}"] = hex_val + + return result diff --git a/Scripts/python/src/theming/lib/terminal.py b/Scripts/python/src/theming/lib/terminal.py deleted file mode 100644 index eee368be5..000000000 --- a/Scripts/python/src/theming/lib/terminal.py +++ /dev/null @@ -1,320 +0,0 @@ -""" -Terminal theme generation for multiple terminal emulators. - -Generates native theme files from a unified terminal color schema. -Supports: foot, ghostty, kitty, alacritty, wezterm -""" - -from __future__ import annotations - -from dataclasses import dataclass - - -def darken_hex(color: str, percent: float) -> str: - """Darken a hex color by a percentage (0-100).""" - hex_color = color.lstrip("#") - r = int(hex_color[0:2], 16) - g = int(hex_color[2:4], 16) - b = int(hex_color[4:6], 16) - - factor = 1 - (percent / 100) - r = max(0, int(r * factor)) - g = max(0, int(g * factor)) - b = max(0, int(b * factor)) - - return f"#{r:02x}{g:02x}{b:02x}" - - -@dataclass -class TerminalColors: - """Terminal color scheme data.""" - - # Base colors (from JSON) - foreground: str - background: str - cursor: str - cursor_text: str - selection_fg: str - selection_bg: str - normal: dict[str, str] # black, red, green, yellow, blue, magenta, cyan, white - bright: dict[str, str] # same keys - - # WezTerm extended colors (always derived) - compose_cursor: str - scrollbar_thumb: str - split: str - visual_bell: str - indexed: dict[int, str] - tab_bar: dict - - # Kitty border colors - active_border: str - inactive_border: str - - @classmethod - def from_dict(cls, data: dict, scheme: dict) -> "TerminalColors": - """Create TerminalColors with auto-derived WezTerm extended colors. - - Args: - data: Terminal color data (foreground, background, cursor, normal, bright, etc.) - scheme: Scheme UI colors (mPrimary, mOnPrimary, mSecondary) - """ - # Base colors - foreground = data["foreground"] - background = data["background"] - cursor = data.get("cursor", foreground) - cursor_text = data.get("cursorText", background) - selection_fg = data.get("selectionFg", foreground) - selection_bg = data.get("selectionBg", "#585b70") - normal = data["normal"] - bright = data["bright"] - - # Scheme accent colors - m_primary = scheme.get("mPrimary", cursor) - m_on_primary = scheme.get("mOnPrimary", cursor_text) - m_secondary = scheme.get("mSecondary", normal["yellow"]) - m_surface_variant = scheme.get("mSurfaceVariant", selection_bg) - - return cls( - foreground=foreground, - background=background, - cursor=cursor, - cursor_text=cursor_text, - selection_fg=selection_fg, - selection_bg=selection_bg, - normal=normal, - bright=bright, - # Derived WezTerm colors - compose_cursor=cursor, - scrollbar_thumb=selection_bg, - split=bright["black"], - visual_bell=normal["black"], - indexed={16: m_secondary, 17: cursor}, - tab_bar={ - "background": darken_hex(background, 10), - "inactiveTabEdge": selection_bg, - "activeTab": {"bg": m_primary, "fg": m_on_primary}, - "inactiveTab": {"bg": darken_hex(background, 5), "fg": foreground}, - "inactiveTabHover": {"bg": background, "fg": foreground}, - "newTab": {"bg": selection_bg, "fg": foreground}, - "newTabHover": {"bg": bright["black"], "fg": foreground}, - }, - - # Kitty border colors - active_border=m_primary, - inactive_border=m_secondary - ) - - -# Color name to index mapping for ANSI colors -COLOR_ORDER = ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"] - - -class TerminalGenerator: - """Generate terminal themes in native formats.""" - - def __init__(self, colors: TerminalColors): - self.colors = colors - - def _strip_hash(self, color: str) -> str: - """Remove # prefix from hex color.""" - return color.lstrip("#") - - def _ensure_hash(self, color: str) -> str: - """Ensure # prefix on hex color.""" - return color if color.startswith("#") else f"#{color}" - - def generate_foot(self) -> str: - """Generate foot terminal theme (INI format, no # prefix).""" - c = self.colors - lines = ["[colors]"] - - # Primary colors - lines.append(f"foreground={self._strip_hash(c.foreground)}") - lines.append(f"background={self._strip_hash(c.background)}") - - # Normal colors (regular0-7) - for i, name in enumerate(COLOR_ORDER): - lines.append(f"regular{i}={self._strip_hash(c.normal[name])}") - - # Bright colors (bright0-7) - for i, name in enumerate(COLOR_ORDER): - lines.append(f"bright{i}={self._strip_hash(c.bright[name])}") - - # Selection - lines.append(f"selection-foreground={self._strip_hash(c.selection_fg)}") - lines.append(f"selection-background={self._strip_hash(c.selection_bg)}") - - # Cursor (format: bg fg) - lines.append( - f"cursor={self._strip_hash(c.cursor_text)} {self._strip_hash(c.cursor)}" - ) - - return "\n".join(lines) + "\n" - - def generate_ghostty(self) -> str: - """Generate ghostty theme (key=value with palette indices).""" - c = self.colors - lines = [] - - # Palette (0-7 normal, 8-15 bright) - for i, name in enumerate(COLOR_ORDER): - lines.append(f"palette = {i}={self._ensure_hash(c.normal[name])}") - for i, name in enumerate(COLOR_ORDER): - lines.append(f"palette = {i + 8}={self._ensure_hash(c.bright[name])}") - - # Primary colors - lines.append(f"background = {self._ensure_hash(c.background)}") - lines.append(f"foreground = {self._ensure_hash(c.foreground)}") - - # Cursor - lines.append(f"cursor-color = {self._ensure_hash(c.cursor)}") - lines.append(f"cursor-text = {self._ensure_hash(c.cursor_text)}") - - # Selection - lines.append(f"selection-background = {self._ensure_hash(c.selection_bg)}") - lines.append(f"selection-foreground = {self._ensure_hash(c.selection_fg)}") - - return "\n".join(lines) + "\n" - - def generate_kitty(self) -> str: - """Generate kitty theme (key value pairs with # prefix).""" - c = self.colors - lines = [] - - # Colors 0-15 - for i, name in enumerate(COLOR_ORDER): - lines.append(f"color{i} {self._ensure_hash(c.normal[name])}") - for i, name in enumerate(COLOR_ORDER): - lines.append(f"color{i + 8} {self._ensure_hash(c.bright[name])}") - - # Primary colors - lines.append(f"background {self._ensure_hash(c.background)}") - lines.append(f"selection_foreground {self._ensure_hash(c.cursor_text)}") - lines.append(f"cursor {self._ensure_hash(c.cursor)}") - lines.append(f"cursor_text_color {self._ensure_hash(c.cursor_text)}") - lines.append(f"foreground {self._ensure_hash(c.foreground)}") - lines.append(f"selection_background {self._ensure_hash(c.foreground)}") - lines.append(f"active_border_color {self._ensure_hash(c.active_border)}") - lines.append(f"inactive_border_color {self._ensure_hash(c.inactive_border)}") - - return "\n".join(lines) + "\n" - - def generate_alacritty(self) -> str: - """Generate alacritty theme (TOML format).""" - c = self.colors - lines = ["# Colors (Noctalia)", ""] - - # Bright colors - lines.append("[colors.bright]") - for name in sorted(COLOR_ORDER): - lines.append(f"{name} = '{self._ensure_hash(c.bright[name])}'") - lines.append("") - - # Cursor - lines.append("[colors.cursor]") - lines.append(f"cursor = '{self._ensure_hash(c.cursor)}'") - lines.append(f"text = '{self._ensure_hash(c.cursor_text)}'") - lines.append("") - - # Normal colors - lines.append("[colors.normal]") - for name in sorted(COLOR_ORDER): - lines.append(f"{name} = '{self._ensure_hash(c.normal[name])}'") - lines.append("") - - # Primary - lines.append("[colors.primary]") - lines.append(f"background = '{self._ensure_hash(c.background)}'") - lines.append(f"foreground = '{self._ensure_hash(c.foreground)}'") - lines.append("") - - # Selection - lines.append("[colors.selection]") - lines.append(f"background = '{self._ensure_hash(c.selection_bg)}'") - lines.append(f"text = '{self._ensure_hash(c.selection_fg)}'") - - return "\n".join(lines) + "\n" - - def generate_wezterm(self) -> str: - """Generate wezterm theme (full TOML with metadata).""" - c = self.colors - tb = c.tab_bar - lines = ["[colors]"] - - # Ansi colors array - lines.append("ansi = [") - for name in COLOR_ORDER: - lines.append(f' "{self._ensure_hash(c.normal[name])}",') - lines.append("]") - - lines.append(f'background = "{self._ensure_hash(c.background)}"') - - # Brights array - lines.append("brights = [") - for name in COLOR_ORDER: - lines.append(f' "{self._ensure_hash(c.bright[name])}",') - lines.append("]") - - # Extended colors - lines.append(f'compose_cursor = "{self._ensure_hash(c.compose_cursor)}"') - lines.append(f'cursor_bg = "{self._ensure_hash(c.cursor)}"') - lines.append(f'cursor_border = "{self._ensure_hash(c.cursor)}"') - lines.append(f'cursor_fg = "{self._ensure_hash(c.cursor_text)}"') - lines.append(f'foreground = "{self._ensure_hash(c.foreground)}"') - lines.append(f'scrollbar_thumb = "{self._ensure_hash(c.scrollbar_thumb)}"') - lines.append(f'selection_bg = "{self._ensure_hash(c.selection_bg)}"') - lines.append(f'selection_fg = "{self._ensure_hash(c.selection_fg)}"') - lines.append(f'split = "{self._ensure_hash(c.split)}"') - lines.append(f'visual_bell = "{self._ensure_hash(c.visual_bell)}"') - - # Indexed colors - lines.append("") - lines.append("[colors.indexed]") - for idx, color in sorted(c.indexed.items()): - lines.append(f'{idx} = "{self._ensure_hash(color)}"') - - # Tab bar - lines.append("") - lines.append("[colors.tab_bar]") - lines.append(f'background = "{self._ensure_hash(tb["background"])}"') - lines.append(f'inactive_tab_edge = "{self._ensure_hash(tb["inactiveTabEdge"])}"') - - for section, key in [ - ("activeTab", "active_tab"), - ("inactiveTab", "inactive_tab"), - ("inactiveTabHover", "inactive_tab_hover"), - ("newTab", "new_tab"), - ("newTabHover", "new_tab_hover"), - ]: - lines.append("") - lines.append(f"[colors.tab_bar.{key}]") - lines.append(f'bg_color = "{self._ensure_hash(tb[section]["bg"])}"') - lines.append(f'fg_color = "{self._ensure_hash(tb[section]["fg"])}"') - lines.append('intensity = "Normal"') - lines.append("italic = false") - lines.append("strikethrough = false") - lines.append('underline = "None"') - - # Metadata - lines.append("") - lines.append("[metadata]") - lines.append('author = "Noctalia"') - lines.append('name = "Noctalia"') - - return "\n".join(lines) + "\n" - - def generate(self, terminal_id: str) -> str: - """Generate theme for specified terminal.""" - generators = { - "foot": self.generate_foot, - "ghostty": self.generate_ghostty, - "kitty": self.generate_kitty, - "alacritty": self.generate_alacritty, - "wezterm": self.generate_wezterm, - } - - if terminal_id not in generators: - raise ValueError(f"Unknown terminal: {terminal_id}") - - return generators[terminal_id]() diff --git a/Scripts/python/src/theming/migrate-colorschemes.py b/Scripts/python/src/theming/migrate-colorschemes.py new file mode 100644 index 000000000..0f87043bb --- /dev/null +++ b/Scripts/python/src/theming/migrate-colorschemes.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import os +import json +import sys +import urllib.request +import urllib.parse +from pathlib import Path + +# Registry URL for color schemes +REGISTRY_URL = "https://raw.githubusercontent.com/noctalia-dev/noctalia-colorschemes/main/registry.json" +RAW_BASE_URL = "https://raw.githubusercontent.com/noctalia-dev/noctalia-colorschemes/main/" + +def is_valid_format(data): + """Check if the scheme data has the new terminal format.""" + for variant in ['dark', 'light']: + if variant in data: + v_data = data[variant] + if isinstance(v_data, dict) and 'terminal' in v_data: + term = v_data['terminal'] + if isinstance(term, dict) and 'normal' in term: + if isinstance(term['normal'], dict) and 'black' in term['normal']: + return True + return False + +def get_registry(): + """Fetch the remote registry to get correct paths for schemes.""" + try: + with urllib.request.urlopen(REGISTRY_URL) as response: + return json.loads(response.read().decode()) + except Exception as e: + print(f"Error fetching registry: {e}") + return None + +def migrate(config_dir): + colorschemes_dir = Path(config_dir) / "colorschemes" + if not colorschemes_dir.exists(): + return + + registry = get_registry() + if not registry: + return + + # Map name to path from registry + theme_map = {t['name']: t['path'] for t in registry.get('themes', [])} + + for scheme_dir in colorschemes_dir.iterdir(): + if not scheme_dir.is_dir(): + continue + + scheme_name = scheme_dir.name + json_file = scheme_dir / f"{scheme_name}.json" + + if not json_file.exists(): + continue + + try: + with open(json_file, 'r') as f: + data = json.load(f) + except Exception: + continue + + if not is_valid_format(data): + print(f"Scheme '{scheme_name}' has old format. Attempting to redownload...") + + # Use registry path if available, otherwise fallback to name + remote_path = theme_map.get(scheme_name, scheme_name) + + # Encode URL parts to handle spaces and special characters + encoded_path = urllib.parse.quote(remote_path) + encoded_name = urllib.parse.quote(scheme_name) + remote_url = f"{RAW_BASE_URL}{encoded_path}/{encoded_name}.json" + + try: + with urllib.request.urlopen(remote_url) as response: + new_data = json.loads(response.read().decode()) + with open(json_file, 'w') as f: + json.dump(new_data, f, indent=2) + + print(f"Successfully migrated '{scheme_name}'") + except Exception as e: + print(f"Failed to migrate '{scheme_name}': {e}") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: migrate-colorschemes.py ") + sys.exit(1) + + migrate(sys.argv[1]) diff --git a/Scripts/python/src/theming/template-processor.py b/Scripts/python/src/theming/template-processor.py index e2af253d2..f7bad695a 100644 --- a/Scripts/python/src/theming/template-processor.py +++ b/Scripts/python/src/theming/template-processor.py @@ -54,8 +54,8 @@ from lib import ( read_image, ImageReadError, extract_palette, generate_theme, TemplateRenderer, expand_predefined_scheme, extract_source_color, source_color_to_rgb, Color, - TerminalColors, TerminalGenerator ) +from lib.scheme import inject_terminal_colors def parse_args() -> argparse.Namespace: @@ -144,12 +144,6 @@ Examples: help='Theme mode to use for "default" in templates (default: dark)' ) - parser.add_argument( - '--terminal-output', - type=str, - help='JSON mapping of terminal IDs to output paths: {"foot": "/path/to/output", ...}' - ) - return parser.parse_args() @@ -188,9 +182,11 @@ def main() -> int: if mode in scheme_data: # Multi-mode format result[mode] = expand_predefined_scheme(scheme_data[mode], mode) + inject_terminal_colors(result[mode], scheme_data[mode]) elif "mPrimary" in scheme_data: # Single-mode format - use same colors for requested mode result[mode] = expand_predefined_scheme(scheme_data, mode) + inject_terminal_colors(result[mode], scheme_data) else: print(f"Error: Invalid scheme format - missing '{mode}' or 'mPrimary'", file=sys.stderr) return 1 @@ -344,52 +340,6 @@ def main() -> int: else: renderer.process_config_file(args.config) - # Process terminal output if specified - if args.terminal_output and args.scheme: - try: - terminal_outputs = json.loads(args.terminal_output) - except json.JSONDecodeError as e: - print(f"Error parsing --terminal-output JSON: {e}", file=sys.stderr) - return 1 - - # Load scheme to check for terminal section - with open(args.scheme, 'r') as f: - scheme_data = json.load(f) - - # Determine which mode to use for terminal colors - mode = args.default_mode - - # Check if scheme has terminal colors - mode_data = scheme_data.get(mode, scheme_data) - if "terminal" not in mode_data: - print(f"Warning: Scheme has no 'terminal' section for mode '{mode}'", file=sys.stderr) - return 0 - - try: - # Extract scheme UI colors for derivation (mPrimary, mOnPrimary, mSecondary) - scheme_colors = { - "mPrimary": mode_data.get("mPrimary"), - "mOnPrimary": mode_data.get("mOnPrimary"), - "mSecondary": mode_data.get("mSecondary"), - } - terminal_colors = TerminalColors.from_dict(mode_data["terminal"], scheme_colors) - generator = TerminalGenerator(terminal_colors) - - for terminal_id, output_path in terminal_outputs.items(): - try: - content = generator.generate(terminal_id) - output_file = Path(output_path).expanduser() - output_file.parent.mkdir(parents=True, exist_ok=True) - output_file.write_text(content) - except ValueError as e: - print(f"Error generating {terminal_id}: {e}", file=sys.stderr) - except IOError as e: - print(f"Error writing {output_path}: {e}", file=sys.stderr) - - except KeyError as e: - print(f"Error: Missing required terminal color: {e}", file=sys.stderr) - return 1 - return 0 diff --git a/Services/Compositor/CompositorService.qml b/Services/Compositor/CompositorService.qml index 22e0048f4..eaff86edc 100644 --- a/Services/Compositor/CompositorService.qml +++ b/Services/Compositor/CompositorService.qml @@ -375,6 +375,13 @@ Singleton { } } + // Scrollable workspace content (Niri) + function scrollWorkspaceContent(direction) { + if (backend && backend.scrollWorkspaceContent) { + backend.scrollWorkspaceContent(direction); + } + } + // Get current workspace function getCurrentWorkspace() { for (var i = 0; i < workspaces.count; i++) { @@ -488,6 +495,16 @@ Singleton { }); } + function userspaceReboot() { + Logger.i("Compositor", "Userspace reboot requested"); + if (executeSessionAction("userspaceReboot")) + return; + + HooksService.executeSessionHook("userspaceReboot", () => { + Quickshell.execDetached(["sh", "-c", "systemctl soft-reboot"]); + }); + } + function rebootToUefi() { Logger.i("Compositor", "Reboot to UEFI firmware requested requested"); if (executeSessionAction("rebootToUefi")) @@ -507,6 +524,15 @@ Singleton { } } + function turnOnMonitors() { + Logger.i("Compositor", "Turn on monitors requested"); + if (backend && backend.turnOnMonitors) { + backend.turnOnMonitors(); + } else { + Logger.w("Compositor", "No backend available for turnOnMonitors"); + } + } + function suspend() { Logger.i("Compositor", "Suspend requested"); if (executeSessionAction("suspend")) diff --git a/Services/Compositor/HyprlandService.qml b/Services/Compositor/HyprlandService.qml index 89edd83cb..6ccfa198a 100644 --- a/Services/Compositor/HyprlandService.qml +++ b/Services/Compositor/HyprlandService.qml @@ -399,6 +399,12 @@ Item { const layoutNameStart = beforeParenthesis.lastIndexOf(',') + 1; const layoutName = ev.substring(layoutNameStart); + // Ignore bogus "error" layout reported by virtual keyboards (e.g. wtype) + if (layoutName.toLowerCase() === "error") { + Logger.d("HyprlandService", "Ignoring bogus 'error' layout from activelayout event"); + return; + } + KeyboardLayoutService.setCurrentLayout(layoutName); Logger.d("HyprlandService", "Keyboard layout switched:", layoutName); } catch (e) { @@ -490,6 +496,14 @@ Item { } } + function turnOnMonitors() { + try { + Quickshell.execDetached(["hyprctl", "dispatch", "dpms", "on"]); + } catch (e) { + Logger.e("HyprlandService", "Failed to turn on monitors:", e); + } + } + function logout() { try { Quickshell.execDetached(["hyprctl", "dispatch", "exit"]); diff --git a/Services/Compositor/LabwcService.qml b/Services/Compositor/LabwcService.qml index ec380c281..8959bab6b 100644 --- a/Services/Compositor/LabwcService.qml +++ b/Services/Compositor/LabwcService.qml @@ -276,6 +276,14 @@ Item { } } + function turnOnMonitors() { + try { + Quickshell.execDetached(["wlr-randr", "--on"]); + } catch (e) { + Logger.e("LabwcService", "Failed to turn on monitors:", e); + } + } + function logout() { stopWorkspaceHelper(); try { diff --git a/Services/Compositor/MangoService.qml b/Services/Compositor/MangoService.qml index 9c8a92e5e..f62c25693 100644 --- a/Services/Compositor/MangoService.qml +++ b/Services/Compositor/MangoService.qml @@ -671,10 +671,24 @@ Item { } function turnOffMonitors() { - try { - Quickshell.execDetached(["wlr-randr", "--off"]); - } catch (e) { - Logger.e("MangoService", "Failed to turn off monitors:", e); + const screens = Quickshell.screens; + const cmds = []; + for (let i = 0; i < screens.length; i++) { + cmds.push("mmsg -s -d disable_monitor," + screens[i].name); + } + if (cmds.length > 0) { + Quickshell.execDetached(["sh", "-c", cmds.join(" && ")]); + } + } + + function turnOnMonitors() { + const screens = Quickshell.screens; + const cmds = []; + for (let i = 0; i < screens.length; i++) { + cmds.push("mmsg -s -d enable_monitor," + screens[i].name); + } + if (cmds.length > 0) { + Quickshell.execDetached(["sh", "-c", cmds.join(" && ")]); } } diff --git a/Services/Compositor/NiriService.qml b/Services/Compositor/NiriService.qml index 7e9210242..f4bbaaf94 100644 --- a/Services/Compositor/NiriService.qml +++ b/Services/Compositor/NiriService.qml @@ -456,6 +456,15 @@ Item { } } + function scrollWorkspaceContent(direction) { + try { + var action = direction < 0 ? "focus-column-left" : "focus-column-right"; + Quickshell.execDetached(["niri", "msg", "action", action]); + } catch (e) { + Logger.e("NiriService", "Failed to scroll workspace content:", e); + } + } + function focusWindow(window) { try { Quickshell.execDetached(["niri", "msg", "action", "focus-window", "--id", window.id.toString()]); @@ -480,6 +489,14 @@ Item { } } + function turnOnMonitors() { + try { + Quickshell.execDetached(["niri", "msg", "action", "power-on-monitors"]); + } catch (e) { + Logger.e("NiriService", "Failed to turn on monitors:", e); + } + } + function logout() { try { Quickshell.execDetached(["niri", "msg", "action", "quit", "--skip-confirmation"]); diff --git a/Services/Compositor/SwayService.qml b/Services/Compositor/SwayService.qml index 2fbb44615..397c114ff 100644 --- a/Services/Compositor/SwayService.qml +++ b/Services/Compositor/SwayService.qml @@ -46,7 +46,6 @@ Item { return; try { I3.refreshWorkspaces(); - I3.dispatch('(["input"])'); Qt.callLater(() => { safeUpdateWorkspaces(); queryWindowWorkspaces(); @@ -104,8 +103,8 @@ Item { } } - // If this is a container with app_id or class (i.e., a window) - if (node.type === "con" && (node.app_id || node.window_properties)) { + // If this is a regular or floating container with app_id/class (i.e., a window) + if ((node.type === "con" || node.type === "floating_con") && (node.app_id || node.window_properties)) { const appId = node.app_id || (node.window_properties ? node.window_properties.class : null); const title = node.name || ""; const id = node.id; @@ -235,16 +234,6 @@ Item { } } - Timer { - id: keyboardLayoutUpdateTimer - interval: 1000 - running: true - repeat: true - onTriggered: { - queryKeyboardLayout(); - } - } - function queryKeyboardLayout() { swayInputsProcess.running = true; } @@ -459,22 +448,17 @@ Item { function handleInputEvent(ev) { try { - let beforeParenthesis; - const parenthesisPos = ev.lastIndexOf('('); - - if (parenthesisPos === -1) { - beforeParenthesis = ev; - } else { - beforeParenthesis = ev.substring(0, parenthesisPos); + const eventData = JSON.parse(ev); + if (eventData.change == "xkb_layout" && eventData.input != null) { + const input = eventData.input; + if (input.type == "keyboard" && input.xkb_active_layout_name != null) { + const layoutName = input.xkb_active_layout_name; + KeyboardLayoutService.setCurrentLayout(layoutName); + Logger.d("SwayService", "Keyboard layout switched:", layoutName); + } } - - const layoutNameStart = beforeParenthesis.lastIndexOf(',') + 1; - const layoutName = ev.substring(layoutNameStart); - - KeyboardLayoutService.setCurrentLayout(layoutName); - Logger.d("HyprlandService", "Keyboard layout switched:", layoutName); } catch (e) { - Logger.e("HyprlandService", "Error handling activelayout:", e); + Logger.e("SwayService", "Error handling input event:", e); } } @@ -516,15 +500,13 @@ Item { if (event.type === "output") { Qt.callLater(queryDisplayScales); } + } + } - if (event.type == "get_inputs") { - handleInputEvent(event.data); - } - - // Query window workspaces on relevant events - if (event.type === "window" || event.type === "workspace") { - Qt.callLater(queryWindowWorkspaces); - } + I3IpcListener { + subscriptions: ["input"] + onIpcEvent: function (event) { + handleInputEvent(event.data); } } @@ -561,6 +543,14 @@ Item { } } + function turnOnMonitors() { + try { + Quickshell.execDetached([msgCommand, "output", "*", "dpms", "on"]); + } catch (e) { + Logger.e("SwayService", "Failed to turn on monitors:", e); + } + } + function logout() { try { Quickshell.execDetached([msgCommand, "exit"]); diff --git a/Services/Control/HooksService.qml b/Services/Control/HooksService.qml index 39f7d62d1..872662a84 100644 --- a/Services/Control/HooksService.qml +++ b/Services/Control/HooksService.qml @@ -159,7 +159,8 @@ Singleton { } try { - Quickshell.execDetached(["sh", "-lc", script]); + // Pass "lock" as $1 via shell arguments so the script receives it + Quickshell.execDetached(["sh", "-lc", script, "lock-hook", "lock"]); Logger.d("HooksService", `Executed screen lock hook: ${script}`); } catch (e) { Logger.e("HooksService", `Failed to execute screen lock hook: ${e}`); @@ -178,7 +179,8 @@ Singleton { } try { - Quickshell.execDetached(["sh", "-lc", script]); + // Pass "unlock" as $1 via shell arguments so the script receives it + Quickshell.execDetached(["sh", "-lc", script, "unlock-hook", "unlock"]); Logger.d("HooksService", `Executed screen unlock hook: ${script}`); } catch (e) { Logger.e("HooksService", `Failed to execute screen unlock hook: ${e}`); diff --git a/Services/Control/IPCService.qml b/Services/Control/IPCService.qml index b20836a50..91bad8c6d 100644 --- a/Services/Control/IPCService.qml +++ b/Services/Control/IPCService.qml @@ -10,6 +10,7 @@ import qs.Commons import qs.Modules.Panels.Settings import qs.Services.Compositor import qs.Services.Hardware +import qs.Services.Location import qs.Services.Media import qs.Services.Networking import qs.Services.Noctalia @@ -97,7 +98,8 @@ Singleton { "notifications": SettingsPanel.Tab.Notifications, "plugins": SettingsPanel.Tab.Plugins, "sessionmenu": SettingsPanel.Tab.SessionMenu, - "systemmonitor": SettingsPanel.Tab.SystemMonitor, + "system": SettingsPanel.Tab.System, + "systemmonitor": SettingsPanel.Tab.System, "userinterface": SettingsPanel.Tab.UserInterface, "wallpaper": SettingsPanel.Tab.Wallpaper }) @@ -374,7 +376,7 @@ Singleton { function lock() { // Only lock if not already locked (prevents the red screen issue) if (!PanelService.lockScreen.active) { - PanelService.lockScreen.active = true; + CompositorService.lock(); } } } @@ -403,6 +405,16 @@ Singleton { } } + IpcHandler { + target: "monitors" + function on() { + CompositorService.turnOnMonitors(); + } + function off() { + CompositorService.turnOffMonitors(); + } + } + IpcHandler { target: "darkMode" function toggle() { @@ -509,11 +521,16 @@ Singleton { } function lock() { - CompositorService.lock(); + if (!PanelService.lockScreen.active) { + CompositorService.lock(); + } } function lockAndSuspend() { - CompositorService.lockAndSuspend(); + // Only lock and suspend if not already locked + if (!PanelService.lockScreen.active) { + CompositorService.lockAndSuspend(); + } } } @@ -551,9 +568,32 @@ Singleton { } } - function random() { + function random(screen: string) { if (Settings.data.wallpaper.enabled) { - WallpaperService.setRandomWallpaper(); + if (!screen || screen === "all" || screen.trim().length === 0) { + screen = undefined; + } + WallpaperService.setRandomWallpaper(screen); + } + } + + function get(screen: string): string { + if (screen === "all" || screen === "") { + if (Quickshell.screens.length > 1) { + var map = {}; + Quickshell.screens.forEach(s => { + map[s.name] = WallpaperService.currentWallpapers[s.name] ?? ""; + }); + return JSON.stringify(map); + } + return WallpaperService.currentWallpapers[Quickshell.screens[0].name] ?? ""; + } else { + var found = Quickshell.screens.find(s => s.name === screen); + if (!found) { + Logger.w("IPC", "wallpaper get: unknown screen: " + screen); + return ""; + } + return WallpaperService.currentWallpapers[screen] ?? ""; } } @@ -564,6 +604,10 @@ Singleton { WallpaperService.changeWallpaper(path, screen); } + function refresh() { + WallpaperService.refreshWallpapersList(); + } + function toggleAutomation() { Settings.data.wallpaper.automationEnabled = !Settings.data.wallpaper.automationEnabled; } @@ -615,6 +659,15 @@ Singleton { bluetoothPanel?.toggle(null, "Bluetooth"); }); } + function toggleAutoConnect() { + Settings.data.network.bluetoothAutoConnect = !Settings.data.network.bluetoothAutoConnect; + } + function enableAutoConnect() { + Settings.data.network.bluetoothAutoConnect = true; + } + function disableAutoConnect() { + Settings.data.network.bluetoothAutoConnect = false; + } } IpcHandler { @@ -788,6 +841,7 @@ Singleton { } function set(name: string) { Settings.data.location.name = name; + LocationService.update(); } } diff --git a/Services/Hardware/BatteryService.qml b/Services/Hardware/BatteryService.qml index 93f57c54a..f61535416 100644 --- a/Services/Hardware/BatteryService.qml +++ b/Services/Hardware/BatteryService.qml @@ -189,7 +189,7 @@ Singleton { // Logger.e("BatteryDebug", "Available Battery count: " + laptopBatteries.length); // can be useful for debugging if (laptopBatteries.length > 1 && device.nativePath) { if (device.nativePath === "DisplayDevice") { - return "All batteries (combined)"; // TODO: i18n + return I18n.tr("battery.all-batteries"); } var match = device.nativePath.match(/(\d+)$/); if (match) { diff --git a/Services/Location/Calendar/Khal.qml b/Services/Location/Calendar/Khal.qml index 3addbf519..2e14e233a 100644 --- a/Services/Location/Calendar/Khal.qml +++ b/Services/Location/Calendar/Khal.qml @@ -131,21 +131,37 @@ Singleton { function parseEvents(text) { const result = []; + const duplicates = new Set(); for (const line of text.split("\n")) { if (!line.trim()) continue; const dayEvents = JSON.parse(line); for (const event of dayEvents) { - result.push({ - uid: event.uid, - calendar: event.calendar, - summary: event.title, - start: parseTimestamp(event["start-long-full"]), - end: parseTimestamp(event["end-long-full"]), - location: event.location, - description: event.description - }); + if (event["repeat-pattern"] !== "") { + // if there is a repeat pattern, the event must be included each time + result.push({ + uid: event.uid, + calendar: event.calendar, + summary: event.title, + start: parseTimestamp(event["start-long-full"]), + end: parseTimestamp(event["end-long-full"]), + location: event.location, + description: event.description + }); + } else if (!duplicates.has(event.uid)) { + // in any other cases, we must remove duplicates using the uid of the event + result.push({ + uid: event.uid, + calendar: event.calendar, + summary: event.title, + start: parseTimestamp(event["start-long-full"]), + end: parseTimestamp(event["end-long-full"]), + location: event.location, + description: event.description + }); + duplicates.add(event.uid); + } } } diff --git a/Services/Location/LocationService.qml b/Services/Location/LocationService.qml index d655d6997..d845800bc 100644 --- a/Services/Location/LocationService.qml +++ b/Services/Location/LocationService.qml @@ -142,6 +142,11 @@ Singleton { isFetchingWeather = false; Logger.i("Location", "Coordinates ready"); + + if (locationChanged) { + adapter.weatherLastFetch = 0; + updateWeatherData(); + } }, errorCallback); } diff --git a/Services/Media/MediaService.qml b/Services/Media/MediaService.qml index cd8c7e853..e30762d62 100644 --- a/Services/Media/MediaService.qml +++ b/Services/Media/MediaService.qml @@ -63,11 +63,13 @@ Singleton { let specificPlayers = []; let genericPlayers = []; for (var i = 0; i < allPlayers.length; i++) { + if (!allPlayers[i]) + continue; const identity = String(allPlayers[i].identity || "").toLowerCase(); - const name = String(allPlayers[i].name || "").toLowerCase(); + const dbusName = String(allPlayers[i].dbusName || "").toLowerCase(); const match = blacklist.find(b => { const s = String(b || "").toLowerCase(); - return s && (identity.includes(s) || name.includes(s)); + return s && (identity.includes(s) || dbusName.includes(s)); }); if (match) continue; diff --git a/Services/Media/CavaService.qml b/Services/Media/SpectrumService.qml similarity index 87% rename from Services/Media/CavaService.qml rename to Services/Media/SpectrumService.qml index bc0f4c68b..00972a9e4 100644 --- a/Services/Media/CavaService.qml +++ b/Services/Media/SpectrumService.qml @@ -14,14 +14,14 @@ Singleton { function registerComponent(componentId) { root.registeredComponents[componentId] = true; root.registeredComponents = Object.assign({}, root.registeredComponents); - Logger.d("Cava", "Component registered:", componentId, "- total:", root.registeredCount); + Logger.d("Spectrum", "Component registered:", componentId, "- total:", root.registeredCount); } // Unregister a component when it no longer needs audio data. function unregisterComponent(componentId) { delete root.registeredComponents[componentId]; root.registeredComponents = Object.assign({}, root.registeredComponents); - Logger.d("Cava", "Component unregistered:", componentId, "- total:", root.registeredCount); + Logger.d("Spectrum", "Component unregistered:", componentId, "- total:", root.registeredCount); } // Check if a component is registered @@ -57,7 +57,7 @@ Singleton { property var config: ({ "general": { "bars": barsCount, - "framerate": Settings.data.audio.cavaFrameRate, + "framerate": Settings.data.audio.spectrumFrameRate, "autosens": 1, "sensitivity": 100, "lower_cutoff_freq": 50, @@ -96,7 +96,7 @@ Singleton { repeat: false onTriggered: { if (root.shouldRun && !process.running) { - Logger.w("Cava", "Restarting after crash..."); + Logger.w("Spectrum", "Restarting after crash..."); process.running = true; } } @@ -107,7 +107,7 @@ Singleton { stdinEnabled: true command: ["cava", "-p", "/dev/stdin"] onRunningChanged: { - Logger.d("Cava", "Process running:", running); + Logger.d("Spectrum", "Process running:", running); } onExited: { stdinEnabled = true; @@ -115,18 +115,18 @@ Singleton { if (root.shouldRun) { root._crashCount++; if (root._crashCount <= root._maxCrashes) { - Logger.w("Cava", "Process exited unexpectedly, restarting in 2s... (attempt " + root._crashCount + "/" + root._maxCrashes + ")"); + Logger.w("Spectrum", "Process exited unexpectedly, restarting in 2s... (attempt " + root._crashCount + "/" + root._maxCrashes + ")"); restartTimer.start(); } else { - Logger.e("Cava", "Process crashed too many times (" + root._maxCrashes + "), giving up"); + Logger.e("Spectrum", "Process crashed too many times (" + root._maxCrashes + "), giving up"); } } else { - Logger.d("Cava", "Process exited (no longer needed)"); + Logger.d("Spectrum", "Process exited (no longer needed)"); root._crashCount = 0; } } onStarted: { - Logger.d("Cava", "Process started"); + Logger.d("Spectrum", "Process started"); for (const k in config) { if (typeof config[k] !== "object") { write(k + "=" + config[k] + "\n"); @@ -184,7 +184,7 @@ Singleton { root.isIdle = true; // Set all values to 0 one final time root.values = Array(root.barsCount).fill(0); - Logger.d("Cava", "Idle detected - stopped rendering"); + Logger.d("Spectrum", "Idle detected - stopped rendering"); } // Don't update values while idle return; @@ -194,7 +194,7 @@ Singleton { root.idleFrameCount = 0; if (root.isIdle) { root.isIdle = false; - Logger.d("Cava", "Audio detected - resumed rendering"); + Logger.d("Spectrum", "Audio detected - resumed rendering"); } } @@ -208,7 +208,7 @@ Singleton { stderr: StdioCollector { onStreamFinished: { if (text.trim()) { - Logger.w("Cava", "Error", text); + Logger.w("Spectrum", "Error", text); } } } diff --git a/Services/Networking/BluetoothService.qml b/Services/Networking/BluetoothService.qml index 875baae33..819d966af 100644 --- a/Services/Networking/BluetoothService.qml +++ b/Services/Networking/BluetoothService.qml @@ -64,15 +64,83 @@ Singleton { property int connectAttempts: 5 property int connectRetryIntervalMs: 2000 - // Internal: temporarily pause discovery during pair/connect to reduce HCI churn + // Internal variables property bool _discoveryWasRunning: false property bool _ctlInit: false + property var _autoConnectQueue: [] + + // Persistent cache for per-device auto-connect toggle + property string cacheFile: Settings.cacheDir + "bluetooth_devices.json" + + FileView { + id: cacheFileView + path: root.cacheFile + printErrors: false + + JsonAdapter { + id: cacheAdapter + property var autoConnectSettings: ({}) + } + } + + function getDeviceAutoConnect(mac) { + if (!mac || !cacheAdapter.autoConnectSettings) { + return false; + } + const settings = cacheAdapter.autoConnectSettings[mac]; + return settings ? !!settings.autoConnect : false; + } + + function setDeviceAutoConnect(device, enabled) { + if (!device || !device.address) { + return; + } + const mac = device.address; + let settings = cacheAdapter.autoConnectSettings || ({}); + if (enabled) { + settings[mac] = { + autoConnect: true, + deviceName: device.name || device.deviceName || "" + }; + } else { + delete settings[mac]; + } + cacheAdapter.autoConnectSettings = settings; + cacheFileView.writeAdapter(); + } + + Connections { + target: Settings.data.network + function onBluetoothAutoConnectChanged() { + if (Settings.data.network.bluetoothAutoConnect && adapter && adapter.enabled) { + autoConnectTimer.restart(); + } else { + autoConnectTimer.stop(); + } + } + } Timer { - id: initDelayTimer - interval: 3000 - running: true + id: autoConnectTimer + interval: 1500 repeat: false + onTriggered: root.attemptAutoConnect() + } + + Timer { + id: autoConnectStepTimer + interval: 500 + repeat: false + onTriggered: { + var device = root._autoConnectQueue.shift(); + if (device && device.paired && !device.connected && !device.blocked) { + Logger.i("Bluetooth", "Auto-connecting to:", device.name || device.deviceName); + connectDeviceWithTrust(device); + } + if (root._autoConnectQueue.length > 0) { + autoConnectStepTimer.restart(); + } + } } function init() { @@ -86,6 +154,8 @@ Singleton { Quickshell.execDetached(["rfkill", "block", "wifi"]); Quickshell.execDetached(["rfkill", "block", "bluetooth"]); } + // Auto-connect on startup + autoConnectTimer.restart(); } // Handle system wakeup to force-poll and ensure state is up-to-date @@ -106,6 +176,11 @@ Singleton { } checkAirplaneMode.running = true; } + function onEnabledChanged() { + if (adapter && adapter.enabled && Settings.data.network.bluetoothAutoConnect) { + autoConnectTimer.restart(); + } + } } onAdapterChanged: { @@ -189,11 +264,13 @@ Singleton { } // Periodic state polling + readonly property bool _lockScreenActive: PanelService.lockScreen?.active ?? false + Timer { id: ctlPollTimer interval: adapter ? ctlPollMs : 2000 repeat: true - running: adapter || ProgramCheckerService.bluetoothctlAvailable + running: (adapter || ProgramCheckerService.bluetoothctlAvailable) && !_lockScreenActive onTriggered: { pollCtlState(); var targetInterval = adapter ? ctlPollMs : 2000; @@ -212,7 +289,7 @@ Singleton { } function pollCtlState() { - if (!adapter && !ProgramCheckerService.bluetoothctlAvailable) { + if (!adapter || !ProgramCheckerService.bluetoothctlAvailable) { return; } if (ctlShowProcess.running) { @@ -555,7 +632,7 @@ Singleton { const intervalMs = Math.max(500, Number(root.connectRetryIntervalMs) | 0); const intervalSec = Math.max(1, Math.round(intervalMs / 1000)); - // Pause discovery during pair/connect to avoid interference + // Temporarily pause discovery during pair/connect to reduce HCI churn root._discoveryWasRunning = root.scanningActive; if (root.scanningActive) { root.setScanActive(false); @@ -597,6 +674,18 @@ Singleton { forgetDevice(device); } + function attemptAutoConnect() { + if (airplaneModeEnabled || !adapter || !adapter.enabled || !Settings.data.network.bluetoothAutoConnect) { + return; + } + + _autoConnectQueue = adapter.devices.values.filter(dev => dev && dev.paired && !dev.connected && !dev.blocked && getDeviceAutoConnect(dev.address) === true); + + if (root._autoConnectQueue.length > 0) { + autoConnectStepTimer.restart(); + } + } + function connectDeviceWithTrust(device) { if (!device) { return; diff --git a/Services/Noctalia/GitHubService.qml b/Services/Noctalia/GitHubService.qml index fb78da5cf..d23a4d20a 100644 --- a/Services/Noctalia/GitHubService.qml +++ b/Services/Noctalia/GitHubService.qml @@ -17,6 +17,7 @@ Singleton { // Public properties for easy access property string latestVersion: I18n.tr("common.unknown") + property string latestQSVersion: I18n.tr("common.unknown") property var contributors: [] // Avatar caching properties (simplified - uses ImageCacheService) @@ -51,6 +52,7 @@ Singleton { id: adapter property string version: I18n.tr("common.unknown") + property string qsVersion: I18n.tr("common.unknown") property var contributors: [] property real timestamp: 0 } @@ -73,12 +75,15 @@ Singleton { needsRefetch = true; Logger.i("GitHub", "Cache expired or missing, scheduling fetch (update frequency:", Math.round(githubUpdateFrequency / 60), "minutes)"); } else { - Logger.i("GitHub", "Cache is fresh, using cached data (age:", Math.round((now - data.timestamp) / 60), "minutes)"); + Logger.i("GitHub", "Cache is fresh, using cached data (age:", Math.round((now - data.timestamp) / 60) + " minutes)"); } if (data.version) { root.latestVersion = data.version; } + if (data.qsVersion) { + root.latestQSVersion = data.qsVersion; + } if (data.contributors && data.contributors.length > 0) { root.contributors = data.contributors; Logger.d("GitHub", "Loaded", data.contributors.length, "contributors from cache"); @@ -98,6 +103,7 @@ Singleton { isFetchingData = true; versionProcess.running = true; + qsVersionProcess.running = true; contributorsProcess.running = true; } @@ -105,7 +111,7 @@ Singleton { function saveData() { data.timestamp = Time.timestamp; Logger.d("GitHub", "Saving data to cache file:", githubDataFile, "with timestamp:", data.timestamp); - Logger.d("GitHub", "Data to save - version:", data.version, "contributors:", data.contributors.length); + Logger.d("GitHub", "Data to save - version:", data.version, "qsVersion:", data.qsVersion, "contributors:", data.contributors.length); // Ensure cache directory exists Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]); @@ -122,12 +128,12 @@ Singleton { // -------------------------------- function checkAndSaveData() { // Only save when all processes are finished - if (!versionProcess.running && !contributorsProcess.running) { + if (!versionProcess.running && !qsVersionProcess.running && !contributorsProcess.running) { root.isFetchingData = false; // Check results - var anySucceeded = versionProcess.fetchSucceeded || contributorsProcess.fetchSucceeded; - var wasRateLimited = versionProcess.wasRateLimited || contributorsProcess.wasRateLimited; + var anySucceeded = versionProcess.fetchSucceeded || qsVersionProcess.fetchSucceeded || contributorsProcess.fetchSucceeded; + var wasRateLimited = versionProcess.wasRateLimited || qsVersionProcess.wasRateLimited || contributorsProcess.wasRateLimited; if (anySucceeded) { root.saveData(); @@ -142,6 +148,8 @@ Singleton { // Reset fetch flags for next time versionProcess.fetchSucceeded = false; versionProcess.wasRateLimited = false; + qsVersionProcess.fetchSucceeded = false; + qsVersionProcess.wasRateLimited = false; contributorsProcess.fetchSucceeded = false; contributorsProcess.wasRateLimited = false; } @@ -150,6 +158,7 @@ Singleton { // -------------------------------- function resetCache() { data.version = I18n.tr("common.unknown"); + data.qsVersion = I18n.tr("common.unknown"); data.contributors = []; data.timestamp = 0; @@ -239,7 +248,46 @@ Singleton { Logger.e("GitHub", "Failed to parse version response:", e); } - // Check if both processes are done + // Check if all processes are done + checkAndSaveData(); + } + } + } + + Process { + id: qsVersionProcess + + property bool fetchSucceeded: false + property bool wasRateLimited: false + + command: ["curl", "-s", "https://api.github.com/repos/noctalia-dev/noctalia-qs/releases/latest"] + + stdout: StdioCollector { + onStreamFinished: { + try { + const response = text; + if (response && response.trim()) { + const data = JSON.parse(response); + if (data.tag_name) { + const version = data.tag_name; + root.data.qsVersion = version; + root.latestQSVersion = version; + qsVersionProcess.fetchSucceeded = true; + Logger.d("GitHub", "Latest QS version fetched:", version); + } else if (data.message) { + // Check if it's a rate limit error + if (data.message.includes("rate limit")) { + qsVersionProcess.wasRateLimited = true; + } else { + Logger.w("GitHub", "QS Version API error:", data.message); + } + } + } + } catch (e) { + Logger.e("GitHub", "Failed to parse QS version response:", e); + } + + // Check if all processes are done checkAndSaveData(); } } @@ -280,7 +328,7 @@ Singleton { Logger.e("GitHub", "Failed to parse contributors response:", e); } - // Check if both processes are done + // Check if all processes are done checkAndSaveData(); } } diff --git a/Services/Noctalia/PluginService.qml b/Services/Noctalia/PluginService.qml index fc812e11d..d3a3a9198 100644 --- a/Services/Noctalia/PluginService.qml +++ b/Services/Noctalia/PluginService.qml @@ -441,8 +441,8 @@ Singleton { // Use git sparse-checkout to clone only the plugin subfolder // GIT_TERMINAL_PROMPT=0 prevents hanging on private repos that need auth // Note: We download from the original pluginId folder in the repo, but save to compositeKey folder - var downloadCmd = "temp_dir=$(mktemp -d) && GIT_TERMINAL_PROMPT=0 git clone --filter=blob:none --sparse --depth=1 --quiet '" + repoUrl + "' \"$temp_dir\" 2>/dev/null && cd \"$temp_dir\" && git sparse-checkout set '" + pluginId + "' 2>/dev/null && mkdir -p '" + pluginDir + "' && cp -r \"$temp_dir/" + pluginId + "/.\" '" + pluginDir - + "/'; exit_code=$?; rm -rf \"$temp_dir\"; exit $exit_code"; + var downloadCmd = "temp_dir=$(mktemp -d) && GIT_TERMINAL_PROMPT=0 git clone --filter=blob:none --sparse --depth=1 --quiet '" + repoUrl + "' \"$temp_dir\" 2>/dev/null && cd \"$temp_dir\" && git sparse-checkout set '" + pluginId + "' 2>/dev/null && mkdir -p '" + pluginDir + "' && rm -f \"$temp_dir/" + pluginId + "/settings.json\" && cp -r \"$temp_dir/" + pluginId + + "/.\" '" + pluginDir + "/'; exit_code=$?; rm -rf \"$temp_dir\"; exit $exit_code"; // Mark as installing var newInstalling = Object.assign({}, root.installingPlugins); diff --git a/Services/Noctalia/UpdateService.qml b/Services/Noctalia/UpdateService.qml index a8f9551c5..012a4b7bb 100644 --- a/Services/Noctalia/UpdateService.qml +++ b/Services/Noctalia/UpdateService.qml @@ -11,7 +11,7 @@ Singleton { id: root // Version properties - readonly property string baseVersion: "4.5.1" + readonly property string baseVersion: "4.6.7" readonly property bool isDevelopment: true readonly property string developmentSuffix: "-git" readonly property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + developmentSuffix}` diff --git a/Services/Power/IdleService.qml b/Services/Power/IdleService.qml index cdbb851a0..16aaee451 100644 --- a/Services/Power/IdleService.qml +++ b/Services/Power/IdleService.qml @@ -41,6 +41,8 @@ Singleton { property var _suspendMonitor: null property var _heartbeatMonitor: null property var _customMonitors: ({}) + property var _queuedStages: [] + property bool _screenOffActive: false // Signals for external listeners (plugins, modules) signal screenOffRequested @@ -60,8 +62,18 @@ Singleton { repeat: false onTriggered: { const action = root.fadePending; - root.fadePending = ""; root._executeAction(action); + overlayCleanupTimer.start(); + } + } + + Timer { + id: overlayCleanupTimer + interval: 500 + repeat: false + onTriggered: { + root.fadePending = ""; + root._runNextQueuedStage(); } } @@ -75,17 +87,99 @@ Singleton { // ------------------------------------------------------- function cancelFade() { - if (fadePending === "") + if (fadePending === "") { + _queuedStages = []; + _restoreMonitors(); return; + } Logger.i("IdleService", "Fade cancelled for:", fadePending); fadePending = ""; + _queuedStages = []; graceTimer.stop(); + overlayCleanupTimer.stop(); + _restoreMonitors(); + } + + function _restoreMonitors() { + if (!_screenOffActive) + return; + _screenOffActive = false; + Logger.i("IdleService", "Restoring monitors (DPMS on)"); + CompositorService.turnOnMonitors(); + + if (Settings.data.idle.resumeScreenOffCommand) { + Logger.i("IdleService", "Executing screen-off resume command"); + Quickshell.execDetached(["sh", "-c", Settings.data.idle.resumeScreenOffCommand]); + } + } + + function _queueStage(stage) { + if (!_isValidStage(stage)) { + Logger.w("IdleService", "Ignoring unknown queued stage:", stage); + return; + } + if (stage === fadePending) + return; + if (_queuedStages.indexOf(stage) !== -1) + return; + _queuedStages.push(stage); + Logger.d("IdleService", "Queued idle stage while fade is active:", stage); + } + + function _isValidStage(stage) { + return stage === "screenOff" || stage === "lock" || stage === "suspend"; + } + + function _isStageEnabled(stage) { + const idle = Settings.data.idle; + if (stage === "screenOff") + return idle.screenOffTimeout > 0; + if (stage === "lock") + return idle.lockTimeout > 0; + if (stage === "suspend") + return idle.suspendTimeout > 0; + return false; + } + + function _runNextQueuedStage() { + if (fadePending !== "") + return; + if (idleSeconds <= 0) { + _queuedStages = []; + return; + } + + while (_queuedStages.length > 0) { + const nextStage = _queuedStages.shift(); + if (!_isValidStage(nextStage)) { + Logger.w("IdleService", "Dropping queued unknown stage:", nextStage); + continue; + } + if (!_isStageEnabled(nextStage)) { + Logger.d("IdleService", "Dropping queued disabled stage:", nextStage); + continue; + } + + Logger.i("IdleService", "Running queued idle stage:", nextStage); + _onIdle(nextStage); + return; + } } function _onIdle(stage) { - // Don't re-trigger if already fading something - if (fadePending !== "") + if (!_isValidStage(stage)) { + Logger.w("IdleService", "Idle fired with unknown stage:", stage); return; + } + if (!_isStageEnabled(stage)) { + Logger.d("IdleService", "Ignoring idle stage because it is disabled:", stage); + return; + } + + if (fadePending !== "") { + _queueStage(stage); + return; + } Logger.i("IdleService", "Idle fired:", stage); fadePending = stage; graceTimer.restart(); @@ -94,16 +188,25 @@ Singleton { function _executeAction(stage) { Logger.i("IdleService", "Executing action:", stage); if (stage === "screenOff") { + if (Settings.data.idle.screenOffCommand) + Quickshell.execDetached(["sh", "-c", Settings.data.idle.screenOffCommand]); CompositorService.turnOffMonitors(); + root._screenOffActive = true; root.screenOffRequested(); } else if (stage === "lock") { + if (Settings.data.idle.lockCommand) + Quickshell.execDetached(["sh", "-c", Settings.data.idle.lockCommand]); if (PanelService.lockScreen && !PanelService.lockScreen.active) { PanelService.lockScreen.active = true; } root.lockRequested(); } else if (stage === "suspend") { + if (Settings.data.idle.suspendCommand) + Quickshell.execDetached(["sh", "-c", Settings.data.idle.suspendCommand]); CompositorService.suspend(); root.suspendRequested(); + } else { + Logger.w("IdleService", "Unknown idle stage action:", stage); } } @@ -172,7 +275,8 @@ Singleton { const entry = entries[i]; const timeoutSec = parseInt(entry.timeout); const cmd = entry.command; - if (!cmd || timeoutSec <= 0) + const resumeCmd = entry.resumeCommand || ""; + if (!cmd && !resumeCmd || timeoutSec <= 0) continue; try { const qml = ` @@ -182,9 +286,14 @@ Singleton { const monitor = Qt.createQmlObject(qml, root, "IdleMonitor_custom_" + i); const capturedCmd = cmd; + const capturedResumeCmd = resumeCmd; monitor.isIdleChanged.connect(function () { if (monitor.isIdle) { - root._executeCustomCommand(capturedCmd); + if (capturedCmd) + root._executeCustomCommand(capturedCmd); + } else { + if (capturedResumeCmd) + root._executeCustomCommand(capturedResumeCmd); } }); newMonitors[i] = monitor; @@ -263,7 +372,15 @@ Singleton { } else { idleCounter.stop(); root.idleSeconds = 0; + if (root.fadePending === "lock" && Settings.data.idle.resumeLockCommand) { + Logger.i("IdleService", "Executing lock resume command"); + Quickshell.execDetached(["sh", "-c", Settings.data.idle.resumeLockCommand]); + } else if (root.fadePending === "suspend" && Settings.data.idle.resumeSuspendCommand) { + Logger.i("IdleService", "Executing suspend resume command"); + Quickshell.execDetached(["sh", "-c", Settings.data.idle.resumeSuspendCommand]); + } root.cancelFade(); + overlayCleanupTimer.stop(); } }); _heartbeatMonitor = monitor; diff --git a/Services/System/SystemStatService.qml b/Services/System/SystemStatService.qml index a11c6290c..747215e47 100644 --- a/Services/System/SystemStatService.qml +++ b/Services/System/SystemStatService.qml @@ -39,6 +39,7 @@ Singleton { // Public values property real cpuUsage: 0 + property list coresUsage: [] property real cpuTemp: 0 property string cpuFreq: "0.0GHz" property real cpuFreqRatio: 0 @@ -75,7 +76,7 @@ Singleton { readonly property int cpuHistoryLength: Math.ceil(historyDurationMs / cpuUsageIntervalMs) readonly property int gpuHistoryLength: Math.ceil(historyDurationMs / gpuIntervalMs) readonly property int memHistoryLength: Math.ceil(historyDurationMs / memIntervalMs) - readonly property int diskHistoryLength: Math.ceil(historyDurationMs / diskIntervalMs) + readonly property int diskHistoryLength: Math.max(10, Math.ceil(historyDurationMs / diskIntervalMs)) readonly property int networkHistoryLength: Math.ceil(historyDurationMs / networkIntervalMs) property var cpuHistory: new Array(cpuHistoryLength).fill(0) @@ -172,11 +173,11 @@ Singleton { // Minimum floor of 1 MB/s so graph doesn't fluctuate at low speeds readonly property real rxMaxSpeed: { const max = Math.max(...rxSpeedHistory); - return Math.max(max, 1048576); // 1 MB/s floor + return Math.max(max, 1000000); // 1 MB/s floor } readonly property real txMaxSpeed: { const max = Math.max(...txSpeedHistory); - return Math.max(max, 524288); // 512 KB/s floor + return Math.max(max, 512000); // 512 KB/s floor } // Ready-to-use ratios based on current maximums (0..1 range) @@ -231,6 +232,14 @@ Singleton { readonly property color memColor: memCritical ? criticalColor : (memWarning ? warningColor : Color.mPrimary) readonly property color swapColor: swapCritical ? criticalColor : (swapWarning ? warningColor : Color.mPrimary) + function getCoreUsageColor(usage) { + if (usage >= cpuCriticalThreshold) + return criticalColor; + if (usage >= cpuWarningThreshold) + return warningColor; + return Color.mPrimary; + } + function getDiskColor(diskPath, available = false) { return isDiskCritical(diskPath, available) ? criticalColor : (isDiskWarning(diskPath, available) ? warningColor : Color.mPrimary); } @@ -246,6 +255,7 @@ Singleton { // Internal state for CPU calculation property var prevCpuStats: null + property var prevCpuCoresStats: null // Internal state for network speed calculation // Previous Bytes need to be stored as 'real' as they represent the total of bytes transfered @@ -299,6 +309,7 @@ Singleton { if (shouldRun) { // Reset differential state so first readings after resume are clean root.prevCpuStats = null; + root.prevCpuCoresStats = null; root.prevTime = 0; // Trigger initial reads @@ -332,6 +343,7 @@ Singleton { function onResumed() { Logger.i("SystemStat", "System resumed - resetting differential state"); root.prevCpuStats = null; + root.prevCpuCoresStats = null; root.prevTime = 0; } } @@ -501,7 +513,7 @@ Singleton { const newUsedGb = {}; const newSizeGb = {}; const newAvailableGb = {}; - const bytesPerGb = 1024 * 1024 * 1024; + const bytesPerGb = 1000 * 1000 * 1000; // Start from line 1 (skip header) for (var i = 1; i < lines.length; i++) { const parts = lines[i].trim().split(/\s+/); @@ -1079,16 +1091,9 @@ Singleton { // ------------------------------------------------------- // Calculate CPU usage from /proc/stat - function calculateCpuUsage(text) { - if (!text) - return; - const lines = text.split('\n'); - const cpuLine = lines[0]; - // First line is total CPU - if (!cpuLine.startsWith('cpu ')) - return; - const parts = cpuLine.split(/\s+/); + function calculateLineUsage(line) { + const parts = line.split(/\s+/); const stats = { "user": parseInt(parts[1]) || 0, "nice": parseInt(parts[2]) || 0, @@ -1101,23 +1106,73 @@ Singleton { "guest": parseInt(parts[9]) || 0, "guestNice": parseInt(parts[10]) || 0 }; - const totalIdle = stats.idle + stats.iowait; - const total = Object.values(stats).reduce((sum, val) => sum + val, 0); + return stats; + } - if (root.prevCpuStats) { - const prevTotalIdle = root.prevCpuStats.idle + root.prevCpuStats.iowait; - const prevTotal = Object.values(root.prevCpuStats).reduce((sum, val) => sum + val, 0); + function computeUsage(prev, curr) { + if (!prev || !curr) + return -1; + const currTotalIdle = curr.idle + curr.iowait; + const currTotal = Object.values(curr).reduce((sum, val) => sum + val, 0); + const prevTotalIdle = prev.idle + prev.iowait; + const prevTotal = Object.values(prev).reduce((sum, val) => sum + val, 0); - const diffTotal = total - prevTotal; - const diffIdle = totalIdle - prevTotalIdle; + const diffTotal = currTotal - prevTotal; + const diffIdle = currTotalIdle - prevTotalIdle; + if (diffTotal > 0) { + return (((diffTotal - diffIdle) / diffTotal) * 100).toFixed(1); + } + return -1; + } - if (diffTotal > 0) { - root.cpuUsage = (((diffTotal - diffIdle) / diffTotal) * 100).toFixed(1); - } + function calculateCpuUsage(text) { + if (!text) + return; + const lines = text.split('\n'); + const cpuLine = lines[0]; + + // First line is total CPU + if (!cpuLine.startsWith('cpu ')) + return; + + const currCpuStats = calculateLineUsage(cpuLine); + const usage = computeUsage(root.prevCpuStats, currCpuStats); + + if (usage >= 0) { + root.cpuUsage = usage; root.pushCpuHistory(); } + root.prevCpuStats = currCpuStats; - root.prevCpuStats = stats; + // Find the number of CPU cores + let nbCores = 0; + for (let i = 1; i < lines.length; i++) { + if (!lines[i].startsWith('cpu')) + break; + nbCores++; + } + + // Fallback if we did not find any cores + if (nbCores === 0) + return; + + // If we found more cores than before, we reset our stats + if (root.coresUsage.length < nbCores) + root.coresUsage = new Array(nbCores).fill(0); + + let coresStats = []; + let newCoresUsage = root.coresUsage.slice(); + for (let i = 0; i < nbCores; i++) { + const coreCpuLine = lines[i + 1]; + const currCoreStats = calculateLineUsage(coreCpuLine); + const coreUsage = computeUsage(root.prevCpuCoresStats?.[i], currCoreStats); + if (coreUsage >= 0) { + newCoresUsage[i] = coreUsage; + } + coresStats.push(currCoreStats); + } + root.coresUsage = newCoresUsage; + root.prevCpuCoresStats = coresStats; } // ------------------------------------------------------- @@ -1209,11 +1264,11 @@ Singleton { // Helper function to format network speeds function formatSpeed(bytesPerSecond) { const units = ["KB", "MB", "GB"]; - let value = bytesPerSecond / 1024; + let value = bytesPerSecond / 1000; let unitIndex = 0; - while (value >= 1024 && unitIndex < units.length - 1) { - value /= 1024; + while (value >= 1000 && unitIndex < units.length - 1) { + value /= 1000; unitIndex++; } @@ -1232,13 +1287,13 @@ Singleton { const units = ["", "K", "M", "G"]; let value = bytesPerSecond; let unitIndex = 0; - while (value >= 1024 && unitIndex < units.length - 1) { - value = value / 1024.0; + while (value >= 1000 && unitIndex < units.length - 1) { + value = value / 1000.0; unitIndex++; } // Promote at ~100 of current unit (e.g., 100k -> ~0.1M shown as 0.1M or 0M if rounded) if (unitIndex < units.length - 1 && value >= 100) { - value = value / 1024.0; + value = value / 1000.0; unitIndex++; } const display = Math.round(value).toString(); diff --git a/Services/Theming/ColorSchemeService.qml b/Services/Theming/ColorSchemeService.qml index be2e34e0e..77b2befb3 100644 --- a/Services/Theming/ColorSchemeService.qml +++ b/Services/Theming/ColorSchemeService.qml @@ -193,7 +193,7 @@ Singleton { Logger.i("ColorScheme", "Applying color scheme:", getBasename(path)); // Generate templates for predefined color schemes - if (hasEnabledTemplates()) { + if (hasEnabledTemplates() || Settings.data.templates.enableUserTheming) { AppThemeService.generateFromPredefinedScheme(data); } } catch (e) { diff --git a/Services/Theming/TemplateProcessor.qml b/Services/Theming/TemplateProcessor.qml index 4af72d633..089ceaa7c 100644 --- a/Services/Theming/TemplateProcessor.qml +++ b/Services/Theming/TemplateProcessor.qml @@ -98,8 +98,8 @@ Singleton { function executeWallpaperColors(wallpaperPath, mode) { Logger.d("TemplateProcessor", `executeWallpaperColors: path=${wallpaperPath}, mode=${mode}`); const content = buildThemeConfig(); - if (!content) { - Logger.d("TemplateProcessor", "executeWallpaperColors: no config content, aborting"); + if (!content && !Settings.data.templates.enableUserTheming) { + Logger.d("TemplateProcessor", "executeWallpaperColors: no config content and no user theming, aborting"); return; } const wp = wallpaperPath.replace(/'/g, "'\\''"); @@ -129,42 +129,41 @@ Singleton { } function executePredefinedScheme(schemeData, mode, wallpaperPath) { - // 1. Handle terminal themes (runtime generation or pre-rendered file copy) - handleTerminalThemes(schemeData, mode); - - // 2. Build TOML config for application templates + // 1. Build TOML config for application templates (including terminals) const tomlContent = buildPredefinedTemplateConfig(mode); - if (!tomlContent) { + if (!tomlContent && !Settings.data.templates.enableUserTheming) { Logger.d("TemplateProcessor", "No application templates enabled for predefined scheme"); return; } // 3. Build script to write files and run Python const schemeJsonPathEsc = schemeJsonPath.replace(/'/g, "'\\''"); - const configPathEsc = predefinedConfigPath.replace(/'/g, "'\\''"); - - // Use heredoc delimiters for safe JSON/TOML content - const schemeDelimiter = "SCHEME_JSON_EOF_" + Math.random().toString(36).substr(2, 9); - const tomlDelimiter = "TOML_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9); let script = ""; - // Write scheme JSON + // Write scheme JSON (needed by both built-in and user templates) + const schemeDelimiter = "SCHEME_JSON_EOF_" + Math.random().toString(36).substr(2, 9); script += `cat > '${schemeJsonPathEsc}' << '${schemeDelimiter}'\n`; script += JSON.stringify(schemeData, null, 2) + "\n"; script += `${schemeDelimiter}\n`; - // Write TOML config - script += `cat > '${configPathEsc}' << '${tomlDelimiter}'\n`; - script += tomlContent + "\n"; - script += `${tomlDelimiter}\n`; + // Run built-in template processor only if there are templates configured + if (tomlContent) { + const configPathEsc = predefinedConfigPath.replace(/'/g, "'\\''"); + const tomlDelimiter = "TOML_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9); - // Run Python template processor with --scheme flag - // Don't pass --mode so templates get both dark and light colors (e.g., zed.json needs both) - // Pass --default-mode so "default" in templates resolves to the current theme mode - // Pass wallpaper as positional arg so image_path is available in templates (no extraction occurs when --scheme is used) - const wpArg = wallpaperPath ? `'${wallpaperPath.replace(/'/g, "'\\''")}'` : ""; - script += `python3 "${templateProcessorScript}" ${wpArg} --scheme '${schemeJsonPathEsc}' --config '${configPathEsc}' --default-mode ${mode}\n`; + // Write TOML config + script += `cat > '${configPathEsc}' << '${tomlDelimiter}'\n`; + script += tomlContent + "\n"; + script += `${tomlDelimiter}\n`; + + // Run Python template processor with --scheme flag + // Don't pass --mode so templates get both dark and light colors (e.g., zed.json needs both) + // Pass --default-mode so "default" in templates resolves to the current theme mode + // Pass wallpaper as positional arg so image_path is available in templates (no extraction occurs when --scheme is used) + const wpArg = wallpaperPath ? `'${wallpaperPath.replace(/'/g, "'\\''")}'` : ""; + script += `python3 "${templateProcessorScript}" ${wpArg} --scheme '${schemeJsonPathEsc}' --config '${configPathEsc}' --default-mode ${mode}\n`; + } // Add user templates if enabled script += buildUserTemplateCommandForPredefined(schemeData, mode, wallpaperPath); @@ -178,6 +177,20 @@ Singleton { */ function buildPredefinedTemplateConfig(mode) { var lines = []; + const homeDir = Quickshell.env("HOME"); + + // Add terminal templates + TemplateRegistry.terminals.forEach(terminal => { + if (isTemplateEnabled(terminal.id)) { + lines.push(`\n[templates.${terminal.id}]`); + lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${terminal.predefinedTemplatePath}"`); + const outputPath = terminal.outputPath.replace("~", homeDir); + lines.push(`output_path = "${outputPath}"`); + const postHookEsc = escapeTomlString(terminal.postHook); + lines.push(`post_hook = "${postHookEsc}"`); + } + }); + addApplicationTheming(lines, mode); if (lines.length > 0) { @@ -319,182 +332,29 @@ Singleton { } function buildGenerationScript(content, wallpaper, mode) { - const delimiter = "THEME_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9); const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''"); const wpDelimiter = "WALLPAPER_PATH_EOF_" + Math.random().toString(36).substr(2, 9); // Use heredoc for wallpaper path to avoid all escaping issues - let script = `cat > '${pathEsc}' << '${delimiter}'\n${content}\n${delimiter}\n`; - script += `NOCTALIA_WP_PATH=$(cat << '${wpDelimiter}'\n${wallpaper}\n${wpDelimiter}\n)\n`; + let script = `NOCTALIA_WP_PATH=$(cat << '${wpDelimiter}'\n${wallpaper}\n${wpDelimiter}\n)\n`; - // Use template-processor.py (Python implementation) - // Don't pass --mode so templates get both dark and light colors (e.g., zed.json needs both) - // Pass --default-mode so "default" in templates resolves to the current theme mode - const schemeType = getSchemeType(); - script += `python3 "${templateProcessorScript}" "$NOCTALIA_WP_PATH" --scheme-type ${schemeType} --config '${pathEsc}' --default-mode ${mode} `; + // Run built-in template processor only if there are templates configured + if (content) { + const delimiter = "THEME_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9); + script += `cat > '${pathEsc}' << '${delimiter}'\n${content}\n${delimiter}\n`; + + // Use template-processor.py (Python implementation) + // Don't pass --mode so templates get both dark and light colors (e.g., zed.json needs both) + // Pass --default-mode so "default" in templates resolves to the current theme mode + const schemeType = getSchemeType(); + script += `python3 "${templateProcessorScript}" "$NOCTALIA_WP_PATH" --scheme-type ${schemeType} --config '${pathEsc}' --default-mode ${mode}\n`; + } script += buildUserTemplateCommand("$NOCTALIA_WP_PATH", mode); return script + "\n"; } - // ================================================================================ - // PREDEFINED COLOR SCHEMES - // TERMINAL THEMES (dual-path: runtime generation or legacy pre-rendered file copy) - // ================================================================================ - function escapeShellPath(path) { - // Escape single quotes by ending the quoted string, adding an escaped quote, and starting a new quoted string - return "'" + path.replace(/'/g, "'\\''") + "'"; - } - - function handleTerminalThemes(schemeData, mode) { - const homeDir = Quickshell.env("HOME"); - - // Check if scheme has terminal section (new format) - const modeData = schemeData[mode] || schemeData; - const hasTerminalSection = modeData && modeData.terminal; - - if (hasTerminalSection) { - // New path: runtime generation from JSON terminal colors - handleTerminalThemesGenerate(schemeData, mode, homeDir); - } else { - // Old path: copy pre-rendered files (backward compatibility for DLC schemes) - handleTerminalThemesCopy(mode, homeDir); - } - } - - /** - * New path: Generate terminal themes at runtime from scheme's terminal section - */ - function handleTerminalThemesGenerate(schemeData, mode, homeDir) { - // Build terminal output mapping for enabled terminals - const terminalOutputs = {}; - TemplateRegistry.terminals.forEach(terminal => { - if (isTemplateEnabled(terminal.id)) { - const outputPath = terminal.outputPath.replace("~", homeDir); - terminalOutputs[terminal.id] = outputPath; - } - }); - - if (Object.keys(terminalOutputs).length === 0) { - Logger.d("TemplateProcessor", "No terminal templates enabled for generation"); - return; - } - - // Write scheme JSON to temp file and call Python with --terminal-output - const schemeJsonPathEsc = schemeJsonPath.replace(/'/g, "'\\''"); - const schemeDelimiter = "SCHEME_JSON_EOF_" + Math.random().toString(36).substr(2, 9); - - let script = ""; - - // Write scheme JSON - script += `cat > '${schemeJsonPathEsc}' << '${schemeDelimiter}'\n`; - script += JSON.stringify(schemeData, null, 2) + "\n"; - script += `${schemeDelimiter}\n`; - - // Create output directories - Object.values(terminalOutputs).forEach(path => { - const dir = path.substring(0, path.lastIndexOf('/')); - script += `mkdir -p ${escapeShellPath(dir)}; `; - }); - - // Run Python with terminal generation - const terminalOutputsJson = JSON.stringify(terminalOutputs).replace(/'/g, "'\\''"); - script += `python3 "${templateProcessorScript}" --scheme '${schemeJsonPathEsc}' --default-mode ${mode} --terminal-output '${terminalOutputsJson}'; `; - - // Run post-hooks for enabled terminals - TemplateRegistry.terminals.forEach(terminal => { - if (isTemplateEnabled(terminal.id)) { - script += `${terminal.postHook}; `; - } - }); - - copyProcess.command = ["sh", "-c", script]; - copyProcess.running = true; - } - - /** - * Old path: Copy pre-rendered terminal files (backward compatibility) - * Should be removed in late february 2026 - */ - function handleTerminalThemesCopy(mode, homeDir) { - const commands = []; - - TemplateRegistry.terminals.forEach(terminal => { - if (isTemplateEnabled(terminal.id)) { - const outputPath = terminal.outputPath.replace("~", homeDir); - const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')); - const templatePaths = getTerminalColorsTemplate(terminal.id, mode); - - commands.push(`mkdir -p ${escapeShellPath(outputDir)}`); - // Try hyphen first (most common), then space (for schemes like "Rosey AMOLED") - const hyphenPath = escapeShellPath(templatePaths.hyphen); - const spacePath = escapeShellPath(templatePaths.space); - commands.push(`if [ -f ${hyphenPath} ]; then cp -f ${hyphenPath} ${escapeShellPath(outputPath)}; elif [ -f ${spacePath} ]; then cp -f ${spacePath} ${escapeShellPath(outputPath)}; else echo "ERROR: Template file not found for ${terminal.id} (tried both hyphen and space patterns)"; fi`); - - // Always use the apply script to set the theme and attempt hot reloading - commands.push(terminal.postHook); - } - }); - - if (commands.length > 0) { - copyProcess.command = ["sh", "-c", commands.join('; ')]; - copyProcess.running = true; - } - } - - function getTerminalColorsTemplate(terminal, mode) { - const schemeNameMap = ({ - "Noctalia (default)": "Noctalia-default", - "Noctalia (legacy)": "Noctalia-legacy", - "Tokyo Night": "Tokyo-Night", - "Rose Pine": "Rosepine" - }); - - let colorScheme = Settings.data.colorSchemes.predefinedScheme; - colorScheme = schemeNameMap[colorScheme] || colorScheme; - - let extension = ""; - if (terminal === 'kitty') { - extension = ".conf"; - } else if (terminal === 'wezterm') { - extension = ".toml"; - } - - // Support both naming conventions: "SchemeName-dark" (hyphen) and "SchemeName dark" (space) - const fileNameHyphen = `${colorScheme}-${mode}${extension}`; - const fileNameSpace = `${colorScheme} ${mode}${extension}`; - const relativePathHyphen = `terminal/${terminal}/${fileNameHyphen}`; - const relativePathSpace = `terminal/${terminal}/${fileNameSpace}`; - - // Try to find the scheme in the loaded schemes list to determine which directory it's in - for (let i = 0; i < ColorSchemeService.schemes.length; i++) { - const schemeJsonPath = ColorSchemeService.schemes[i]; - // Check if this is the scheme we're looking for - if (schemeJsonPath.indexOf(`/${colorScheme}/`) !== -1 || schemeJsonPath.indexOf(`/${colorScheme}.json`) !== -1) { - // Extract the scheme directory from the JSON path - // JSON path is like: /path/to/scheme/SchemeName/SchemeName.json - // We need: /path/to/scheme/SchemeName/terminal/... - const schemeDir = schemeJsonPath.substring(0, schemeJsonPath.lastIndexOf('/')); - return { - hyphen: `${schemeDir}/${relativePathHyphen}`, - space: `${schemeDir}/${relativePathSpace}` - }; - } - } - - // Fallback: try downloaded first, then preinstalled - const downloadedPathHyphen = `${ColorSchemeService.downloadedSchemesDirectory}/${colorScheme}/${relativePathHyphen}`; - const downloadedPathSpace = `${ColorSchemeService.downloadedSchemesDirectory}/${colorScheme}/${relativePathSpace}`; - const preinstalledPathHyphen = `${ColorSchemeService.schemesDirectory}/${colorScheme}/${relativePathHyphen}`; - const preinstalledPathSpace = `${ColorSchemeService.schemesDirectory}/${colorScheme}/${relativePathSpace}`; - - return { - hyphen: preinstalledPathHyphen, - space: preinstalledPathSpace - }; - } - // ================================================================================ // USER TEMPLATES, advanced usage // ================================================================================ @@ -610,18 +470,4 @@ Singleton { } } } - - // ------------ - Process { - id: copyProcess - workingDirectory: Quickshell.shellDir - running: false - stderr: StdioCollector { - onStreamFinished: { - if (this.text) { - Logger.e("TemplateProcessor", "copyProcess stderr:", this.text); - } - } - } - } } diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index a7fab2322..1a8233ebe 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -8,6 +8,11 @@ import qs.Commons Singleton { id: root + Component.onCompleted: { + if (Settings.data.templates.enableUserTheming) + writeUserTemplatesToml(); + } + readonly property string templateApplyScript: Quickshell.shellDir + '/Scripts/bash/template-apply.sh' readonly property string gtkRefreshScript: Quickshell.shellDir + '/Scripts/python/src/theming/gtk-refresh.py' readonly property string vscodeHelperScript: Quickshell.shellDir + '/Scripts/python/src/theming/vscode-helper.py' @@ -23,6 +28,7 @@ Singleton { "id": "foot", "name": "Foot", "templatePath": "terminal/foot", + "predefinedTemplatePath": "terminal/foot-predefined", "outputPath": "~/.config/foot/themes/noctalia", "postHook": `${templateApplyScript} foot` }, @@ -30,6 +36,7 @@ Singleton { "id": "ghostty", "name": "Ghostty", "templatePath": "terminal/ghostty", + "predefinedTemplatePath": "terminal/ghostty-predefined", "outputPath": "~/.config/ghostty/themes/noctalia", "postHook": `${templateApplyScript} ghostty` }, @@ -37,6 +44,7 @@ Singleton { "id": "kitty", "name": "Kitty", "templatePath": "terminal/kitty.conf", + "predefinedTemplatePath": "terminal/kitty-predefined.conf", "outputPath": "~/.config/kitty/themes/noctalia.conf", "postHook": `${templateApplyScript} kitty` }, @@ -44,6 +52,7 @@ Singleton { "id": "alacritty", "name": "Alacritty", "templatePath": "terminal/alacritty.toml", + "predefinedTemplatePath": "terminal/alacritty-predefined.toml", "outputPath": "~/.config/alacritty/themes/noctalia.toml", "postHook": `${templateApplyScript} alacritty` }, @@ -51,6 +60,7 @@ Singleton { "id": "wezterm", "name": "Wezterm", "templatePath": "terminal/wezterm.toml", + "predefinedTemplatePath": "terminal/wezterm-predefined.toml", "outputPath": "~/.config/wezterm/colors/Noctalia.toml", "postHook": `${templateApplyScript} wezterm` } @@ -189,6 +199,10 @@ Singleton { "name": "vencord", "path": "~/.config/Vencord" }, + { + "name": "vencord-flatpak", + "path": "~/.var/app/com.discordapp.Discord/config/Vencord" + }, { "name": "betterdiscord", "path": "~/.config/BetterDiscord" @@ -538,7 +552,7 @@ Singleton { var userConfigPath = Settings.configDir + "user-templates.toml"; // Check if file already exists - fileCheckProcess.command = ["test", "-f", userConfigPath]; + fileCheckProcess.command = ["test", "-s", userConfigPath]; fileCheckProcess.running = true; } @@ -546,17 +560,14 @@ Singleton { var userConfigPath = Settings.configDir + "user-templates.toml"; var configContent = buildUserTemplatesToml(); var userConfigPathEsc = userConfigPath.replace(/'/g, "'\\''"); + var configDirEsc = Settings.configDir.replace(/'/g, "'\\''"); - // Ensure directory exists (should already exist but just in case) - Quickshell.execDetached(["mkdir", "-p", Settings.configDir]); - - // Write the config file using heredoc to avoid escaping issues - var script = `cat > '${userConfigPathEsc}' << 'EOF'\n`; + // Combine mkdir and write in a single script to avoid race condition + var script = `mkdir -p '${configDirEsc}' && cat > '${userConfigPathEsc}' << 'EOF'\n`; script += configContent; script += "EOF\n"; - Quickshell.execDetached(["sh", "-c", script]); - - Logger.d("TemplateRegistry", "User templates config written to:", userConfigPath); + fileWriteProcess.command = ["sh", "-c", script]; + fileWriteProcess.running = true; } // Extract Emacs clients for ProgramCheckerService compatibility @@ -575,19 +586,33 @@ Singleton { } ] - // Process for checking if user templates file exists + // Process for checking if user templates file exists and is non-empty Process { id: fileCheckProcess running: false onExited: function (exitCode) { if (exitCode === 0) { - // File exists, skip creation + // File exists and is non-empty, skip creation Logger.d("TemplateRegistry", "User templates config already exists, skipping creation"); } else { - // File doesn't exist, create it + // File doesn't exist or is empty, create it doWriteUserTemplatesToml(); } } } + + // Process for writing user templates file with error reporting + Process { + id: fileWriteProcess + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + Logger.d("TemplateRegistry", "User templates config written to:", Settings.configDir + "user-templates.toml"); + } else { + Logger.e("TemplateRegistry", "Failed to write user templates config (exit code:", exitCode + ")"); + } + } + } } diff --git a/Services/UI/BarWidgetRegistry.qml b/Services/UI/BarWidgetRegistry.qml index f452b9f0f..238d5f281 100644 --- a/Services/UI/BarWidgetRegistry.qml +++ b/Services/UI/BarWidgetRegistry.qml @@ -203,10 +203,7 @@ Singleton { "visualizerType": "linear", "textColor": "none", "compactMode": false, - "panelShowAlbumArt": true, - "panelShowVisualizer": true, - "compactShowAlbumArt": true, - "compactShowVisualizer": false + "panelShowAlbumArt": true }, "Microphone": { "displayMode": "onhover", @@ -237,6 +234,7 @@ Singleton { "useMonospaceFont": true, "usePadding": false, "showCpuUsage": true, + "showCpuCores": false, "showCpuFreq": false, "showCpuTemp": true, "showGpuTemp": false, @@ -305,7 +303,8 @@ Singleton { "occupiedColor": "secondary", "emptyColor": "secondary", "showBadge": true, - "pillSize": 0.6 + "pillSize": 0.6, + "fontWeight": "bold" }, "Volume": { "displayMode": "onhover", diff --git a/Services/UI/DesktopWidgetRegistry.qml b/Services/UI/DesktopWidgetRegistry.qml index 5f1bd7f2e..f4ad868cf 100644 --- a/Services/UI/DesktopWidgetRegistry.qml +++ b/Services/UI/DesktopWidgetRegistry.qml @@ -28,6 +28,9 @@ Singleton { property Component systemStatComponent: Component { DesktopSystemStat {} } + property Component audioVisualizerComponent: Component { + DesktopAudioVisualizer {} + } // Widget registry object mapping widget names to components // Created in Component.onCompleted to ensure Components are ready @@ -40,6 +43,7 @@ Singleton { widgetsObj["MediaPlayer"] = mediaPlayerComponent; widgetsObj["Weather"] = weatherComponent; widgetsObj["SystemStat"] = systemStatComponent; + widgetsObj["AudioVisualizer"] = audioVisualizerComponent; widgets = widgetsObj; Logger.i("DesktopWidgetRegistry", "Service started"); @@ -49,39 +53,52 @@ Singleton { "Clock": "WidgetSettings/ClockSettings.qml", "MediaPlayer": "WidgetSettings/MediaPlayerSettings.qml", "Weather": "WidgetSettings/WeatherSettings.qml", - "SystemStat": "WidgetSettings/SystemStatSettings.qml" + "SystemStat": "WidgetSettings/SystemStatSettings.qml", + "AudioVisualizer": "WidgetSettings/AudioVisualizerSettings.qml" }) property var widgetMetadata: ({ "Clock": { "showBackground": true, + "roundedCorners": true, "clockStyle": "digital", "clockColor": "none", "useCustomFont": false, + "customFont": "", "format": "HH:mm\\nd MMMM yyyy" }, "MediaPlayer": { "showBackground": true, + "roundedCorners": true, "visualizerType": "linear", "hideMode": "visible", "showButtons": true, "showAlbumArt": true, - "showVisualizer": true, - "roundedCorners": true + "showVisualizer": true }, "Weather": { - "showBackground": true + "showBackground": true, + "roundedCorners": true }, "SystemStat": { "showBackground": true, + "roundedCorners": true, "statType": "CPU", "diskPath": "/", - "roundedCorners": true, "layout": "bottom" + }, + "AudioVisualizer": { + "showBackground": true, + "roundedCorners": true, + "width": 320, + "height": 72, + "visualizerType": "linear", + "hideWhenIdle": false, + "colorName": "primary" } }) - property var cpuIntensiveWidgets: ["SystemStat"] + property var cpuIntensiveWidgets: ["SystemStat", "AudioVisualizer"] // Plugin widget storage (mirroring BarWidgetRegistry pattern) property var pluginWidgets: ({}) @@ -245,7 +262,7 @@ Singleton { if (root.isPluginWidget(widgetId)) { var pluginId = widgetId.replace("plugin:", ""); var manifest = PluginRegistry.getPluginManifest(pluginId); - if (manifest && manifest.entryPoints && manifest.entryPoints.settings) { + if (manifest && manifest.entryPoints && (manifest.entryPoints.desktopWidgetSettings || manifest.entryPoints.settings)) { hasSettings = true; } } else { diff --git a/Services/UI/ImageCacheService.qml b/Services/UI/ImageCacheService.qml index cb6171dd7..661c19e43 100644 --- a/Services/UI/ImageCacheService.qml +++ b/Services/UI/ImageCacheService.qml @@ -374,8 +374,8 @@ Singleton { const srcEsc = sourcePath.replace(/'/g, "'\\''"); const dstEsc = outputPath.replace(/'/g, "'\\''"); - // Use Lanczos filter for high-quality downscaling, subtle unsharp mask, and PNG for lossless output - const command = `magick '${srcEsc}' -auto-orient -filter Lanczos -resize '${width}x${height}^' -unsharp 0x0.5 '${dstEsc}'`; + // Use Lanczos filter for high-quality downscaling and PNG for lossless output + const command = `magick '${srcEsc}' -auto-orient -filter Lanczos -resize '${width}x${height}^' '${dstEsc}'`; runProcess(command, cacheKey, outputPath, sourcePath); } diff --git a/Services/UI/LauncherProviderRegistry.qml b/Services/UI/LauncherProviderRegistry.qml index 59523ab2f..f5810ab73 100644 --- a/Services/UI/LauncherProviderRegistry.qml +++ b/Services/UI/LauncherProviderRegistry.qml @@ -3,6 +3,7 @@ pragma Singleton import QtQuick import Quickshell import qs.Commons +import qs.Services.Noctalia Singleton { id: root @@ -13,11 +14,15 @@ Singleton { property var pluginProviders: ({}) // { "plugin:pluginId": component } property var pluginProviderMetadata: ({}) // { "plugin:pluginId": metadata } + // Persistent provider instances — survive LauncherCore destruction/recreation + // so plugins don't re-parse large datasets on every launcher open. + property var providerInstances: ({}) // { "plugin:pluginId": instance } + function init() { Logger.i("LauncherProviderRegistry", "Service started"); } - // Register a plugin launcher provider + // Register a plugin launcher provider and instantiate it immediately function registerPluginProvider(pluginId, component, metadata) { if (!pluginId || !component) { Logger.e("LauncherProviderRegistry", "Cannot register plugin provider: invalid parameters"); @@ -29,7 +34,22 @@ Singleton { pluginProviders[providerId] = component; pluginProviderMetadata[providerId] = metadata || {}; - Logger.i("LauncherProviderRegistry", "Registered plugin provider:", providerId); + // Instantiate immediately so data loading starts in the background + var pluginApi = PluginService.getPluginAPI(pluginId); + if (pluginApi) { + var instance = component.createObject(null, { + pluginApi: pluginApi + }); + if (instance) { + providerInstances[providerId] = instance; + if (instance.init) + instance.init(); + Logger.i("LauncherProviderRegistry", "Registered and instantiated plugin provider:", providerId); + } else { + Logger.e("LauncherProviderRegistry", "Failed to instantiate plugin provider:", providerId); + } + } + root.pluginProviderRegistryUpdated(); return true; } @@ -43,6 +63,11 @@ Singleton { return false; } + if (providerInstances[providerId]) { + providerInstances[providerId].destroy(); + delete providerInstances[providerId]; + } + delete pluginProviders[providerId]; delete pluginProviderMetadata[providerId]; @@ -56,6 +81,11 @@ Singleton { return Object.keys(pluginProviders); } + // Get the live provider instance by ID + function getProviderInstance(providerId) { + return providerInstances[providerId] || null; + } + // Get provider component by ID function getProviderComponent(providerId) { return pluginProviders[providerId] || null; diff --git a/Services/UI/WallpaperService.qml b/Services/UI/WallpaperService.qml index d0840d707..52afaea6e 100644 --- a/Services/UI/WallpaperService.qml +++ b/Services/UI/WallpaperService.qml @@ -4,6 +4,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Commons +import qs.Services.Power import qs.Services.Theming import qs.Services.UI @@ -430,18 +431,27 @@ Singleton { } // ------------------------------------------------------------------- - function setRandomWallpaper() { + function setRandomWallpaper(screen) { Logger.d("Wallpaper", "setRandomWallpaper"); if (Settings.data.wallpaper.enableMultiMonitorDirectories) { - // Pick a random wallpaper per screen - for (var i = 0; i < Quickshell.screens.length; i++) { - var screenName = Quickshell.screens[i].name; - var wallpaperList = getWallpapersList(screenName); + if (screen === undefined) { + // Pick a random wallpaper per screen + for (var i = 0; i < Quickshell.screens.length; i++) { + var screenName = Quickshell.screens[i].name; + var wallpaperList = getWallpapersList(screenName); + if (wallpaperList.length > 0) { + var randomPath = _pickUnusedRandom(screenName, wallpaperList); + changeWallpaper(randomPath, screenName); + } + } + } else { + // Pick a random wallpaper for the specified screen + var wallpaperList = getWallpapersList(screen); if (wallpaperList.length > 0) { - var randomPath = _pickUnusedRandom(screenName, wallpaperList); - changeWallpaper(randomPath, screenName); + var randomPath = _pickUnusedRandom(screen, wallpaperList); + changeWallpaper(randomPath, screen); } } } else { @@ -450,7 +460,7 @@ Singleton { var wallpaperList = getWallpapersList(Screen.name); if (wallpaperList.length > 0) { var randomPath = _pickUnusedRandom("all", wallpaperList); - changeWallpaper(randomPath, undefined); + changeWallpaper(randomPath, screen); } } } @@ -1072,7 +1082,7 @@ Singleton { Timer { id: randomWallpaperTimer interval: Settings.data.wallpaper.randomIntervalSec * 1000 - running: Settings.data.wallpaper.automationEnabled + running: Settings.data.wallpaper.automationEnabled && !PowerProfileService.noctaliaPerformanceMode repeat: true onTriggered: setNextWallpaper() triggeredOnStart: false diff --git a/Shaders/frag/graph.frag b/Shaders/frag/graph.frag new file mode 100644 index 000000000..fd7afd16f --- /dev/null +++ b/Shaders/frag/graph.frag @@ -0,0 +1,114 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D dataSource; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + vec4 lineColor1; + vec4 lineColor2; + float count1; + float count2; + float scroll1; + float scroll2; + float lineWidth; + float graphFillOpacity; + float texWidth; + float resY; + float aaSize; +}; + +// Sample normalized value from data texture +// channel 0 = primary (R), channel 1 = secondary (G) +float fetchData(float idx, int ch) { + float i = clamp(idx, 0.0, texWidth - 1.0); + float u = (floor(i) + 0.5) / texWidth; + vec4 t = texture(dataSource, vec2(u, 0.5)); + return ch == 0 ? t.r : t.g; +} + +// Cubic Hermite interpolation with reduced tangent scale for smooth curves +float cubicHermite(float y0, float y1, float y2, float y3, float t) { + float m1 = (y2 - y0) * 0.25; + float m2 = (y3 - y1) * 0.25; + + float t2 = t * t; + float t3 = t2 * t; + return (2.0 * t3 - 3.0 * t2 + 1.0) * y1 + + (t3 - 2.0 * t2 + t) * m1 + + (-2.0 * t3 + 3.0 * t2) * y2 + + (t3 - t2) * m2; +} + +// Evaluate curve at fractional data index +float evalCurve(float dataIdx, int ch) { + float i = floor(dataIdx); + float t = dataIdx - i; + return cubicHermite( + fetchData(i - 1.0, ch), + fetchData(i, ch), + fetchData(i + 1.0, ch), + fetchData(i + 2.0, ch), + t + ); +} + +// Premultiplied alpha over compositing +vec4 blendOver(vec4 src, vec4 dst) { + return src + dst * (1.0 - src.a); +} + +void main() { + vec2 uv = qt_TexCoord0; + float normY = 1.0 - uv.y; // 0 = bottom, 1 = top + + vec4 result = vec4(0.0); + float halfW = lineWidth * 0.5; + + // Primary line + if (count1 >= 4.0) { + float segs = count1 - 3.0; + float di = 2.0 + scroll1 + uv.x * segs; + float pixStep1 = dFdx(di); + float cy = evalCurve(di, 0); + float cyNext = evalCurve(di + pixStep1, 0); + + // Fill below curve (gradient: opaque at top, transparent at bottom) + if (graphFillOpacity > 0.0 && normY <= cy) { + float a = graphFillOpacity * normY * lineColor1.a; + result = blendOver(vec4(lineColor1.rgb * a, a), result); + } + + // Perpendicular distance to the line segment between adjacent samples + float slope1 = (cyNext - cy) * resY; + float vDist1 = (normY - cy) * resY; + float dist1 = abs(vDist1) * inversesqrt(slope1 * slope1 + 1.0); + float sa = smoothstep(halfW + aaSize, halfW, dist1) * lineColor1.a; + result = blendOver(vec4(lineColor1.rgb * sa, sa), result); + } + + // Secondary line + if (count2 >= 4.0) { + float segs = count2 - 3.0; + float di = 2.0 + scroll2 + uv.x * segs; + float pixStep2 = dFdx(di); + float cy = evalCurve(di, 1); + float cyNext = evalCurve(di + pixStep2, 1); + + if (graphFillOpacity > 0.0 && normY <= cy) { + float a = graphFillOpacity * normY * lineColor2.a; + result = blendOver(vec4(lineColor2.rgb * a, a), result); + } + + float slope2 = (cyNext - cy) * resY; + float vDist2 = (normY - cy) * resY; + float dist2 = abs(vDist2) * inversesqrt(slope2 * slope2 + 1.0); + float sa = smoothstep(halfW + aaSize, halfW, dist2) * lineColor2.a; + result = blendOver(vec4(lineColor2.rgb * sa, sa), result); + } + + fragColor = result * qt_Opacity; +} diff --git a/Shaders/frag/wave_spectrum.frag b/Shaders/frag/wave_spectrum.frag new file mode 100644 index 000000000..c3de02d14 --- /dev/null +++ b/Shaders/frag/wave_spectrum.frag @@ -0,0 +1,74 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D dataSource; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + vec4 fillColor; + float count; + float texWidth; + float vertical; +}; + +// Sample amplitude from data texture (R channel) +float fetchData(float idx) { + float i = clamp(idx, 0.0, texWidth - 1.0); + float u = (floor(i) + 0.5) / texWidth; + return texture(dataSource, vec2(u, 0.5)).r; +} + +// Cubic Hermite interpolation for smooth wave curves +float cubicHermite(float y0, float y1, float y2, float y3, float t) { + float m1 = (y2 - y0) * 0.25; + float m2 = (y3 - y1) * 0.25; + float t2 = t * t; + float t3 = t2 * t; + return (2.0 * t3 - 3.0 * t2 + 1.0) * y1 + + (t3 - 2.0 * t2 + t) * m1 + + (-2.0 * t3 + 3.0 * t2) * y2 + + (t3 - t2) * m2; +} + +// Evaluate interpolated amplitude at fractional data index +float evalCurve(float dataIdx) { + float i = floor(dataIdx); + float t = dataIdx - i; + return cubicHermite( + fetchData(i - 1.0), + fetchData(i), + fetchData(i + 1.0), + fetchData(i + 2.0), + t + ); +} + +void main() { + vec2 uv = qt_TexCoord0; + + // Swap axes for vertical mode + float axisPos = (vertical > 0.5) ? uv.y : uv.x; + float crossPos = (vertical > 0.5) ? uv.x : uv.y; + + // Mirror: value[0] at center, value[count-1] at edges + float distFromCenter = abs(axisPos - 0.5) * 2.0; + float dataIdx = distFromCenter * max(count - 1.0, 1.0); + + // Interpolated amplitude, clamped to valid range + float amplitude = clamp(evalCurve(dataIdx), 0.0, 1.0); + + // Wave fills center ± amplitude/2 in the cross axis + float halfAmp = amplitude * 0.5; + float distFromMid = abs(crossPos - 0.5); + + // Antialiased edge (~1px smooth transition) + float edge = fwidth(crossPos) * 1.5; + float mask = smoothstep(halfAmp + edge, halfAmp - edge, distFromMid); + + // Premultiplied alpha output + float a = mask * fillColor.a; + fragColor = vec4(fillColor.rgb * a, a) * qt_Opacity; +} diff --git a/Shaders/qsb/graph.frag.qsb b/Shaders/qsb/graph.frag.qsb new file mode 100644 index 000000000..e89ec293f Binary files /dev/null and b/Shaders/qsb/graph.frag.qsb differ diff --git a/Shaders/qsb/wave_spectrum.frag.qsb b/Shaders/qsb/wave_spectrum.frag.qsb new file mode 100644 index 000000000..f8dcb6ae1 Binary files /dev/null and b/Shaders/qsb/wave_spectrum.frag.qsb differ diff --git a/Widgets/AudioSpectrum/NWaveSpectrum.qml b/Widgets/AudioSpectrum/NWaveSpectrum.qml index c0ef0a047..2fa0accf0 100644 --- a/Widgets/AudioSpectrum/NWaveSpectrum.qml +++ b/Widgets/AudioSpectrum/NWaveSpectrum.qml @@ -1,5 +1,5 @@ import QtQuick -import QtQuick.Shapes +import Quickshell import qs.Commons Item { @@ -14,85 +14,57 @@ Item { property bool showMinimumSignal: false property real minimumSignalValue: 0.05 // Default to 5% of height - // Reactive path that updates when values change - readonly property string svgPath: { - if (!values || !Array.isArray(values) || values.length === 0) { - return "M 0 0"; // Valid no-op path to avoid Qt triangulator crash on empty path - } + readonly property int valuesCount: (values && Array.isArray(values)) ? values.length : 0 + readonly property bool hasData: valuesCount >= 2 - // Apply minimum signal if enabled - const processedValues = showMinimumSignal ? values.map(v => v === 0 ? minimumSignalValue : v) : values; + // Data texture: one pixel per value, R channel = amplitude + Item { + id: dataRow + width: Math.max(root.valuesCount, 4) + height: 1 - // Create the mirrored values - const partToMirror = processedValues.slice(1).reverse(); - const mirroredValues = partToMirror.concat(processedValues); + Repeater { + model: dataRow.width - if (mirroredValues.length < 2) { - return "M 0 0"; // Valid no-op path to avoid Qt triangulator crash on empty path - } - - const count = mirroredValues.length; - - if (vertical) { - const stepY = height / (count - 1); - const centerX = width / 2; - const amplitude = width / 2; - - let xOffset = mirroredValues[0] * amplitude; - let path = `M ${centerX - xOffset} 0`; - - for (let i = 1; i < count; i++) { - const y = i * stepY; - xOffset = mirroredValues[i] * amplitude; - path += ` L ${centerX - xOffset} ${y}`; + Rectangle { + required property int index + x: index + width: 1 + height: 1 + color: { + if (index >= root.valuesCount) + return Qt.rgba(0, 0, 0, 1); + var v = root.values[index]; + if (v === undefined || v === null || !isFinite(v)) + v = 0; + if (root.showMinimumSignal && v === 0) + v = root.minimumSignalValue; + return Qt.rgba(Math.max(0, Math.min(1, v)), 0, 0, 1); + } } - - for (let i = count - 1; i >= 0; i--) { - const y = i * stepY; - xOffset = mirroredValues[i] * amplitude; - path += ` L ${centerX + xOffset} ${y}`; - } - - return path + " Z"; - } else { - const stepX = width / (count - 1); - const centerY = height / 2; - const amplitude = height / 2; - - let yOffset = mirroredValues[0] * amplitude; - let path = `M 0 ${centerY - yOffset}`; - - for (let i = 1; i < count; i++) { - const x = i * stepX; - yOffset = mirroredValues[i] * amplitude; - path += ` L ${x} ${centerY - yOffset}`; - } - - for (let i = count - 1; i >= 0; i--) { - const x = i * stepX; - yOffset = mirroredValues[i] * amplitude; - path += ` L ${x} ${centerY + yOffset}`; - } - - return path + " Z"; } } - Shape { - id: shape + ShaderEffectSource { + id: dataTex + sourceItem: dataRow + textureSize: Qt.size(dataRow.width, 1) + live: true + smooth: false + hideSource: true + } + + ShaderEffect { anchors.fill: parent - preferredRendererType: Shape.CurveRenderer - containsMode: Shape.FillContains + visible: root.hasData && root.width > 0 && root.height > 0 - ShapePath { - id: shapePath - fillColor: root.fillColor - strokeColor: root.strokeWidth > 0 ? root.strokeColor : "transparent" - strokeWidth: root.strokeWidth + property variant dataSource: dataTex + property color fillColor: root.fillColor + property real count: root.valuesCount + property real texWidth: dataRow.width + property real vertical: root.vertical ? 1.0 : 0.0 - PathSvg { - path: root.svgPath - } - } + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wave_spectrum.frag.qsb") + blending: true } } diff --git a/Widgets/NBox.qml b/Widgets/NBox.qml index 9f58a6367..16a010799 100644 --- a/Widgets/NBox.qml +++ b/Widgets/NBox.qml @@ -3,11 +3,28 @@ import qs.Commons // Rounded group container using the variant surface color. // To be used in side panels and settings panes to group fields or buttons. -Rectangle { +// Opacity is based on panelBackgroundOpacity but clamped to a minimum to avoid full transparency. + +Item { id: root - color: Color.mSurfaceVariant - radius: Style.radiusM - border.color: Style.boxBorderColor - border.width: Style.borderS + property color color: Color.mSurfaceVariant + property bool forceOpaque: false + property alias radius: bg.radius + property alias border: bg.border + + Rectangle { + id: bg + anchors.fill: parent + radius: Style.radiusM + border.color: Style.boxBorderColor + border.width: Style.borderS + color: { + if (forceOpaque) { + return root.color; + } + + return Color.smartAlpha(root.color); + } + } } diff --git a/Widgets/NCheckbox.qml b/Widgets/NCheckbox.qml index 68740799a..09ed49f7f 100644 --- a/Widgets/NCheckbox.qml +++ b/Widgets/NCheckbox.qml @@ -36,6 +36,7 @@ RowLayout { Rectangle { id: box + Layout.margins: Style.borderS implicitWidth: Math.round(root.baseSize) implicitHeight: Math.round(root.baseSize) radius: Style.iRadiusXS diff --git a/Widgets/NColorPicker.qml b/Widgets/NColorPicker.qml index 3118eae70..c005240f6 100644 --- a/Widgets/NColorPicker.qml +++ b/Widgets/NColorPicker.qml @@ -12,6 +12,7 @@ Rectangle { signal colorSelected(color color) + Layout.margins: Style.borderS implicitWidth: 150 implicitHeight: Math.round(Style.baseWidgetSize * 1.1) diff --git a/Widgets/NColorSlider.qml b/Widgets/NColorSlider.qml index 39d5e5ee2..f0397611b 100644 --- a/Widgets/NColorSlider.qml +++ b/Widgets/NColorSlider.qml @@ -93,7 +93,9 @@ Slider { anchors.centerIn: parent width: root.trackWidth height: bgContainer.height + visible: root.trackWidth > 0 && bgContainer.height > 0 preferredRendererType: Shape.CurveRenderer + asynchronous: true ShapePath { id: trackPath diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index a2a0fe5d6..c94f317e1 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -136,10 +136,12 @@ RowLayout { ComboBox { id: combo + Layout.margins: Style.borderS Layout.minimumWidth: Math.round(root.minimumWidth * Style.uiScaleRatio) Layout.preferredHeight: Math.round(root.preferredHeight * Style.uiScaleRatio) implicitWidth: Layout.minimumWidth model: root.model + textRole: "name" currentIndex: root.findIndexByKey(root.currentKey) onActivated: { diff --git a/Widgets/NDateTimeTokens.qml b/Widgets/NDateTimeTokens.qml index 5a7f6742e..496c70bc8 100644 --- a/Widgets/NDateTimeTokens.qml +++ b/Widgets/NDateTimeTokens.qml @@ -10,6 +10,7 @@ Rectangle { signal tokenClicked(string token) + Layout.margins: Style.borderS color: Color.mSurface border.color: Color.mOutline border.width: Style.borderS diff --git a/Widgets/NGraph.qml b/Widgets/NGraph.qml index 680f05783..24a849398 100644 --- a/Widgets/NGraph.qml +++ b/Widgets/NGraph.qml @@ -1,5 +1,5 @@ import QtQuick -import QtQuick.Shapes +import Quickshell import qs.Commons Item { @@ -23,9 +23,10 @@ Item { property real maxValue2: maxValue // Style settings - property real strokeWidth: 2 * Style.uiScaleRatio + property real strokeWidth: 1.5 property bool fill: true property real fillOpacity: 0.15 + property real antialiasing: 0.5 // Smooth scrolling interval (how often data updates) property int updateInterval: 1000 @@ -39,18 +40,16 @@ Item { readonly property bool hasData: values.length >= 4 readonly property bool hasData2: values2.length >= 4 - // Target max values (what we're animating toward) + // Scale animation state property real _targetMax1: maxValue property real _targetMax2: maxValue2 - - // Current animated max values (interpolated in timer when animateScale is true) property real _animMax1: maxValue property real _animMax2: maxValue2 onMaxValueChanged: { _targetMax1 = maxValue; if (animateScale && _ready1) { - _animTimer.start(); + _scaleTimer.start(); } else { _animMax1 = maxValue; } @@ -59,7 +58,7 @@ Item { onMaxValue2Changed: { _targetMax2 = maxValue2; if (animateScale && _ready2) { - _animTimer.start(); + _scaleTimer.start(); } else { _animMax2 = maxValue2; } @@ -69,31 +68,45 @@ Item { readonly property real _effectiveMax1: animateScale ? _animMax1 : maxValue readonly property real _effectiveMax2: animateScale ? _animMax2 : maxValue2 - // Animation state for primary line + // Scroll state (driven by NumberAnimation) property real _t1: 1.0 property bool _ready1: false property real _pred1: 0 - // Animation state for secondary line property real _t2: 1.0 property bool _ready2: false property real _pred2: 0 + // Frame-accurate scroll animations tied to Qt's render loop + NumberAnimation { + id: _scrollAnim1 + target: root + property: "_t1" + from: 0 + to: 1 + duration: root.updateInterval + } + + NumberAnimation { + id: _scrollAnim2 + target: root + property: "_t2" + from: 0 + to: 1 + duration: root.updateInterval + } + onValuesChanged: { if (values.length < 4) return; const last = values[values.length - 1]; const prev = values[values.length - 2]; - _pred1 = Math.max(minValue, last + (last - prev)); + _pred1 = Math.max(minValue, last + (last - prev) * 0.5); - if (!_ready1) { + if (!_ready1) _ready1 = true; - _t1 = 0; - } else { - _t1 = Math.max(0, _t1 - 1.0); - } - _animTimer.start(); + _scrollAnim1.restart(); } onValues2Changed: { @@ -102,219 +115,120 @@ Item { const last = values2[values2.length - 1]; const prev = values2[values2.length - 2]; - _pred2 = Math.max(minValue2, last + (last - prev)); + _pred2 = Math.max(minValue2, last + (last - prev) * 0.5); - if (!_ready2) { + if (!_ready2) _ready2 = true; - _t2 = 0; - } else { - _t2 = Math.max(0, _t2 - 1.0); - } - _animTimer.start(); + _scrollAnim2.restart(); } + // Scale animation timer (only needed for animateScale mode) Timer { - id: _animTimer + id: _scaleTimer interval: 16 repeat: true - property real _prevTime: 0 onTriggered: { - const now = Date.now(); - const elapsed = _prevTime > 0 ? (now - _prevTime) : 16; - _prevTime = now; - const dt = elapsed / root.updateInterval; + const scaleLerp = 0.15; + const threshold = 0.5; let stillAnimating = false; - // Scroll animation - if (root._t1 < 1.0) { - root._t1 = Math.min(1.0, root._t1 + dt); + if (Math.abs(root._animMax1 - root._targetMax1) > threshold) { + root._animMax1 += (root._targetMax1 - root._animMax1) * scaleLerp; stillAnimating = true; + } else if (root._animMax1 !== root._targetMax1) { + root._animMax1 = root._targetMax1; } - if (root._t2 < 1.0) { - root._t2 = Math.min(1.0, root._t2 + dt); + + if (Math.abs(root._animMax2 - root._targetMax2) > threshold) { + root._animMax2 += (root._targetMax2 - root._animMax2) * scaleLerp; stillAnimating = true; + } else if (root._animMax2 !== root._targetMax2) { + root._animMax2 = root._targetMax2; } - // Scale animation (lerp toward target) - synchronized with scroll - if (root.animateScale) { - const scaleLerp = 0.15; // Smooth lerp factor per frame - const threshold = 0.5; // Snap when close enough - - if (Math.abs(root._animMax1 - root._targetMax1) > threshold) { - root._animMax1 += (root._targetMax1 - root._animMax1) * scaleLerp; - stillAnimating = true; - } else if (root._animMax1 !== root._targetMax1) { - root._animMax1 = root._targetMax1; - } - - if (Math.abs(root._animMax2 - root._targetMax2) > threshold) { - root._animMax2 += (root._targetMax2 - root._animMax2) * scaleLerp; - stillAnimating = true; - } else if (root._animMax2 !== root._targetMax2) { - root._animMax2 = root._targetMax2; - } - } - - if (!stillAnimating) { - _prevTime = 0; + if (!stillAnimating) stop(); - } } } - // Convert a value to Y coordinate (with padding to keep values from touching edges) - function valueToY(val, minVal, maxVal) { + // Normalize a value to [0, 1] with padding applied + function _normalize(val, minVal, maxVal) { let range = maxVal - minVal; if (range <= 0) - return height / 2; + return 0.5; let padding = range * curvePadding; let paddedMin = minVal - padding; - let paddedMax = maxVal + padding; - let paddedRange = paddedMax - paddedMin; - let normalized = (val - paddedMin) / paddedRange; - return height - normalized * height; + let paddedRange = (maxVal + padding) - paddedMin; + return Math.max(0, Math.min(1, (val - paddedMin) / paddedRange)); } - // Build SVG path with cubic bezier curves (Catmull-Rom → Bezier conversion) - function buildSvg(vals, pred, minVal, maxVal, t, closeFill) { - if (!vals || vals.length < 4 || width <= 0 || height <= 0) - return "M 0 0"; + // Data texture built from Rectangles instead of Canvas. + // Each Rectangle is one data point, color-coded with normalized values. + // R channel = primary, G channel = secondary. + Item { + id: _dataRow + width: Math.max(root.values.length + 1, root.values2.length + 1, 4) + height: 1 - const n = vals.length; - const step = width / (n - 3); - if (!isFinite(step) || step <= 0) - return "M 0 0"; + Repeater { + model: _dataRow.width - // Build raw data points - let raw = []; - raw.push({ - x: (-2 - t) * step, - y: valueToY(vals[0], minVal, maxVal) - }); - raw.push({ - x: (-1 - t) * step, - y: valueToY(vals[1], minVal, maxVal) - }); - for (let i = 2; i < n; i++) { - raw.push({ - x: (i - 2 - t) * step, - y: valueToY(vals[i], minVal, maxVal) - }); + Rectangle { + required property int index + x: index + width: 1 + height: 1 + color: { + let r = 0, g = 0; + let n1 = root.values.length; + let n2 = root.values2.length; + let eMax1 = root._effectiveMax1; + let eMax2 = root._effectiveMax2; + + if (index < n1) + r = root._normalize(root.values[index], root.minValue, eMax1); + else if (n1 > 0) + r = root._normalize(root._pred1, root.minValue, eMax1); + + if (index < n2) + g = root._normalize(root.values2[index], root.minValue2, eMax2); + else if (n2 > 0) + g = root._normalize(root._pred2, root.minValue2, eMax2); + + return Qt.rgba(r, g, 0, 1); + } + } } - raw.push({ - x: (n - 2 - t) * step, - y: valueToY(pred, minVal, maxVal) - }); - - // Start at first point - let svg = `M ${raw[0].x} ${raw[0].y}`; - - // Catmull-Rom to cubic bezier: cp1 = p1 + (p2-p0)/6, cp2 = p2 - (p3-p1)/6 - for (let i = 0; i < raw.length - 1; i++) { - const p0 = raw[Math.max(i - 1, 0)]; - const p1 = raw[i]; - const p2 = raw[i + 1]; - const p3 = raw[Math.min(i + 2, raw.length - 1)]; - - const cp1x = p1.x + (p2.x - p0.x) / 6; - const cp1y = p1.y + (p2.y - p0.y) / 6; - const cp2x = p2.x - (p3.x - p1.x) / 6; - const cp2y = p2.y - (p3.y - p1.y) / 6; - - svg += ` C ${cp1x} ${cp1y} ${cp2x} ${cp2y} ${p2.x} ${p2.y}`; - } - - if (closeFill) { - const last = raw[raw.length - 1]; - svg += ` L ${last.x} ${height} L ${raw[0].x} ${height} Z`; - } - - return svg; } - // Reactive SVG paths — re-evaluated when any dependency changes - readonly property string _strokeSvg1: buildSvg(values, _pred1, minValue, _effectiveMax1, _t1, false) - readonly property string _fillSvg1: fill ? buildSvg(values, _pred1, minValue, _effectiveMax1, _t1, true) : "M 0 0" - readonly property string _strokeSvg2: buildSvg(values2, _pred2, minValue2, _effectiveMax2, _t2, false) - readonly property string _fillSvg2: fill ? buildSvg(values2, _pred2, minValue2, _effectiveMax2, _t2, true) : "M 0 0" + ShaderEffectSource { + id: _dataTex + sourceItem: _dataRow + textureSize: Qt.size(_dataRow.width, 1) + live: true + smooth: false + hideSource: true + } - Shape { + ShaderEffect { anchors.fill: parent - preferredRendererType: Shape.CurveRenderer + visible: (root.hasData || root.hasData2) && width > 0 && height > 0 - // Fill for primary line - ShapePath { - fillGradient: LinearGradient { - y1: 0 - y2: root.height + property variant dataSource: _dataTex + property color lineColor1: root.color + property color lineColor2: root.color2 + property real count1: root.values.length + property real count2: root.values2.length + property real scroll1: root._t1 + property real scroll2: root._t2 + property real lineWidth: root.strokeWidth + property real graphFillOpacity: root.fill ? root.fillOpacity : 0.0 + property real texWidth: _dataRow.width + property real resY: height + property real aaSize: root.antialiasing - GradientStop { - position: 0 - color: Qt.rgba(root.color.r, root.color.g, root.color.b, root.fillOpacity) - } - - GradientStop { - position: 1 - color: "transparent" - } - } - strokeWidth: -1 - strokeColor: "transparent" - - PathSvg { - path: root._fillSvg1 - } - } - - // Stroke for primary line - ShapePath { - strokeColor: root.color - strokeWidth: root.strokeWidth - fillColor: "transparent" - capStyle: ShapePath.RoundCap - joinStyle: ShapePath.RoundJoin - - PathSvg { - path: root._strokeSvg1 - } - } - - // Fill for secondary line - ShapePath { - fillGradient: LinearGradient { - y1: 0 - y2: root.height - - GradientStop { - position: 0 - color: Qt.rgba(root.color2.r, root.color2.g, root.color2.b, root.fillOpacity) - } - - GradientStop { - position: 1 - color: "transparent" - } - } - strokeWidth: -1 - strokeColor: "transparent" - - PathSvg { - path: root._fillSvg2 - } - } - - // Stroke for secondary line - ShapePath { - strokeColor: root.color2 - strokeWidth: root.strokeWidth - fillColor: "transparent" - capStyle: ShapePath.RoundCap - joinStyle: ShapePath.RoundJoin - - PathSvg { - path: root._strokeSvg2 - } - } + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/graph.frag.qsb") + blending: true } } diff --git a/Widgets/NGridView.qml b/Widgets/NGridView.qml index 7b7dbaab6..b1d375464 100644 --- a/Widgets/NGridView.qml +++ b/Widgets/NGridView.qml @@ -30,6 +30,9 @@ Item { property int gradientHeight: 16 property bool reserveScrollbarSpace: true + // Keep scrollbars visible whenever overflow exists (without forcing visibility when not scrollable) + property bool showScrollbarWhenScrollable: Settings.data.ui.scrollbarAlwaysVisible + // Available width for content (excludes scrollbar space when reserveScrollbarSpace is true) // Note: Always reserves space when enabled to avoid binding loops with cellWidth calculations readonly property real availableWidth: width - (reserveScrollbarSpace ? handleWidth + Style.marginXS : 0) @@ -311,7 +314,7 @@ Item { implicitHeight: 100 radius: root.handleRadius color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor - opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollBarActive ? (parent.active ? 1.0 : 0.0) : 0.0 + opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollBarActive ? ((root.showScrollbarWhenScrollable || parent.active) ? 1.0 : 0.0) : 0.0 Behavior on opacity { NumberAnimation { @@ -330,7 +333,7 @@ Item { implicitWidth: root.handleWidth implicitHeight: 100 color: root.trackColor - opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollBarActive ? (parent.active ? 0.3 : 0.0) : 0.0 + opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollBarActive ? ((root.showScrollbarWhenScrollable || parent.active) ? 0.3 : 0.0) : 0.0 radius: root.handleRadius / 2 Behavior on opacity { diff --git a/Widgets/NIconButton.qml b/Widgets/NIconButton.qml index 88be3ba3c..de57588dd 100644 --- a/Widgets/NIconButton.qml +++ b/Widgets/NIconButton.qml @@ -14,9 +14,10 @@ Item { property string tooltipText property string tooltipDirection: "auto" property bool allowClickWhenDisabled: false + property bool handleWheel: false property bool hovering: false - property color colorBg: Color.mSurfaceVariant + property color colorBg: Color.smartAlpha(Color.mSurfaceVariant) property color colorFg: Color.mPrimary property color colorBgHover: Color.mHover property color colorFgHover: Color.mOnHover @@ -122,6 +123,11 @@ Item { root.middleClicked(); } } - onWheel: wheel => root.wheel(wheel.angleDelta.y) + onWheel: wheel => { + if (root.handleWheel) { + root.wheel(wheel.angleDelta.y); + } + wheel.accepted = false; + } } } diff --git a/Widgets/NIconButtonHot.qml b/Widgets/NIconButtonHot.qml index 7eccbe1d7..6f3761dfe 100644 --- a/Widgets/NIconButtonHot.qml +++ b/Widgets/NIconButtonHot.qml @@ -22,7 +22,7 @@ Rectangle { property bool pressed: false // Color properties - property color colorBg: Color.mSurfaceVariant + property color colorBg: Color.smartAlpha(Color.mSurfaceVariant) property color colorFg: Color.mPrimary property color colorBgHover: Color.mHover property color colorFgHover: Color.mOnHover diff --git a/Widgets/NLabel.qml b/Widgets/NLabel.qml index 9a2937df3..626a3709e 100644 --- a/Widgets/NLabel.qml +++ b/Widgets/NLabel.qml @@ -35,21 +35,23 @@ ColumnLayout { } NText { - Layout.fillWidth: !root.showIndicator + id: labelText + Layout.fillWidth: true text: root.label pointSize: Style.fontSizeL font.weight: Style.fontWeightSemiBold color: labelColor wrapMode: Text.WordWrap - } - // Settings indicator - Loader { - active: root.showIndicator - sourceComponent: NSettingsIndicator { - show: true - tooltipText: root.indicatorTooltip || "" - Layout.alignment: Qt.AlignVCenter + // Settings indicator dot positioned right after the text content + Loader { + active: root.showIndicator + x: labelText.contentWidth + Style.marginXS + anchors.verticalCenter: parent.verticalCenter + sourceComponent: NSettingsIndicator { + show: true + tooltipText: root.indicatorTooltip || "" + } } } } diff --git a/Widgets/NLinearGauge.qml b/Widgets/NLinearGauge.qml new file mode 100644 index 000000000..93a039843 --- /dev/null +++ b/Widgets/NLinearGauge.qml @@ -0,0 +1,28 @@ +import QtQuick +import qs.Commons +import qs.Services.UI + +Rectangle { + id: root + + // Mandatory properties for gauges + required property int orientation // Qt.Vertical || Qt.Horizontal + required property real ratio // 0..1 + + radius: orientation === Qt.Vertical ? width / 2 : height / 2 + color: Color.mOutline + property color fillColor: Color.mPrimary + + // Fill that grows from bottom if vertical and left if horizontal. + // Snap to zero if the computed pixel length is sub-pixel (< 1px). + Rectangle { + readonly property real clampedRatio: Math.min(1, Math.max(0, root.ratio)) + readonly property real rawFill: (orientation === Qt.Vertical ? root.height : root.width) * clampedRatio + width: orientation === Qt.Vertical ? root.width : (rawFill < 1 ? 0 : rawFill) + height: orientation === Qt.Vertical ? (rawFill < 1 ? 0 : rawFill) : root.height + radius: root.radius + color: root.fillColor + anchors.bottom: orientation === Qt.Vertical ? parent.bottom : undefined + anchors.left: parent.left + } +} diff --git a/Widgets/NListView.qml b/Widgets/NListView.qml index b21b12fa3..4f4da6b6a 100644 --- a/Widgets/NListView.qml +++ b/Widgets/NListView.qml @@ -26,6 +26,9 @@ Item { property int gradientHeight: 16 property bool reserveScrollbarSpace: true + // Keep scrollbars visible whenever overflow exists (without forcing visibility when not scrollable) + property bool showScrollbarWhenScrollable: Settings.data.ui.scrollbarAlwaysVisible + // Available width for content (excludes scrollbar space when reserveScrollbarSpace is true) readonly property real availableWidth: width - (reserveScrollbarSpace ? handleWidth + Style.marginXS : 0) @@ -221,7 +224,7 @@ Item { implicitHeight: 100 radius: root.handleRadius color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor - opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollBarActive ? (parent.active ? 1.0 : 0.0) : 0.0 + opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollBarActive ? ((root.showScrollbarWhenScrollable || parent.active) ? 1.0 : 0.0) : 0.0 Behavior on opacity { NumberAnimation { @@ -240,7 +243,7 @@ Item { implicitWidth: root.handleWidth implicitHeight: 100 color: root.trackColor - opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollBarActive ? (parent.active ? 0.3 : 0.0) : 0.0 + opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollBarActive ? ((root.showScrollbarWhenScrollable || parent.active) ? 0.3 : 0.0) : 0.0 radius: root.handleRadius / 2 Behavior on opacity { diff --git a/Widgets/NPluginSettingsPopup.qml b/Widgets/NPluginSettingsPopup.qml index 85bb0d64d..e39463d70 100644 --- a/Widgets/NPluginSettingsPopup.qml +++ b/Widgets/NPluginSettingsPopup.qml @@ -14,7 +14,8 @@ Popup { property var screen: null readonly property real maxHeight: (screen ? screen.height : (parent ? parent.height : 800)) * 0.8 - width: Math.max(settingsContent.implicitWidth + padding * 2, 600 * Style.uiScaleRatio) + property real _minWidth: 600 * Style.uiScaleRatio + width: _minWidth height: Math.min(settingsContent.implicitHeight + padding * 2, maxHeight) padding: Style.marginXL @@ -143,6 +144,8 @@ Popup { "pluginApi": currentPluginApi }); + var preferred = (settingsLoader.item && settingsLoader.item.preferredWidth !== undefined) ? settingsLoader.item.preferredWidth + padding * 2 : 0; + width = Math.max(preferred, _minWidth); open(); } } diff --git a/Widgets/NScrollView.qml b/Widgets/NScrollView.qml index aa935560a..caaa798df 100644 --- a/Widgets/NScrollView.qml +++ b/Widgets/NScrollView.qml @@ -23,6 +23,8 @@ ScrollView { property int gradientHeight: 16 property bool reserveScrollbarSpace: true property real userRightPadding: 0 + // Keep scrollbars visible whenever overflow exists (without forcing visibility when not scrollable) + property bool showScrollbarWhenScrollable: Settings.data.ui.scrollbarAlwaysVisible // Scroll speed multiplier for mouse wheel (1.0 = default, higher = faster) property real wheelScrollMultiplier: 2.0 @@ -141,7 +143,7 @@ ScrollView { implicitHeight: 100 radius: root.handleRadius color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor - opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollable ? (parent.active ? 1.0 : 0.0) : 0.0 + opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollable ? ((root.showScrollbarWhenScrollable || parent.active) ? 1.0 : 0.0) : 0.0 Behavior on opacity { NumberAnimation { @@ -160,7 +162,7 @@ ScrollView { implicitWidth: root.handleWidth implicitHeight: 100 color: root.trackColor - opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollable ? (parent.active ? 0.3 : 0.0) : 0.0 + opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollable ? ((root.showScrollbarWhenScrollable || parent.active) ? 0.3 : 0.0) : 0.0 radius: root.handleRadius / 2 Behavior on opacity { @@ -184,7 +186,7 @@ ScrollView { implicitHeight: root.handleWidth radius: root.handleRadius color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor - opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.horizontalScrollable ? (parent.active ? 1.0 : 0.0) : 0.0 + opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.horizontalScrollable ? ((root.showScrollbarWhenScrollable || parent.active) ? 1.0 : 0.0) : 0.0 Behavior on opacity { NumberAnimation { @@ -203,7 +205,7 @@ ScrollView { implicitWidth: 100 implicitHeight: root.handleWidth color: root.trackColor - opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.horizontalScrollable ? (parent.active ? 0.3 : 0.0) : 0.0 + opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.horizontalScrollable ? ((root.showScrollbarWhenScrollable || parent.active) ? 0.3 : 0.0) : 0.0 radius: root.handleRadius / 2 Behavior on opacity { diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index 646eab089..6ad6fd1f4 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -168,10 +168,12 @@ RowLayout { ComboBox { id: combo + Layout.margins: Style.borderS Layout.minimumWidth: Math.round(root.minimumWidth * Style.uiScaleRatio) Layout.preferredHeight: Math.round(root.preferredHeight * Style.uiScaleRatio) implicitWidth: Layout.minimumWidth model: root.activeModel + textRole: "name" currentIndex: findIndexInActiveModel(currentKey) onActivated: { if (combo.currentIndex >= 0 && root.activeModel && combo.currentIndex < root.activeModel.count) { diff --git a/Widgets/NSectionEditor.qml b/Widgets/NSectionEditor.qml index 7268b3de4..ee770ec03 100644 --- a/Widgets/NSectionEditor.qml +++ b/Widgets/NSectionEditor.qml @@ -586,6 +586,16 @@ NBox { Layout.preferredHeight: Style.baseWidgetSize * 0.5 } + // CPU intensive indicator icon + NIcon { + visible: root.widgetRegistry && root.widgetRegistry.isCpuIntensive(modelData.id) + icon: "cpu-intensive" + pointSize: Style.fontSizeXXS + color: root.getWidgetColor(modelData)[1] + Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.5 : 0 + Layout.preferredHeight: Style.baseWidgetSize * 0.5 + } + RowLayout { spacing: 0 Layout.preferredWidth: buttonsCount * buttonsWidth * Style.uiScaleRatio diff --git a/Widgets/NSlider.qml b/Widgets/NSlider.qml index 4c6edef20..8732d7403 100644 --- a/Widgets/NSlider.qml +++ b/Widgets/NSlider.qml @@ -41,7 +41,9 @@ Slider { // Background track Shape { anchors.fill: parent + visible: bgContainer.width > 0 && bgContainer.height > 0 preferredRendererType: Shape.CurveRenderer + asynchronous: true ShapePath { id: bgPath @@ -119,8 +121,9 @@ Slider { Shape { width: bgContainer.fillWidth height: bgContainer.height - visible: bgContainer.fillWidth > 0 + visible: bgContainer.fillWidth > 0 && bgContainer.height > 0 preferredRendererType: Shape.CurveRenderer + asynchronous: true clip: true ShapePath { diff --git a/Widgets/NSpinBox.qml b/Widgets/NSpinBox.qml index 3d7141905..36130fa18 100644 --- a/Widgets/NSpinBox.qml +++ b/Widgets/NSpinBox.qml @@ -91,6 +91,7 @@ RowLayout { // Main spinbox container Rectangle { id: spinBoxContainer + Layout.margins: Style.borderS implicitWidth: 120 implicitHeight: Math.round((root.baseSize - 4) / 2) * 2 radius: Style.iRadiusS @@ -372,7 +373,7 @@ RowLayout { top: root.to } - Keys.onReturnPressed: { + onAccepted: { applyValue(); focus = false; } diff --git a/Widgets/NTabBar.qml b/Widgets/NTabBar.qml index 89921b606..c382ce118 100644 --- a/Widgets/NTabBar.qml +++ b/Widgets/NTabBar.qml @@ -66,9 +66,10 @@ Rectangle { } // Styling + Layout.margins: Style.borderS implicitWidth: tabRow.implicitWidth + (margins * 2) implicitHeight: tabHeight + (margins * 2) - color: Color.mSurfaceVariant + color: Color.smartAlpha(Color.mSurfaceVariant) radius: Style.iRadiusM RowLayout { diff --git a/Widgets/NTabButton.qml b/Widgets/NTabButton.qml index e0cf4a233..d40314d41 100644 --- a/Widgets/NTabButton.qml +++ b/Widgets/NTabButton.qml @@ -33,8 +33,8 @@ Rectangle { topRightRadius: isLast ? Style.iRadiusM : Style.iRadiusXXXS bottomRightRadius: isLast ? Style.iRadiusM : Style.iRadiusXXXS - color: root.isHovered ? Color.mHover : (root.checked ? Color.mPrimary : Color.mSurface) - border.color: Color.mOutline + color: root.isHovered ? Color.mHover : (root.checked ? Color.mPrimary : Color.smartAlpha(Color.mSurface)) + border.color: root.checked ? Color.mPrimary : Color.mOutline border.width: Style.borderS Behavior on color { diff --git a/Widgets/NText.qml b/Widgets/NText.qml index fa2c71682..e7e283540 100644 --- a/Widgets/NText.qml +++ b/Widgets/NText.qml @@ -18,11 +18,13 @@ Text { } return fontScale; } + property var features: ({}) opacity: enabled ? 1.0 : 0.6 font.family: root.family font.weight: Style.fontWeightMedium font.pointSize: Math.max(1, root.pointSize * fontScale) + font.features: root.features color: Color.mOnSurface elide: Text.ElideRight wrapMode: Text.NoWrap diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index ba917c5d8..66fef69a0 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -56,6 +56,7 @@ ColumnLayout { Layout.fillWidth: true Layout.minimumWidth: root.minimumInputWidth + Layout.margins: Style.borderS implicitHeight: Style.baseWidgetSize * 1.1 * Style.uiScaleRatio // This is important - makes the control accept focus @@ -112,7 +113,7 @@ ColumnLayout { mouse.accepted = true; } onWheel: wheel => { - wheel.accepted = true; + wheel.accepted = false; } } @@ -121,7 +122,7 @@ ColumnLayout { id: inputContainer anchors.fill: parent anchors.leftMargin: Style.marginM - anchors.rightMargin: Style.marginM + anchors.rightMargin: 0 clip: true z: 1 @@ -205,7 +206,7 @@ ColumnLayout { mouse.accepted = true; } onWheel: wheel => { - wheel.accepted = true; + wheel.accepted = false; } } } diff --git a/Widgets/NToggle.qml b/Widgets/NToggle.qml index 1770d4908..744e69a15 100644 --- a/Widgets/NToggle.qml +++ b/Widgets/NToggle.qml @@ -45,6 +45,7 @@ RowLayout { id: switcher Layout.alignment: Qt.AlignVCenter + Layout.margins: Style.borderS implicitWidth: Math.round(root.baseSize * .85) * 2 implicitHeight: Math.round(root.baseSize * .5) * 2 diff --git a/flake.lock b/flake.lock index 71ed28bfc..4694b91c7 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1771848320, - "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", + "lastModified": 1772963539, + "narHash": "sha256-9jVDGZnvCckTGdYT53d/EfznygLskyLQXYwJLKMPsZs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2fc6539b481e1d2569f25f8799236694180c0993", + "rev": "9dcb002ca1690658be4a04645215baea8b95f31d", "type": "github" }, "original": { @@ -20,14 +20,15 @@ "inputs": { "nixpkgs": [ "nixpkgs" - ] + ], + "systems": "systems" }, "locked": { - "lastModified": 1772227064, - "narHash": "sha256-f821ZSoGpa/aXrWq0gPpea9qBnX8KDyavGKkptz2Mog=", + "lastModified": 1773175685, + "narHash": "sha256-YOkWzVq7opym1ovJvSCvqpG6OCDGJwPo/EPeRxcGay4=", "owner": "noctalia-dev", "repo": "noctalia-qs", - "rev": "0741d27d2f7db567270f139c5d1684614ecf9863", + "rev": "6b9eceefde3d47ca83c544b54bcdd358be4cbd2f", "type": "github" }, "original": { @@ -41,6 +42,21 @@ "nixpkgs": "nixpkgs", "noctalia-qs": "noctalia-qs" } + }, + "systems": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index cd76d3004..d6bb08cf8 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,6 @@ system: nixpkgs.legacyPackages.${system}.appendOverlays [ self.overlays.default - noctalia-qs.overlays.default ] ); in @@ -47,12 +46,15 @@ ]; in mkDate (self.lastModifiedDate or "19700101") + "_" + (self.shortRev or "dirty"); + quickshell = noctalia-qs.packages.${prev.stdenv.hostPlatform.system}.default; }; }; }; devShells = eachSystem (system: { - default = pkgsFor.${system}.callPackage ./nix/shell.nix { }; + default = pkgsFor.${system}.callPackage ./nix/shell.nix { + quickshell = noctalia-qs.packages.${system}.default; + }; }); homeModules.default = diff --git a/nix/package.nix b/nix/package.nix index 4ae7e80a0..3533b25ca 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -8,6 +8,7 @@ ddcutil wlsunset wl-clipboard + wlr-randr imagemagick wget (python3.withPackages (pp: lib.optional calendarSupport pp.pygobject3)) @@ -25,6 +26,7 @@ ddcutil, wlsunset, wl-clipboard, + wlr-randr, imagemagick, wget, python3, diff --git a/shell.qml b/shell.qml index de617a1bf..f21fb2c4b 100644 --- a/shell.qml +++ b/shell.qml @@ -107,7 +107,6 @@ ShellRoot { Qt.callLater(function () { LocationService.init(); NightLightService.apply(); - HooksService.init(); BluetoothService.init(); IdleInhibitorService.init(); IdleService.init(); @@ -179,6 +178,7 @@ ShellRoot { running: false interval: 1500 onTriggered: { + HooksService.init(); FontService.init(); UpdateService.init(); showWizardOrChangelog();