Compare commits

...

262 Commits

Author SHA1 Message Date
93dd3ee2a3 Make empty notification list transparent 2025-12-01 10:42:42 +01:00
97fb7bf0be Fix toasts 2025-12-01 10:42:42 +01:00
d27f0c2486 Add inner bar implementation for notifications 2025-12-01 10:42:41 +01:00
0397cadf91 Use a BarItem for the notification pill 2025-12-01 10:42:01 +01:00
Ly-sec
85fca41c50 README: add llego to the supporter list <3 (I'm sorry I forgot) 2025-11-30 20:41:38 +01:00
ItsLemmy
b6c1f6e90a autofmt 2025-11-30 14:36:23 -05:00
ItsLemmy
5d3c91f3ad i18n: added missing calendar translations + fixed required card. 2025-11-30 14:35:58 -05:00
Ly-sec
5b73ae6bcb Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-11-30 20:27:26 +01:00
Ly-sec
d74cbe356b i18n: tooltips describe object, not function 2025-11-30 20:27:21 +01:00
ItsLemmy
5c2d4f4412 Merge branch 'plugin-system' 2025-11-30 14:26:34 -05:00
ItsLemmy
e972e1f7aa Cards & Settings refactoring
- All cards now live in Modules/Cards
- CalendarPanel is now called ClockPanel
- Added a way to ease settings migration in separate QML files
2025-11-30 14:26:09 -05:00
Lysec
13af9227c9 Merge pull request #927 from lonerOrz/fix/re-cc
fix(cc): registry enableColorization
2025-11-30 20:10:05 +01:00
loner
1139addd58 fix(cc): registry enableColorization 2025-12-01 03:03:46 +08:00
Ly-sec
7f88725023 NComboBox: fix clicking issue 2025-11-30 19:48:43 +01:00
ItsLemmy
087c9b4ced SetupWizard: improve look of the thumbnails strip below the big image 2025-11-30 11:56:33 -05:00
ItsLemmy
0d2b93dee1 Lockscreen: antialiasing on bg image 2025-11-30 11:46:47 -05:00
ItsLemmy
f04622ade7 autoformat 2025-11-30 11:46:33 -05:00
ItsLemmy
925bbe7a5e NImageRounded: back to using a custom shader as it looks much better than ClippingRectangle.
It seems ClippingRectangle has issues with fractional pixes.
2025-11-30 11:46:18 -05:00
Ly-sec
a773300469 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-11-30 17:08:01 +01:00
Ly-sec
759539c101 i18n: add missing translations 2025-11-30 17:07:53 +01:00
ItsLemmy
a84525ea52 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-11-30 10:40:31 -05:00
Lysec
a17082d27f Merge pull request #909 from lonerOrz/feat/colorization
Reconstruct the control center icon colorization
2025-11-30 16:39:10 +01:00
ItsLemmy
c7f947d235 Settings: added a launcher button for the default/new user settings. + gitignore cleanup 2025-11-30 10:36:17 -05:00
Ly-sec
eaff0c6434 i18n: ColorSchemeTab 2025-11-30 16:15:04 +01:00
ItsLemmy
a81205f444 Hyprland: attempt to fix potential keyboard issue. 2025-11-30 09:47:14 -05:00
Ly-sec
d738f14a81 AboutTab: possible commit display for nixos 2025-11-30 15:04:09 +01:00
Ly-sec
94132dce6d TemplateProcessor: fix user-defined template generated colors from predefined colorschemes 2025-11-30 14:51:34 +01:00
Ly-sec
946c8883ca TemplateProcessor: fix user-defined template generation with wallpaper colors 2025-11-30 14:41:16 +01:00
Ly-sec
225e6d3914 AboutTab: clean up logging 2025-11-30 13:47:01 +01:00
Ly-sec
1a7ab224ca Launcher: add pin button to grid view 2025-11-30 13:44:51 +01:00
Ly-sec
1a2de1da11 AboutTab: small fixes 2025-11-30 13:44:29 +01:00
Lysec
80b93ab895 Merge pull request #913 from bokicoder/patch-1
Update visibility condition for pin/unpin button
2025-11-30 13:41:34 +01:00
Lysec
111170fbee Merge pull request #921 from bokicoder/main
Nix: add `qtmultimedia` dependency
2025-11-30 13:36:49 +01:00
wxlyyy
92853b4700 Nix: add qtmultimedia dependency 2025-11-30 20:24:20 +08:00
Ly-sec
67b4971b65 Matugen/emacs: more logic fixes 2025-11-30 12:42:47 +01:00
Ly-sec
2a6b236faf SoundService: switch to qt6-multimedia 2025-11-30 12:35:34 +01:00
Ly-sec
ca04156375 TemplateProcessor: fix emacs template logic 2025-11-30 12:20:11 +01:00
Ly-sec
9266ccfec4 AboutTab: one more possible fix for arch commit detection 2025-11-30 11:47:35 +01:00
Ly-sec
5b5d41acf8 AboutTab: possible fix for arch commit with git versions 2025-11-30 11:32:54 +01:00
Ly-sec
f52c4491b8 AboutTab: fix arch commit detection
AboutTab: remove download button
2025-11-30 11:17:44 +01:00
Ly-sec
4887be96f5 AboutTab: fix arch commit detection
GitHubService: add optional TOKEN auth
2025-11-30 11:06:04 +01:00
Ly-sec
6aca04cddb AboutTab: add git commit if using -git version 2025-11-30 10:54:20 +01:00
ItsLemmy
3f00bec8f4 Wallpaper: bring back customizable default wallpaper that is NOT monitor specific 2025-11-30 00:18:15 -05:00
ItsLemmy
ae2bf590ee Merge branch 'mangowc-refactor' 2025-11-29 23:50:34 -05:00
ItsLemmy
e0f38ff80b Mango: refactored mango service, much better but not perfect. 2025-11-29 23:48:19 -05:00
ItsLemmy
4f9ba6f601 DarkMode: simplify follow the color scheme, no need for a visual active state. 2025-11-29 23:04:49 -05:00
Lemmy
1c9d659635 Merge pull request #912 from notiant/patch-1
Reduce tooltip delay if bar widget doesn't expand
2025-11-29 20:00:32 -05:00
ItsLemmy
8f7d2f28f2 SmartPanel: fix edge case where dynamic content size may trigger dual axis animations on first open. 2025-11-29 19:58:13 -05:00
ItsLemmy
5f175a4f9a Panels: animations direction fixes 2025-11-29 18:30:54 -05:00
ItsLemmy
6fb840ef0b Default settings 2025-11-29 18:29:15 -05:00
ItsLemmy
017a5a6f91 Matugen: improved wallpaper filepath escaping to ensure it works well with potential non standard characters. 2025-11-29 18:01:33 -05:00
ItsLemmy
5451985e48 Notifications: Only delete cached images that are in our cache directory 2025-11-29 17:52:34 -05:00
ItsLemmy
fe25840dfa Wallpaper: restore directory per monitor settings. 2025-11-29 16:18:33 -05:00
Lysec
078195f54b Merge pull request #916 from notiant/patch-2
Add missing translations for filepicker tooltips
2025-11-29 18:58:55 +01:00
Lysec
669665a5af Merge pull request #914 from bokicoder/main
Nix: update dependencies
2025-11-29 18:58:36 +01:00
notiant
260b2e9a11 Add missing translations for filepicker tooltips 2025-11-29 18:52:05 +01:00
wxlyyy
dc1c0e8f47 Nix: update dependencies 2025-11-30 00:29:51 +08:00
ItsLemmy
30db679207 Wallpapers: moved persistent data to their own file in ~/.cache/noctalia/wallpapers.json !! no migration path, user will have to set their wallpaper at least once !! 2025-11-29 11:22:53 -05:00
ItsLemmy
993b6bc422 Settings/State: Moved state IPC logic to ShellState.qml. 2025-11-29 11:04:44 -05:00
bokicoder
25bd796d7d Update visibility condition for pin/unpin button 2025-11-29 23:51:17 +08:00
notiant
05ceff017d Reduce tooltip delay if bar widget doesn't expand 2025-11-29 16:45:05 +01:00
ItsLemmy
9d4ac03d21 Removed fonts dependencies (now using Qt font as default) and removed some complex settings migration code. 2025-11-29 10:38:06 -05:00
ItsLemmy
588a5782ae Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-11-29 10:04:30 -05:00
ItsLemmy
ddfde344bc AboutTab: caching circular images 2025-11-29 10:04:28 -05:00
Lysec
69b9ecbb30 Merge pull request #911 from osp54/i18n/update-uk-translations
i18n: Improve Ukrainian localization
2025-11-29 15:59:11 +01:00
osp54
c9e479275c i18n: Improve Ukrainian localization 2025-11-29 16:46:03 +02:00
ItsLemmy
ad755bb3fb Merge branch 'fix-crash-on-close' 2025-11-29 09:39:05 -05:00
ItsLemmy
6d6261ca00 Merge branch 'unified-panel-content' 2025-11-29 09:38:11 -05:00
loner
9a9ebf11fb i18n(ControlCenter): Update translations for colorization switch 2025-11-29 21:58:02 +08:00
loner
aabe251f0d feat(ControlCenter): Implement master colorization switch 2025-11-29 21:57:57 +08:00
loner
59f70e803b feat(ControlCenter): Prioritize distro logo and preserve custom icon settings 2025-11-29 21:57:53 +08:00
loner
01d42e55f3 feat(controlcenter): Add system icon colorization 2025-11-29 21:57:49 +08:00
Ly-sec
0ab8458ca2 NIconPicker: replace GridView with NGridView 2025-11-29 14:11:18 +01:00
Ly-sec
cdc5725e1b NIconPicker: add vertical scrollbar 2025-11-29 13:42:50 +01:00
Ly-sec
d53a3d8de2 SchemeDownloader: add support for spaces in names 2025-11-29 13:36:37 +01:00
Ly-sec
e627e67463 ColorSchemeTab: rename Rosepine to Rose Pine 2025-11-29 13:09:41 +01:00
Ly-sec
e07f2d34c0 CompositorService: lockAndSuspend - wait for lock before suspending 2025-11-29 13:05:24 +01:00
Ly-sec
75b17b9185 i18n: Compositor Theming 2025-11-29 12:42:59 +01:00
Ly-sec
9d5ac132c7 i18n: niri template 2025-11-29 12:34:16 +01:00
Ly-sec
7b091ad7c5 Launcher: fix warning 2025-11-29 12:30:41 +01:00
Ly-sec
366c867f94 Matugen/niri: added 2025-11-29 12:26:18 +01:00
Lysec
fd9341d2f1 Merge pull request #908 from oluijks/fix/color-temp-widgets
fix(night-light): replace day/night color temperature inputs with sliders
2025-11-29 12:07:19 +01:00
Ly-sec
7366298026 Matugen/Emacs: added 2025-11-29 12:02:42 +01:00
Olaf Luijks
17b09739ad fix(night-light): replace day/night color temperature inputs with sliders
- use separate sliders for night and day temperatures
- apply changes on slider release to avoid harsh flashing
- add per-slider descriptions and update translations
2025-11-29 10:25:05 +01:00
Ly-sec
3db394c80a Autoformat 2025-11-29 08:30:32 +01:00
Ly-sec
0d6b70a4c1 IPC: notifications - add removeOldestHistory 2025-11-29 08:30:12 +01:00
Lysec
ce7a412956 Merge pull request #898 from oluijks/feat/notification-tabs
feat: add date-range tabs to the notification history panel
2025-11-29 08:22:28 +01:00
Lysec
43ecd3ce9b Merge pull request #896 from eric-handley/feat/improve-emoji-selector
Improve >emoji selector with category drawers
2025-11-29 08:06:25 +01:00
Lemmy
5d70941a24 Merge pull request #907 from notiant/patch-1
Prevent empty bar pills from expanding
2025-11-28 22:15:17 -05:00
Eric Handley
1242082a9e fix: QFont warning spam 2025-11-28 17:56:05 -08:00
Eric Handley
4cc6d8b54e fix: emoji grid alignment on right side 2025-11-28 17:54:25 -08:00
Eric Handley
2867048d9b fix: arrow key navigation + use tab to cycle through categories 2025-11-28 17:51:21 -08:00
notiant
9fd914875c Prevent expanding empty bar pills 2025-11-29 01:02:39 +01:00
Ly-sec
aeee91d08a BatterySettings: add option to pick which battery is being shown
BatteryPanel: remove redundant things
2025-11-28 20:55:50 +01:00
Lysec
46f881026e Merge pull request #895 from lonerOrz/fis/lockkey
fix: Caps Off OSD color inconsistency with Num Off OSD
2025-11-28 13:54:52 +01:00
Ly-sec
4301eae37d Launcher: add fuzzy sort to > commands (fix #894) 2025-11-28 13:45:42 +01:00
Olaf Luijks
f6080b9aa7 chore: remove silly comments 2025-11-28 10:07:07 +01:00
Olaf Luijks
aa892fceab fix(notifications): harden history date tabs for empty lists 2025-11-28 09:55:07 +01:00
Olaf Luijks
4e5046eb91 feat(notifications): add date-range tabs to history panel 2025-11-28 09:48:22 +01:00
Eric Handley
816689dca2 fix: better interface scaling 2025-11-28 00:05:02 -08:00
Eric Handley
4812d9d1e6 feat: add flag category 2025-11-27 23:50:19 -08:00
loner
69004c072c fix: Caps Off OSD color inconsistency with Num Off OSD 2025-11-28 15:28:21 +08:00
Eric Handley
76982e5de6 fix: always open to "recent" tab 2025-11-27 23:25:01 -08:00
Eric Handley
6b27db0d4f fix: remove unnecessary fallbacks and redundant code 2025-11-27 23:21:48 -08:00
Eric Handley
ff78afeb82 feat: upgrade to gemoji database 2025-11-27 23:21:36 -08:00
Eric Handley
8d495cea3a fix: correct sushi emoji character in emoji.json 2025-11-27 23:21:36 -08:00
Eric Handley
722539796e fix: increase emoji size in grid view 2025-11-27 23:21:36 -08:00
Eric Handley
5a9cebf420 fix: show empty state for unused recent emojis 2025-11-27 23:21:36 -08:00
Eric Handley
1d74157d15 fix: force grid view when browsing emoji categories 2025-11-27 23:21:36 -08:00
Eric Handley
238b2f3ea3 feat: add category tab bar UI to emoji selector 2025-11-27 23:21:36 -08:00
Eric Handley
c6b28bec4d feat: add category-based browsing to emoji selector 2025-11-27 23:21:36 -08:00
Eric Handley
9d25f9c9e7 feat: add NIconTabButton widget for icon-only tabs 2025-11-27 22:43:11 -08:00
Lysec
5e205ad69a Merge pull request #892 from oluijks/fix/label-html-rendering
feat/fix: render label descriptions as styled text
2025-11-28 01:36:59 +01:00
Olaf Luijks
3ec973ca21 feat: render label descriptions as styled text 2025-11-28 01:27:44 +01:00
Ly-sec
a177031265 Weather: check for more weather states 2025-11-28 01:26:51 +01:00
ItsLemmy
a48e4dcecd Panels: went back to have panel's content drawn in main screen instead of separate PanelWindow 2025-11-27 19:18:34 -05:00
Ly-sec
a4193382df MediaMini: center icons 2025-11-28 00:54:09 +01:00
Lysec
9f015ebd9a Merge pull request #868 from EmmetZ/mediamini-icon
fix(MediaMini): make icon smaller when inside progress ring
2025-11-28 00:51:27 +01:00
ItsLemmy
0e46c4bb2b Notification: create layer on demand 2025-11-27 14:28:57 -05:00
Ly-sec
f66e063d5a i18n: update SystemMonitorTab 2025-11-27 16:14:16 +01:00
Ly-sec
3f02b28ecc SystemMonitorTab: add network polling 2025-11-27 16:10:36 +01:00
ItsLemmy
bfc5afa947 polling translations 2025-11-27 09:51:59 -05:00
ItsLemmy
af6ef8e763 i18n Japanese: fixing bad merge 2025-11-27 09:51:27 -05:00
Ly-sec
4e4a974f2c i18n-ja: add polling translation 2025-11-27 15:07:00 +01:00
Ly-sec
7cb293733c SystemMonitorTab: add polling option 2025-11-27 15:06:17 +01:00
ItsLemmy
2fe915e3bc NSectionEditor/ControlCenter: Allow up to 10 widgets if using a single side. 2025-11-27 09:00:19 -05:00
ItsLemmy
fd17032fe5 ShortcutCard: fixed typo introduced by #882 2025-11-27 08:45:42 -05:00
Lemmy
ee2ff95de0 Merge pull request #882 from alaughlin/hide-empty-shortcut-boxes
Control Center: hide shortcuts box if empty
2025-11-27 08:42:49 -05:00
ItsLemmy
ec16b4eafc Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-11-27 08:35:18 -05:00
ItsLemmy
8c339fc199 i18n: autosorting 2025-11-27 08:35:13 -05:00
Lysec
c8dc62c981 Merge pull request #879 from 3akev/niri_socket
Niri: receive events directly from socket
2025-11-27 14:35:11 +01:00
ItsLemmy
e6a4db9707 Better rounding 2025-11-27 08:34:15 -05:00
Lemmy
7ab4049942 Merge pull request #888 from mnt-h/japanese-translation
Add Japanese translation (ja)
2025-11-27 08:19:34 -05:00
Lemmy
453727795c Merge pull request #885 from MrDowntempo/fix/cleaned-up-color-picker
Added small margin, put theme colors on top
2025-11-27 08:16:57 -05:00
HAMADA Minato
94a582b634 Add Japanese translation (ja) 2025-11-27 21:39:34 +09:00
Ly-sec
72263f198e AudioPanel: suppress OSD for multi monitor when changing volume 2025-11-27 13:19:32 +01:00
Ly-sec
778dce21c1 AudioPanel: suppress OSD when toggle mute/unmute to prevent overlapping 2025-11-27 09:34:00 +01:00
Lysec
f70e49ad9e Merge pull request #884 from notiant/patch-1
syntax error fix
2025-11-27 07:24:24 +01:00
ItsLemmy
705334169b Settings: Do not turn on lockkeys by default 2025-11-26 23:27:07 -05:00
Lemmy
3e5cf91bfb Update README.md 2025-11-26 22:05:46 -05:00
ItsLemmy
5e833f0683 Round image with Qt. 2025-11-26 21:46:59 -05:00
Corey Woodworth
84246e0d5d Added small margin, put theme colors on top 2025-11-26 21:20:12 -05:00
ItsLemmy
0c8b0cb395 Only top 20 2025-11-26 21:10:09 -05:00
ItsLemmy
6e4f450f97 Cleanup 2025-11-26 21:04:06 -05:00
notiant
64c1f4383e syntax error fix 2025-11-27 02:59:32 +01:00
ItsLemmy
04f5a0cbf8 test 2025-11-26 20:50:06 -05:00
ItsLemmy
1c1232dc5b Revert "Another attempt"
This reverts commit bee2414333.
2025-11-26 20:40:52 -05:00
ItsLemmy
bee2414333 Another attempt 2025-11-26 20:38:29 -05:00
ItsLemmy
b344e41828 Revert "SmartPanelWindow: add a small delay in an attempt to improve cleanup"
This reverts commit d2023500a9.
2025-11-26 20:32:07 -05:00
ItsLemmy
d2023500a9 SmartPanelWindow: add a small delay in an attempt to improve cleanup 2025-11-26 20:28:15 -05:00
ItsLemmy
ba1e783c8f ColorSchemeService: fixed 2 syntax errors coming from PR #880 2025-11-26 20:18:38 -05:00
Adam Laughlin
4d24791ec1 Control Center: hide shortcuts box if empty 2025-11-26 20:08:13 -05:00
Lemmy
81c35aaee9 Merge pull request #880 from notiant/patch-3
Add some missing translations & more consistency for English
2025-11-26 20:04:34 -05:00
ItsLemmy
40f70cdbb1 Back to -git 2025-11-26 19:43:31 -05:00
ItsLemmy
1aa0cc6467 v3.4.0 2025-11-26 19:42:53 -05:00
ItsLemmy
23d3eb642e AudioService + OSD: minor improvements
- Replace hardcoded value by epsilon property
- Dont send volume change if delta is below epsilon
2025-11-26 19:33:51 -05:00
ItsLemmy
a188aa2e17 Autofmt + all missing transactions 2025-11-26 15:57:34 -05:00
Ly-sec
8419549183 AudioService: this might be it 2025-11-26 21:10:26 +01:00
Ly-sec
e2854f2079 AudioService: possible fix? 2025-11-26 20:08:47 +01:00
Ly-sec
05c90909d2 ClipboardPreview: fix ClipboardPreview 2025-11-26 19:22:18 +01:00
Ly-sec
cdb93a3d96 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-11-26 19:19:17 +01:00
Ly-sec
309648d6d6 Calendar: add timer
LocationTab: rework calendar settings
SoundService: add simple service to play & loop sounds
2025-11-26 19:18:30 +01:00
notiant
649ec5ac5a small edit 2025-11-26 18:58:44 +01:00
notiant
601f297b35 Merge branch 'main' into patch-3 2025-11-26 18:55:49 +01:00
ItsLemmy
7f9bb6f0a5 Credits: saber 2025-11-26 12:29:13 -05:00
ItsLemmy
4d72a0bd0c Credits: minor cleanup 2025-11-26 11:51:45 -05:00
ItsLemmy
f79aad5f0e CREDITS.md 2025-11-26 11:46:29 -05:00
3akev
a4d9463c5d Niri: receive events directly from socket 2025-11-26 17:30:34 +01:00
ItsLemmy
f10207a159 Settings / SetupWizard & OSD
- Settings cleanup and avoid segfault by not using var.
- SetupWizard simplified opening condition logic. Will only open when no
settings available
- OSD: simplified settings logic, updated translations to explain that
no type selected = all types enabled. similar to bar and monitors logic.
- Do not open changelog on a fresh install as we already open the
SetupWizard
2025-11-26 09:52:15 -05:00
Ly-sec
f611e3a2c0 OSD: use volume-x(volume-3) for 0% volume 2025-11-26 15:07:19 +01:00
Ly-sec
94d1d9dc9c Tray: fix blacklist wildcardc 2025-11-26 15:00:27 +01:00
Ly-sec
c0b836af26 OSD: fix 0% brightness icon 2025-11-26 14:54:38 +01:00
Ly-sec
a44137f81f OSD: fix 0% volume icon 2025-11-26 14:53:31 +01:00
Ly-sec
60eb9c6e78 Bluetooth/Wifi: fix always hide logic 2025-11-26 13:57:58 +01:00
Ly-sec
42211c6eda Bluetooth/Wifi: fix on hover mode 2025-11-26 13:49:54 +01:00
Ly-sec
3ef5e169e4 Brightness/VolumeWidget: fix visual issues (#875) 2025-11-26 12:40:18 +01:00
EmmetZ
cb0609451d fix(MediaMini): make icon smaller when inside progress ring 2025-11-26 18:32:10 +08:00
Ly-sec
737bde0a6a Matugen/Vesktop: fix noctalia logo display 2025-11-26 10:34:55 +01:00
Ly-sec
bc9c27baf8 Matugen/Vesktop: fix thread text color 2025-11-26 10:31:36 +01:00
Lysec
331519bba4 Merge pull request #877 from homebobhomebob/homebobhomebob-patch-1
Update es.json translation for 'play' to 'Reproducir'
2025-11-26 08:57:40 +01:00
homebobhomebob
d2f018c133 Update es.json translation for 'play' to 'Reproducir'
spanish has a lot words for the same thing (in spanish (jugar) play it is used to play ...games (jugar juegos), but play music is "reproducir musica"
2025-11-26 07:28:23 +00:00
Lysec
66d949ec2a Merge pull request #876 from lonerOrz/feat/shader-progress-border
feat(shader): Add progress_border.frag shader source file
2025-11-26 07:40:14 +01:00
loner
b3cd4568f3 feat(shader): Add progress_border.frag shader source file 2025-11-26 14:36:58 +08:00
notiant
22ee8904a4 Add some missing translations & more consistency for English 2025-11-26 06:14:21 +01:00
ItsLemmy
3c5dfd87db NImageRounded: attempt to fix crash on older Qt versions 2025-11-25 20:56:00 -05:00
ItsLemmy
58d4730814 Restore rounded_image shader for the SetupWizard for now. 2025-11-25 20:25:01 -05:00
ItsLemmy
ad044882a9 NIcon: always center icon 2025-11-25 19:59:37 -05:00
ItsLemmy
7742bb5cc0 OSD: fix non existing fontWeight 2025-11-25 19:59:23 -05:00
ItsLemmy
a2e686bb21 AudioService: proper volume clamping 2025-11-25 19:59:06 -05:00
ItsLemmy
b7d4e74012 NImageRounded/Circled: removed shaders and used a simpler ClippingWrapperRectangle 2025-11-25 19:38:23 -05:00
ItsLemmy
12fe6c5559 Debug: inhibitReloadPopup onReloadFailed unless we are debugging. 2025-11-25 16:55:10 -05:00
ItsLemmy
a35123918c Battery Panel: Charge Level => Battery Level 2025-11-25 15:20:51 -05:00
ItsLemmy
764acef4e7 settings-defaul 2025-11-25 15:20:30 -05:00
ItsLemmy
82c629278d Battery: removed unecessary property 2025-11-25 15:11:57 -05:00
Lysec
724d991d9f Merge pull request #872 from acdcbyl/main
Programcheck: support flatpak for telegram
2025-11-25 18:21:45 +01:00
Ly-sec
8277ce1631 IPCService: add state IPC call 2025-11-25 18:15:57 +01:00
Ly-sec
5a247f9de4 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-11-25 17:03:11 +01:00
Ly-sec
cb3af2d0d6 Matugen/Vesktop: fix chatbar height 2025-11-25 17:03:06 +01:00
Lysec
e91e3d9a4e Merge pull request #874 from lonerOrz/feat/media-display
feat: Optimize the icon display inside the progress circle
2025-11-25 16:59:13 +01:00
loner
15a936bebc feat: Optimize the icon display inside the progress circle 2025-11-25 23:55:08 +08:00
Ly-sec
cc11971fc8 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-11-25 16:28:22 +01:00
Ly-sec
83f8028d47 OsdTab: add toggle for all OSDs 2025-11-25 16:27:19 +01:00
Lemmy
3f08493134 Remove extra line in README.md 2025-11-25 09:50:25 -05:00
Lysec
5c2da31155 README: update video 2025-11-25 15:42:01 +01:00
Aiser
f79d9ce852 Programcheck: support flatpak for telegram 2025-11-25 22:34:41 +08:00
Lysec
1d396afb05 Update README.md 2025-11-25 15:31:24 +01:00
Lemmy
8ab2d84c85 Merge pull request #870 from bokicoder/main
Launcher: allow switching between plugins via IPC
2025-11-25 08:27:30 -05:00
Lysec
4a57803847 Merge pull request #871 from lonerOrz/feat/lock-key
feat: Add setting to disable lock key OSD notifications
2025-11-25 14:25:24 +01:00
loner
f9f83a6db3 i18n: Add show-lock-key-notifications translation 2025-11-25 21:20:55 +08:00
loner
4c6cf8d21b feat: Add setting to disable lock key OSD notifications 2025-11-25 21:20:50 +08:00
Ly-sec
9cf44be361 Battery: fix pill when charging 2025-11-25 14:12:17 +01:00
wxlyyy
f9c0c0a480 Launcher: allow switching between plugins via IPC 2025-11-25 19:11:17 +08:00
ItsLemmy
6a427b2cfc ColorScheme: Download more button on its own line to avoid breaking layout in german. 2025-11-24 23:12:04 -05:00
ItsLemmy
ce7b27c316 Autoformating + back to -git 2025-11-24 21:38:07 -05:00
ItsLemmy
adfee30f8c v3.3.1 2025-11-24 21:34:58 -05:00
Ly-sec
70b19791bb Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-11-25 02:15:24 +01:00
Ly-sec
7f48ea73b2 Matugen/Vesktop: more layout fixes 2025-11-25 02:15:18 +01:00
Lysec
6b5a2d2339 Merge pull request #850 from notiant/patch-1
LockScreen: Re-add some reverted changes
2025-11-25 01:49:40 +01:00
Ly-sec
8afb6cfb6a Matugen/Vesktop: adjust text brightness 2025-11-25 01:47:56 +01:00
Ly-sec
f38861061e Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-11-25 01:41:12 +01:00
Ly-sec
1cf9178a26 Matugen/Vesktop: add noctalia logo, fix timestamp color 2025-11-25 01:40:43 +01:00
Lemmy
1408af4a0a Merge pull request #862 from notiant/patch-2
Remove minimum height from wifi & bluetooth panel
2025-11-24 19:19:57 -05:00
Lemmy
71455e4af9 Merge pull request #838 from lonerOrz/feat/osd
feat(osd): Implement lock key notifications with dynamic sizing
2025-11-24 19:18:41 -05:00
Lysec
ff983d7c54 Merge pull request #859 from singhantariksh/main
refactor(vesktop.css): updated from hyprluna to midnight vesktop theme
2025-11-25 01:18:21 +01:00
Lemmy
7ba8ac28c8 Merge pull request #857 from lonerOrz/fix/check
fix(program-checker): Improve Telegram detection for NixOS
2025-11-24 19:13:21 -05:00
ItsLemmy
e1dc72216e Mango/Sway: fixed unclickable NPopupContextMenu 2025-11-24 19:12:08 -05:00
ItsLemmy
5983ba2fd1 Mango: fixed connection "on toplevels changed". 2025-11-24 18:39:19 -05:00
Lemmy
1b4e6c9bb5 Merge pull request #863 from atheeq-rhxn/main
Proper window tags parsing in mangowc and code optimizations
2025-11-24 18:28:06 -05:00
ItsLemmy
3cf4e1f95b TaskBar+Grouped: improved popup menu positionning and factorized code. 2025-11-24 14:59:42 -05:00
atheeq-rhxn
7be37eadf9 fix(MangoWC): Tags, windows parsing in overview mode 2025-11-25 00:45:24 +05:30
Antariksh Singh
a894511711 fix(vesktop.css): banner icons visibility issue
the banner icons had a dark background as well as a dark icon color, changed the icon color to a brighter one, and minor description changes
2025-11-25 00:32:28 +05:30
atheeq-rhxn
4e63b54c0e fix(MangoWC): Tags, Window parsing and optimize code 2025-11-25 00:12:54 +05:30
notiant
c70098a738 Remove minimum height from wifi & bluetooth panel 2025-11-24 18:17:40 +01:00
Antariksh Singh
5a6a175558 updated theme name and description in vesktop.css 2025-11-24 21:18:42 +05:30
Antariksh Singh
49747dffcc refactor(vesktop.css): updated from hyprluna to midnight vesktop theme 2025-11-24 21:07:19 +05:30
Lysec
38721a1a80 Merge pull request #858 from lonerOrz/fix/progress
fix: Music progress ring color not updating on theme change when paused
2025-11-24 15:58:40 +01:00
loner
a57480320f fix: Music progress ring color not updating on theme change when paused 2025-11-24 22:56:53 +08:00
Ly-sec
0e899d5559 Taskbar/TaskbarGrouped: fix hyprland context menu (sort of) 2025-11-24 15:41:28 +01:00
Ly-sec
c7116827a4 NPopupContextMenu: add dynamic width calculation 2025-11-24 14:13:02 +01:00
Ly-sec
54cd3d74e5 i18n: adjust notification widget translation 2025-11-24 13:56:59 +01:00
loner
fab0d3d8db fix(program-checker): Improve Telegram detection for NixOS 2025-11-24 19:44:22 +08:00
Lysec
73b6aa8c47 Merge pull request #856 from lonerOrz/fix/icon
Resolve icon duplication and progress ring display
2025-11-24 11:30:00 +01:00
loner
611ddbe612 feat: Enhance MediaMini widget with bug fixes and improvements 2025-11-24 17:31:42 +08:00
loner
56c228b4da fix(MediaMini): Resolve icon duplication and progress ring display issues 2025-11-24 17:03:11 +08:00
Lysec
4a4b25ae96 Merge pull request #855 from lonerOrz/fix/icon
fix: MediaMini play/pause icon duplication and improve album art display
2025-11-24 09:28:03 +01:00
loner
8f850cdbfd fix: MediaMini play/pause icon duplication and improve album art display 2025-11-24 16:08:21 +08:00
ItsLemmy
e61a073f57 DefaultSettings: refreshed with cava 2025-11-23 19:25:57 -05:00
ItsLemmy
b93c5051d0 BarPill: minor color fix 2025-11-23 17:54:37 -05:00
ItsLemmy
e6c9a828af Battery + BarPill color fixes and cleanup
Battery: prioritize charging > low battery > unplugged with decent level
BarPill: prioritize hover state, then custom color, then
fallback/default.
2025-11-23 17:47:20 -05:00
Lemmy
9c01319261 Revise README with updates and breaking change info
Updated README.md
2025-11-23 16:36:45 -05:00
Ly-sec
bd2507d9f8 Set version to git 2025-11-23 22:35:53 +01:00
Ly-sec
b53f5ef504 Release v3.3.0 2025-11-23 22:28:58 +01:00
Lysec
a22a3c1345 Merge pull request #853 from lonerOrz/fix/mm
fix: MediaMini progress ring visibility when showAlbumArt is disabled
2025-11-23 22:17:53 +01:00
loner
87dd944075 fix: MediaMini progress ring visibility when showAlbumArt is disabled 2025-11-24 05:14:24 +08:00
Ly-sec
ad96d2b05c Launcher: force calculator to listview
ClipboardService: fix warning
IPCService: add launcher emoji ipc
2025-11-23 22:05:40 +01:00
Ly-sec
80bc4f9c55 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-11-23 21:54:55 +01:00
Ly-sec
f033ebb854 autoformat 2025-11-23 21:54:38 +01:00
Ly-sec
04c8f5b54e LauncherTab: add grid view option
Launcher: force clipboard history to list view
NGridView: created
2025-11-23 21:51:14 +01:00
notiant
d28c89afcd fix syntax error 2025-11-23 20:21:36 +01:00
notiant
c9eead1d9e Re-add some inverted changes 2025-11-23 20:02:36 +01:00
loner
16486ba054 Fix: Prevent lockkey OSD from showing at startup 2025-11-24 00:15:53 +08:00
loner
40a717e009 feat(osd): Implement lock key notifications with dynamic sizing 2025-11-23 18:48:31 +08:00
153 changed files with 25818 additions and 6387 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
.qmlls.ini
.zed
Bin/battery-manager/uninstall-battery-manager.sh
.idea

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,339 @@
;;; noctalia-theme.el --- Theme using Matugen SCSS variables
;; Copyright (C) 2025
;; Author: Generated (Improved)
;; Version: 1.2
;; Package-Requires: ((emacs "24.1"))
;; Keywords: faces
;;; Commentary:
;; A theme using Matugen SCSS variables with quality of life improvements:
;; - Better source block distinction
;; - Improved text visibility when selected
;; - Refined org-mode styling with hidden asterisks
;; - Enhanced contrast and readability
;; - Seamless integration of source blocks with consistent styling
;;; Code:
(deftheme noctalia "Theme using Matugen variables with quality of life improvements.")
;; Define all the color variables (replaced by template processor)
(let* ((bg "{{colors.background.default.hex}}")
(err "{{colors.error.default.hex}}")
(err-container "{{colors.error_container.default.hex}}")
(on-background "{{colors.on_background.default.hex}}")
(on-err "{{colors.on_error.default.hex}}")
(on-err-container "{{colors.on_error_container.default.hex}}")
(on-primary "{{colors.on_primary.default.hex}}")
(on-primary-container "{{colors.on_primary_container.default.hex}}")
(on-secondary "{{colors.on_secondary.default.hex}}")
(on-secondary-container "{{colors.on_secondary_container.default.hex}}")
(on-surface "{{colors.on_surface.default.hex}}")
(on-surface-variant "{{colors.on_surface_variant.default.hex}}")
(on-tertiary "{{colors.on_tertiary.default.hex}}")
(on-tertiary-container "{{colors.on_tertiary_container.default.hex}}")
(outline-color "{{colors.outline.default.hex}}")
(outline-variant "{{colors.outline_variant.default.hex}}")
(primary "{{colors.primary.default.hex}}")
(primary-container "{{colors.primary_container.default.hex}}")
(secondary "{{colors.secondary.default.hex}}")
(secondary-container "{{colors.secondary_container.default.hex}}")
(shadow "{{colors.shadow.default.hex}}")
(surface "{{colors.surface.default.hex}}")
(surface-container "{{colors.surface_container.default.hex}}")
(surface-container-high "{{colors.surface_container_high.default.hex}}")
(surface-container-highest "{{colors.surface_container_highest.default.hex}}")
(surface-container-low "{{colors.surface_container_low.default.hex}}")
(surface-container-lowest "{{colors.surface_container_lowest.default.hex}}")
(surface-variant "{{colors.surface_variant.default.hex}}")
(tertiary "{{colors.tertiary.default.hex}}")
(tertiary-container "{{colors.tertiary_container.default.hex}}")
;; Map success colors to tertiary (as used in other templates)
(success "{{colors.tertiary.default.hex}}")
(on-success "{{colors.on_tertiary.default.hex}}")
(success-container "{{colors.tertiary_container.default.hex}}")
(on-success-container "{{colors.on_tertiary_container.default.hex}}")
;; Map fixed colors to regular colors
(primary-fixed "{{colors.primary.default.hex}}")
(primary-fixed-dim "{{colors.primary_container.default.hex}}")
(secondary-fixed "{{colors.secondary.default.hex}}")
(secondary-fixed-dim "{{colors.secondary_container.default.hex}}")
(tertiary-fixed "{{colors.tertiary.default.hex}}")
(tertiary-fixed-dim "{{colors.tertiary_container.default.hex}}")
(on-primary-fixed "{{colors.on_primary.default.hex}}")
(on-primary-fixed-variant "{{colors.on_primary_container.default.hex}}")
(on-secondary-fixed "{{colors.on_secondary.default.hex}}")
(on-secondary-fixed-variant "{{colors.on_secondary_container.default.hex}}")
(on-tertiary-fixed "{{colors.on_tertiary.default.hex}}")
(on-tertiary-fixed-variant "{{colors.on_tertiary_container.default.hex}}")
;; Map inverse colors to surface variants
(inverse-on-surface "{{colors.on_surface.default.hex}}")
(inverse-primary "{{colors.primary.default.hex}}")
(inverse-surface "{{colors.surface.default.hex}}")
;; Map terminal colors (term0-term15) to available colors
(term0 "{{colors.surface.default.hex}}")
(term1 "{{colors.error.default.hex}}")
(term2 "{{colors.tertiary.default.hex}}")
(term3 "{{colors.secondary.default.hex}}")
(term4 "{{colors.primary.default.hex}}")
(term5 "{{colors.tertiary_container.default.hex}}")
(term6 "{{colors.secondary_container.default.hex}}")
(term7 "{{colors.on_surface.default.hex}}")
(term8 "{{colors.outline.default.hex}}")
(term9 "{{colors.error.default.hex}}")
(term10 "{{colors.tertiary.default.hex}}")
(term11 "{{colors.secondary.default.hex}}")
(term12 "{{colors.primary.default.hex}}")
(term13 "{{colors.tertiary_container.default.hex}}")
(term14 "{{colors.secondary_container.default.hex}}")
(term15 "{{colors.on_surface.default.hex}}"))
(custom-theme-set-faces
'noctalia
;; Basic faces
`(default ((t (:background ,bg :foreground ,on-background))))
`(cursor ((t (:background ,primary))))
`(highlight ((t (:background ,primary-container :foreground ,on-primary-container))))
`(region ((t (:background ,primary-container :foreground ,on-primary-container :extend t))))
`(secondary-selection ((t (:background ,secondary-container :foreground ,on-secondary-container :extend t))))
`(isearch ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold))))
`(lazy-highlight ((t (:background ,secondary-container :foreground ,on-secondary-container))))
`(vertical-border ((t (:foreground ,surface-variant))))
`(border ((t (:background ,surface-variant :foreground ,surface-variant))))
`(fringe ((t (:background ,surface :foreground ,outline-variant))))
`(shadow ((t (:foreground ,outline-variant))))
`(link ((t (:foreground ,primary :underline t))))
`(link-visited ((t (:foreground ,tertiary :underline t))))
`(success ((t (:foreground ,success))))
`(warning ((t (:foreground ,secondary))))
`(error ((t (:foreground ,err))))
`(match ((t (:background ,secondary-container :foreground ,on-secondary-container))))
;; Font-lock
`(font-lock-builtin-face ((t (:foreground ,primary))))
`(font-lock-comment-face ((t (:foreground ,outline-color :slant italic))))
`(font-lock-comment-delimiter-face ((t (:foreground ,outline-variant))))
`(font-lock-constant-face ((t (:foreground ,tertiary :weight bold))))
`(font-lock-doc-face ((t (:foreground ,on-surface-variant :slant italic))))
`(font-lock-function-name-face ((t (:foreground ,primary :weight bold))))
`(font-lock-keyword-face ((t (:foreground ,secondary :weight bold))))
`(font-lock-string-face ((t (:foreground ,tertiary))))
`(font-lock-type-face ((t (:foreground ,primary-fixed))))
`(font-lock-variable-name-face ((t (:foreground ,on-surface))))
`(font-lock-warning-face ((t (:foreground ,err :weight bold))))
`(font-lock-preprocessor-face ((t (:foreground ,secondary-fixed-dim))))
`(font-lock-negation-char-face ((t (:foreground ,tertiary-fixed))))
;; Show paren
`(show-paren-match ((t (:background ,primary-container :foreground ,on-primary-container :weight bold))))
`(show-paren-mismatch ((t (:background ,err-container :foreground ,on-err-container :weight bold))))
;; Mode line - improved status bar styling
`(mode-line ((t (:background ,surface-container :foreground ,on-surface :box nil))))
`(mode-line-inactive ((t (:background ,surface :foreground ,on-surface-variant :box nil))))
`(mode-line-buffer-id ((t (:foreground ,primary :weight bold))))
`(mode-line-emphasis ((t (:foreground ,primary :weight bold))))
`(mode-line-highlight ((t (:foreground ,primary :box nil))))
;; Improved Source blocks - make them integrated with the theme
`(org-block ((t (:background ,surface-container-low :extend t :inherit fixed-pitch))))
`(org-block-begin-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch))))
`(org-block-end-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch))))
`(org-code ((t (:background ,surface-container-low :foreground ,tertiary-fixed :inherit fixed-pitch))))
`(org-verbatim ((t (:background ,surface-container-low :foreground ,primary-fixed :inherit fixed-pitch))))
`(org-meta-line ((t (:foreground ,outline-color :slant italic))))
;; 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-document-title ((t (:foreground ,primary :weight bold :height 1.3))))
`(org-document-info ((t (:foreground ,primary-container))))
`(org-todo ((t (:foreground ,err :weight bold))))
`(org-done ((t (:foreground ,success :weight bold))))
`(org-headline-done ((t (:foreground ,on-surface-variant))))
`(org-hide ((t (:foreground ,bg)))) ;; Hide leading asterisks
`(org-ellipsis ((t (:foreground ,tertiary :underline nil)))) ;; Style for folded content indicator
`(org-table ((t (:foreground ,secondary-fixed :inherit fixed-pitch))))
`(org-formula ((t (:foreground ,tertiary :inherit fixed-pitch))))
`(org-checkbox ((t (:foreground ,primary :weight bold :inherit fixed-pitch))))
`(org-date ((t (:foreground ,secondary-fixed :underline t))))
`(org-special-keyword ((t (:foreground ,on-surface-variant :slant italic))))
`(org-tag ((t (:foreground ,outline-color :weight normal))))
;; Magit
`(magit-section-highlight ((t (:background ,surface-container-low))))
`(magit-diff-hunk-heading ((t (:background ,surface-container :foreground ,on-surface-variant))))
`(magit-diff-hunk-heading-highlight ((t (:background ,surface-container-high :foreground ,on-surface))))
`(magit-diff-context ((t (:foreground ,on-surface-variant))))
`(magit-diff-context-highlight ((t (:background ,surface-container-low :foreground ,on-surface))))
`(magit-diff-added ((t (:background ,success-container :foreground ,on-success-container))))
`(magit-diff-added-highlight ((t (:background ,success-container :foreground ,on-success-container :weight bold))))
`(magit-diff-removed ((t (:background ,err-container :foreground ,on-err-container))))
`(magit-diff-removed-highlight ((t (:background ,err-container :foreground ,on-err-container :weight bold))))
`(magit-hash ((t (:foreground ,outline-color))))
`(magit-branch-local ((t (:foreground ,tertiary :weight bold))))
`(magit-branch-remote ((t (:foreground ,primary :weight bold))))
;; Company
`(company-tooltip ((t (:background ,surface-container :foreground ,on-surface))))
`(company-tooltip-selection ((t (:background ,primary-container :foreground ,on-primary-container))))
`(company-tooltip-common ((t (:foreground ,primary))))
`(company-tooltip-common-selection ((t (:foreground ,on-primary-container :weight bold))))
`(company-tooltip-annotation ((t (:foreground ,tertiary))))
`(company-scrollbar-fg ((t (:background ,primary))))
`(company-scrollbar-bg ((t (:background ,surface-variant))))
`(company-preview ((t (:foreground ,on-surface-variant :slant italic))))
`(company-preview-common ((t (:foreground ,primary :slant italic))))
;; Ido
`(ido-first-match ((t (:foreground ,primary :weight bold))))
`(ido-only-match ((t (:foreground ,tertiary :weight bold))))
`(ido-subdir ((t (:foreground ,secondary))))
`(ido-indicator ((t (:foreground ,err))))
`(ido-virtual ((t (:foreground ,outline-color))))
;; Helm
`(helm-selection ((t (:background ,primary-container :foreground ,on-primary-container))))
`(helm-match ((t (:foreground ,primary :weight bold))))
`(helm-source-header ((t (:background ,surface-container-high :foreground ,primary :weight bold :height 1.1))))
`(helm-candidate-number ((t (:foreground ,tertiary :weight bold))))
`(helm-ff-directory ((t (:foreground ,primary :weight bold))))
`(helm-ff-file ((t (:foreground ,on-surface))))
`(helm-ff-executable ((t (:foreground ,tertiary))))
;; Which-key
`(which-key-key-face ((t (:foreground ,primary :weight bold))))
`(which-key-separator-face ((t (:foreground ,outline-variant))))
`(which-key-command-description-face ((t (:foreground ,on-surface))))
`(which-key-group-description-face ((t (:foreground ,secondary))))
`(which-key-special-key-face ((t (:foreground ,tertiary :weight bold))))
;; Line numbers
`(line-number ((t (:foreground ,outline-variant :inherit fixed-pitch))))
`(line-number-current-line ((t (:foreground ,primary :weight bold :inherit fixed-pitch))))
;; Parenthesis matching
`(sp-show-pair-match-face ((t (:background ,primary-container :foreground ,on-primary-container))))
`(sp-show-pair-mismatch-face ((t (:background ,err-container :foreground ,on-err-container))))
;; Rainbow delimiters
`(rainbow-delimiters-depth-1-face ((t (:foreground ,primary))))
`(rainbow-delimiters-depth-2-face ((t (:foreground ,secondary))))
`(rainbow-delimiters-depth-3-face ((t (:foreground ,tertiary))))
`(rainbow-delimiters-depth-4-face ((t (:foreground ,primary-fixed))))
`(rainbow-delimiters-depth-5-face ((t (:foreground ,secondary-fixed))))
`(rainbow-delimiters-depth-6-face ((t (:foreground ,tertiary-fixed))))
`(rainbow-delimiters-depth-7-face ((t (:foreground ,primary-fixed-dim))))
`(rainbow-delimiters-depth-8-face ((t (:foreground ,secondary-fixed-dim))))
`(rainbow-delimiters-depth-9-face ((t (:foreground ,tertiary-fixed-dim))))
`(rainbow-delimiters-mismatched-face ((t (:foreground ,err :weight bold))))
`(rainbow-delimiters-unmatched-face ((t (:foreground ,err :weight bold))))
;; Dired
`(dired-directory ((t (:foreground ,primary :weight bold))))
`(dired-ignored ((t (:foreground ,outline-variant))))
`(dired-flagged ((t (:foreground ,err))))
`(dired-marked ((t (:foreground ,tertiary :weight bold))))
`(dired-symlink ((t (:foreground ,secondary :slant italic))))
`(dired-header ((t (:foreground ,primary :weight bold :height 1.1))))
;; Terminal colors
`(term-color-black ((t (:foreground ,term0 :background ,term0))))
`(term-color-red ((t (:foreground ,term1 :background ,term1))))
`(term-color-green ((t (:foreground ,term2 :background ,term2))))
`(term-color-yellow ((t (:foreground ,term3 :background ,term3))))
`(term-color-blue ((t (:foreground ,term4 :background ,term4))))
`(term-color-magenta ((t (:foreground ,term5 :background ,term5))))
`(term-color-cyan ((t (:foreground ,term6 :background ,term6))))
`(term-color-white ((t (:foreground ,term7 :background ,term7))))
;; EShell
`(eshell-prompt ((t (:foreground ,primary :weight bold))))
`(eshell-ls-directory ((t (:foreground ,primary :weight bold))))
`(eshell-ls-symlink ((t (:foreground ,secondary :slant italic))))
`(eshell-ls-executable ((t (:foreground ,tertiary))))
`(eshell-ls-archive ((t (:foreground ,on-tertiary-container))))
`(eshell-ls-backup ((t (:foreground ,outline-variant))))
`(eshell-ls-clutter ((t (:foreground ,err))))
`(eshell-ls-missing ((t (:foreground ,err))))
`(eshell-ls-product ((t (:foreground ,on-surface-variant))))
`(eshell-ls-readonly ((t (:foreground ,on-surface-variant))))
`(eshell-ls-special ((t (:foreground ,secondary-fixed))))
`(eshell-ls-unreadable ((t (:foreground ,outline-variant))))
;; Improved markdown mode
`(markdown-header-face ((t (:foreground ,primary :weight bold))))
`(markdown-header-face-1 ((t (:foreground ,primary :weight bold :height 1.2))))
`(markdown-header-face-2 ((t (:foreground ,primary-container :weight bold :height 1.1))))
`(markdown-header-face-3 ((t (:foreground ,secondary :weight bold))))
`(markdown-header-face-4 ((t (:foreground ,secondary-container :weight bold))))
`(markdown-inline-code-face ((t (:foreground ,tertiary-fixed :background ,surface-container-low :inherit fixed-pitch))))
`(markdown-code-face ((t (:background ,surface-container-low :extend t :inherit fixed-pitch))))
`(markdown-pre-face ((t (:background ,surface-container-low :inherit fixed-pitch))))
`(markdown-table-face ((t (:foreground ,secondary-fixed :inherit fixed-pitch))))
;; Web mode
`(web-mode-html-tag-face ((t (:foreground ,primary))))
`(web-mode-html-tag-bracket-face ((t (:foreground ,on-surface-variant))))
`(web-mode-html-attr-name-face ((t (:foreground ,secondary))))
`(web-mode-html-attr-value-face ((t (:foreground ,tertiary))))
`(web-mode-css-selector-face ((t (:foreground ,primary))))
`(web-mode-css-property-name-face ((t (:foreground ,secondary))))
`(web-mode-css-string-face ((t (:foreground ,tertiary))))
;; Flycheck
`(flycheck-error ((t (:underline (:style wave :color ,err)))))
`(flycheck-warning ((t (:underline (:style wave :color ,secondary)))))
`(flycheck-info ((t (:underline (:style wave :color ,tertiary)))))
`(flycheck-fringe-error ((t (:foreground ,err))))
`(flycheck-fringe-warning ((t (:foreground ,secondary))))
`(flycheck-fringe-info ((t (:foreground ,tertiary))))
;; Mini-buffer customization
`(minibuffer-prompt ((t (:foreground ,primary :weight bold))))
;; Improved search highlighting
`(lsp-face-highlight-textual ((t (:background ,primary-container :foreground ,on-primary-container :weight bold))))
`(lsp-face-highlight-read ((t (:background ,secondary-container :foreground ,on-secondary-container :weight bold))))
`(lsp-face-highlight-write ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold))))
;; Info and help modes
`(info-title-1 ((t (:foreground ,primary :weight bold :height 1.3))))
`(info-title-2 ((t (:foreground ,primary-container :weight bold :height 1.2))))
`(info-title-3 ((t (:foreground ,secondary :weight bold :height 1.1))))
`(info-title-4 ((t (:foreground ,secondary-container :weight bold))))
`(Info-quoted ((t (:foreground ,tertiary))))
`(info-menu-header ((t (:foreground ,primary :weight bold))))
`(info-menu-star ((t (:foreground ,primary))))
`(info-node ((t (:foreground ,tertiary :weight bold))))
;; Fixed-pitch faces
`(fixed-pitch ((t (:family "monospace"))))
`(fixed-pitch-serif ((t (:family "monospace serif"))))
;; Variable-pitch face
`(variable-pitch ((t (:family "sans serif"))))
))
;; Add org-mode hooks for hiding leading stars
(with-eval-after-load 'org
(setq org-hide-leading-stars t)
(setq org-startup-indented t))
;;;###autoload
(when load-file-name
(add-to-list 'custom-theme-load-path
(file-name-as-directory (file-name-directory load-file-name))))
(provide-theme 'noctalia)
;;; noctalia-theme.el ends here

View File

@@ -0,0 +1,29 @@
layout {
background-color "transparent"
focus-ring {
active-color "{{colors.primary.default.hex}}"
inactive-color "{{colors.outline.default.hex}}"
urgent-color "{{colors.error.default.hex}}"
}
border {
active-color "{{colors.primary.default.hex}}"
inactive-color "{{colors.outline.default.hex}}"
urgent-color "{{colors.error.default.hex}}"
}
shadow {
color "{{colors.shadow.default.hex}}70"
}
tab-indicator {
active-color "{{colors.primary.default.hex}}"
inactive-color "{{colors.outline.default.hex}}"
urgent-color "{{colors.error.default.hex}}"
}
insert-hint {
color "{{colors.primary.default.hex}}80"
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

View File

@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Standard (Anzeigegerät)",
"description": "Wählen Sie das anzuzeigende Batteriegerät aus.",
"label": "Batteriebetriebenes Gerät"
},
"display-mode": {
"description": "Wählen Sie, wie dieser Wert angezeigt werden soll.",
"label": "Anzeigemodus"
@@ -97,9 +102,13 @@
"control-center": {
"browse-file": "Datei durchsuchen",
"browse-library": "Bibliothek durchsuchen",
"colorize-distro-logo": {
"description": "Wende die Designfarben auf das Logo deiner Distribution an.",
"label": "Distro-Logo einfärben"
"color-selection": {
"description": "Wendet Themenfarben auf Symbole an.",
"label": "Farbauswahl"
},
"enable-colorization": {
"description": "Aktiviert die Färbung für das Kontrollzentrum-Symbol und wendet Themenfarben an.",
"label": "Färbung aktivieren"
},
"icon": {
"description": "Symbol aus der Bibliothek oder eine benutzerdefinierte Datei auswählen.",
@@ -133,11 +142,11 @@
"update-text": "Text auf Linksklick aktualisieren"
},
"max-text-length-horizontal": {
"description": "Maximale Anzahl an Zeichen, die in horizontaler Leiste angezeigt werden (0 zum Ausblenden des Textes)",
"description": "Maximale Anzahl an Zeichen, die in horizontaler Leiste angezeigt werden (0 zum Ausblenden des Textes).",
"label": "Max. Textlänge (horizontal)"
},
"max-text-length-vertical": {
"description": "Maximale Anzahl an Zeichen, die in vertikaler Leiste angezeigt werden (0 zum Ausblenden des Textes)",
"description": "Maximale Anzahl an Zeichen, die in vertikaler Leiste angezeigt werden (0 zum Ausblenden des Textes).",
"label": "Max. Textlänge (vertikal)"
},
"middle-click": {
@@ -163,7 +172,7 @@
"label": "Stream"
},
"wheel": {
"description": "Befehl, der bei Verwendung des Scrollrads ausgeführt wird.\nVerwenden Sie $delta für die Scrollrad-Delta im Befehl",
"description": "Befehl, der bei Verwendung des Scrollrads ausgeführt wird.\nVerwenden Sie $delta für die Scrollrad-Delta im Befehl.",
"label": "Scrollrad",
"update-text": "Anzeigetext beim Scrollen aktualisieren"
},
@@ -172,7 +181,7 @@
"label": "Scrollrad runter Befehl"
},
"wheel-mode-separate": {
"description": "Separate Befehle für Scrollrad hoch und runter aktivieren",
"description": "Separate Befehle für Scrollrad hoch und runter aktivieren.",
"label": "Separate Scrollrad-Befehle"
},
"wheel-up": {
@@ -202,7 +211,7 @@
},
"show-scroll-lock": {
"description": "Scroll-Lock-Status anzeigen.",
"label": "Rollenfeststelltaste"
"label": "Rollen-Taste"
}
},
"media-mini": {
@@ -227,14 +236,14 @@
"description": "Künstler - Titel anstatt Titel - Künstler anzeigen.",
"label": "Künstler zuerst anzeigen"
},
"show-visualizer": {
"description": "Audio-Visualizer anzeigen, wenn Musik abgespielt wird.",
"label": "Visualizer anzeigen"
},
"show-progress-ring": {
"description": "Runden Fortschrittsindikator anzeigen, der den Titelfortschritt anzeigt.",
"label": "Fortschrittsring anzeigen"
},
"show-visualizer": {
"description": "Audio-Visualizer anzeigen, wenn Musik abgespielt wird.",
"label": "Visualizer anzeigen"
},
"use-fixed-width": {
"description": "Wenn aktiviert, verwendet das Widget immer die maximale Breite, anstatt sich dynamisch an den Inhalt anzupassen.",
"label": "Feste Breite verwenden"
@@ -271,9 +280,6 @@
}
},
"system-monitor": {
"cpu-critical-threshold": {
"label": "CPU Usage (Critical)"
},
"cpu-temperature": {
"description": "CPU-Temperaturwerte anzeigen, falls verfügbar.",
"label": "CPU-Temperatur"
@@ -282,25 +288,10 @@
"description": "Aktuelle CPU-Auslastung in Prozent anzeigen.",
"label": "CPU-Auslastung"
},
"cpu-warning-threshold": {
"label": "CPU Usage (Warning)"
},
"disk-critical-threshold": {
"label": "Storage Space (Critical)"
},
"disk-path": {
"description": "Wählen Sie den Festplatten-Mountpunkt aus, der überwacht werden soll.",
"label": "Festplattenpfad"
},
"disk-warning-threshold": {
"label": "Storage Space (Warning)"
},
"mem-critical-threshold": {
"label": "Memory Usage (Critical)"
},
"mem-warning-threshold": {
"label": "Memory Usage (Warning)"
},
"memory-percentage": {
"description": "Speicherverbrauch als Prozentsatz statt absolute Werte anzeigen.",
"label": "Speicher als Prozentsatz"
@@ -316,16 +307,6 @@
"storage-usage": {
"description": "Festplattenspeicher-Nutzungsinformationen anzeigen.",
"label": "Speichernutzung"
},
"temp-critical-threshold": {
"label": "CPU Temperature (Critical)"
},
"temp-warning-threshold": {
"label": "CPU Temperature (Warning)"
},
"thresholds": {
"description": "Schwellenwerte für visuelle Indikatoren konfigurieren. Warn-/Kritik-Indikatoren erscheinen in hervorgehobenen Farben bei Überschreitung.",
"header": "Schwellenwerte"
}
},
"taskbar": {
@@ -373,14 +354,14 @@
"description": "Anzahl der Zeichen, die von Arbeitsbereichsnamen angezeigt werden (1-10).",
"label": "Zeichenanzahl"
},
"hide-unoccupied": {
"description": "Arbeitsbereiche ohne Fenster nicht anzeigen.",
"label": "Unbesetzte ausblenden"
},
"follow-focused-screen": {
"description": "Zeigt Arbeitsbereiche vom aktuell fokussierten Bildschirm an, statt vom Bildschirm, auf dem sich die Leiste befindet.",
"label": "Fokussiertem Bildschirm folgen"
},
"hide-unoccupied": {
"description": "Arbeitsbereiche ohne Fenster nicht anzeigen.",
"label": "Unbesetzte ausblenden"
},
"label-mode": {
"description": "Wählen Sie, wie Arbeitsbereichs-Beschriftungen angezeigt werden.",
"label": "Beschriftungsmodus"
@@ -389,8 +370,8 @@
}
},
"battery": {
"battery-level": "Akkustand",
"brightness": "Helligkeit",
"charge-level": "Ladestand",
"charging": "Wird geladen.",
"charging-rate": "Laderate: {rate} W.",
"discharging": "Wird entladen.",
@@ -404,7 +385,7 @@
"plugged-in": "Angeschlossen.",
"power-profile": "Energieprofil",
"time-left": "Verbleibende Zeit: {time}.",
"time-until-full": "Zeit bis vollständig geladen: {time}."
"time-until-full": "Verbleibende Ladezeit: {time}."
},
"bluetooth": {
"panel": {
@@ -412,6 +393,7 @@
"blocked": "Blockiert",
"connect": "Verbinden",
"connected-devices": "Verbundene Geräte",
"connecting": "Verbinden...",
"disabled": "Bluetooth ist deaktiviert",
"disconnect": "Trennen",
"enable-message": "Aktivieren Sie Bluetooth, um verfügbare Geräte zu sehen.",
@@ -426,6 +408,19 @@
"panel": {
"week": "Woche"
},
"timer": {
"countdown": "Countdown",
"duration": "Dauer",
"hours": "h",
"minutes": "m",
"pause": "Pause",
"reset": "Zurücksetzen",
"seconds": "s",
"start": "Start",
"stopwatch": "Stoppuhr",
"timer": "Timer",
"title": "Zeitmesser"
},
"weather": {
"loading": "Wetter wird geladen…"
}
@@ -466,11 +461,11 @@
"connect-vpn": "Mit {name} verbinden",
"cycle-visualizer": "Zyklus-Visualisierer",
"disable-bluetooth": "Bluetooth deaktivieren",
"disable-dnd": "Bitte nicht stören deaktivieren",
"disable-dnd": "'Nicht stören' deaktivieren",
"disable-wifi": "WLAN deaktivieren",
"disconnect-vpn": "Verbindung zu {name} trennen",
"enable-bluetooth": "Bluetooth aktivieren",
"enable-dnd": "Nicht stören aktivieren",
"enable-dnd": "'Nicht stören' aktivieren",
"enable-wifi": "WLAN aktivieren",
"next": "Nächste/r/s",
"open-calendar": "Kalender öffnen",
@@ -518,14 +513,20 @@
"no-notifications": "Keine Benachrichtigungen",
"title": "Benachrichtigungen"
},
"range": {
"all": "Alle",
"earlier": "Älter",
"today": "Heute",
"yesterday": "Gestern"
},
"time": {
"now": "jetzt",
"diffM": "vor 1 Minute",
"diffMM": "vor {diff} Minuten",
"diffD": "vor 1 Tag",
"diffDD": "vor {diff} Tagen",
"diffH": "vor 1 Stunde",
"diffHH": "vor {diff} Stunden",
"diffD": "vor 1 Tag",
"diffDD": "vor {diff} Tagen"
"diffM": "vor 1 Minute",
"diffMM": "vor {diff} Minuten",
"now": "jetzt"
}
},
"options": {
@@ -545,7 +546,8 @@
},
"colors": {
"error": "Fehler",
"onSurface": "An der Oberfläche",
"none": "Keine",
"onSurface": "Auf der Oberfläche",
"primary": "Primär",
"secondary": "Sekundär",
"tertiary": "Tertiär"
@@ -706,7 +708,7 @@
"enabled": "Bluetooth"
},
"tooltip": {
"action": "Klicken zum Verwalten der Bluetooth-Geräte"
"action": "Bluetooth"
}
},
"keepAwake": {
@@ -715,7 +717,7 @@
"enabled": "Wach halten"
},
"tooltip": {
"action": "Klicken zum Umschalten des Wachmodus"
"action": "Wach halten"
}
},
"nightLight": {
@@ -725,7 +727,7 @@
"forced": "Nachtlicht"
},
"tooltip": {
"action": "Klicken zum Wechseln des Nachtlicht-Modus\nRechtsklick: Einstellungen öffnen"
"action": "Nachtlicht"
}
},
"notifications": {
@@ -734,7 +736,7 @@
"enabled": "Benachrichtigungen"
},
"tooltip": {
"action": "Linksklick: Benachrichtigungsverlauf öffnen\nRechtsklick: 'Nicht stören' umschalten"
"action": "Benachrichtigungen"
}
},
"powerProfile": {
@@ -742,7 +744,7 @@
"unavailable": "Energieprofil"
},
"tooltip": {
"action": "Klicken zum Wechseln des Energieprofils",
"action": "Energieprofil",
"disabled": "Installiere power-profiles-daemon, um Energieprofile zu verwenden"
}
},
@@ -752,13 +754,13 @@
"stopped": "Aufnehmen"
},
"tooltip": {
"action": "Klicken zum Starten/Stoppen einer Bildschirmaufnahme"
"action": "Bildschirmrekorder"
}
},
"wallpaperSelector": {
"label": "Hintergrundbild",
"tooltip": {
"action": "Linksklick: Hintergrundbild-Auswahl öffnen\nRechtsklick: Zufälliges Hintergrundbild setzen"
"action": "Hintergrundbild-Auswahl"
}
},
"wifi": {
@@ -768,7 +770,7 @@
"wifi": "WLAN"
},
"tooltip": {
"action": "Klicken zum Verwalten der WLAN-Verbindungen"
"action": "WLAN"
}
}
},
@@ -794,6 +796,8 @@
},
"noctalia": {
"download-latest": "Neueste Version herunterladen",
"git-commit": "Git-Commit:",
"git-commit-loading": "Lädt...",
"installed-version": "Installierte Version:",
"latest-version": "Neueste Version:",
"section": {
@@ -983,15 +987,15 @@
},
"dark-mode": {
"mode": {
"description": "Ermöglicht einen automatischen Wechsel zwischen dem hellen und dunklen Modus.",
"label": "Automatischer dunkler Modus",
"description": "Ermöglicht einen automatischen Wechsel zwischem hellen und dunklen Modus.",
"label": "Automatischer Dunkelmodus",
"location": "Standort",
"manual": "Manuell",
"off": "Aus"
},
"switch": {
"description": "Wechselt zu einem dunkleren Theme für einfachere Betrachtung bei Nacht.",
"label": "Dunkler Modus"
"label": "Dunkelmodus"
}
},
"delete": {
@@ -1040,18 +1044,30 @@
}
},
"templates": {
"compositors": {
"description": "Compositor-Theming.",
"label": "Compositor/innen",
"niri": {
"description": "Schreibe {filepath}. Benötigt niri v25.11+",
"description-missing": "Benötigt die Installation von {app}"
}
},
"misc": {
"description": "Zusätzliche Konfigurationsoptionen.",
"label": "Verschiedenes",
"description": "Erstellen Sie Ihre eigenen Vorlagen",
"label": "Erweitert",
"user-templates": {
"description": "Benutzerdefinierte Matugen-Konfiguration aktivieren. Eine Vorlagendatei wird beim ersten Aktivieren unter ~/.config/noctalia/user-templates.toml erstellt",
"label": "Benutzer-Vorlagen"
"description": "Nur aktivieren, wenn Sie wissen, was Sie tun. Weitere Informationen finden Sie in unserer Online-Dokumentation",
"label": "Benutzer-Vorlagen aktivieren"
}
},
"programs": {
"cava": {
"description": "Schreibe {filepath}",
"description-missing": "Benötigt die Installation von {app}"
},
"code": {
"description": "Schreibe {Dateipfad}. Das Hyprluna-Theme muss manuell installiert und aktiviert werden",
"description-missing": "Kein Code-Client erkannt. Installieren Sie VSCode oder VSCodium."
"description-missing": "Kein Code-Client erkannt. Installieren Sie VSCode oder VSCodium"
},
"description": "Anwendungsspezifisches Theming.",
"discord": {
@@ -1072,11 +1088,7 @@
"description-missing": "Benötigt die Installation von {app}"
},
"telegram": {
"description": "Schreibe {filepath}.",
"description-missing": "Benötigt die Installation von {app}"
},
"cava": {
"description": "Schreibe {filepath}.",
"description": "Schreibe {filepath}",
"description-missing": "Benötigt die Installation von {app}"
},
"vicinae": {
@@ -1262,9 +1274,11 @@
},
"temperature": {
"day": "Tag",
"day-description": "Steuert die Farbtemperatur tagsüber.",
"description": "Farbwärme für Nacht- und Tageszeit einstellen.",
"label": "Farbtemperatur",
"night": "Nacht"
"night": "Nacht",
"night-description": "Steuert die Farbtemperatur nachts."
}
},
"title": "Anzeige"
@@ -1368,7 +1382,8 @@
"description": "Ändern Sie Ihren Avatar.",
"label": "Profil"
},
"select-avatar": "Avatar auswählen"
"select-avatar": "Avatar auswählen",
"tooltip": "Nach Avatarbild suchen"
},
"screen-corners": {
"radius": {
@@ -1446,6 +1461,10 @@
"description": "Verwenden Sie ein benutzerdefiniertes Präfix zum Starten von Anwendungen anstelle der Standardmethode.",
"label": "Benutzerdefiniertes Start-Präfix aktivieren"
},
"grid-view": {
"description": "Elemente in einem Raster statt in einer Liste anzeigen.",
"label": "Rasteransicht"
},
"position": {
"description": "Wählen Sie, wo das Starter-Panel erscheint.",
"label": "Position"
@@ -1470,6 +1489,20 @@
"title": "Starter"
},
"location": {
"calendar": {
"cards": {
"section": {
"description": "Karten im Kalenderfeld organisieren und aktivieren/deaktivieren.",
"label": "Kalenderkarten"
}
},
"header": {
"label": "Kalenderkopfzeile"
},
"month": {
"label": "Kalendermonat"
}
},
"date-time": {
"12hour-format": {
"description": "Zeigt die Uhrzeit im 12-Stunden-Format auf dem Sperrbildschirm und im Kalender an. Die Uhr in der Statusleiste hat eigene Einstellungen.",
@@ -1671,7 +1704,29 @@
"label": "Allgemein"
}
},
"title": "On-Screen Display"
"title": "On-Screen Display",
"types": {
"brightness": {
"description": "OSD anzeigen, wenn sich die Bildschirmhelligkeit ändert.",
"label": "Helligkeit"
},
"input-volume": {
"description": "OSD anzeigen, wenn sich die Mikrofonlautstärke ändert.",
"label": "Eingangslautstärke"
},
"lockkey": {
"description": "OSD anzeigen, wenn Feststelltaste, Num-Taste oder Rollen-Taste umgeschaltet werden.",
"label": "Feststelltasten"
},
"section": {
"description": "Wählen Sie die Ereignisse aus, die das OSD auslösen. Wenn keine Ereignisse ausgewählt werden, lösen alle verfügbaren Ereignisse das OSD aus.",
"label": "OSD-Auslöseereignisse"
},
"volume": {
"description": "OSD anzeigen, wenn sich die Ausgabelautstärke ändert.",
"label": "Ausgangslautstärke"
}
}
},
"screen-recorder": {
"audio": {
@@ -1806,6 +1861,12 @@
"memory-section": {
"label": "Speicherverbrauch"
},
"network-section": {
"label": "Netzwerk"
},
"polling-interval": {
"label": "Abfrageintervall"
},
"temp-critical-threshold": {
"label": "Kritische Schwelle"
},
@@ -1816,7 +1877,7 @@
"label": "CPU-Temperatur"
},
"thresholds-section": {
"description": "Setzen Sie Schwellenwerte für Systemmetriken; Werte darüber werden hervorgehoben.",
"description": "Passe die Warn-/Kritisch-Schwellen und die Abfrageintervalle für jede Systemmetrik an.",
"label": "Schwellenwerte"
},
"title": "Systemmonitor",
@@ -1857,7 +1918,7 @@
"reset": "Deckkraft des gedimmten Desktops zurücksetzen"
},
"panel-background-opacity": {
"description": "Hintergrunddeckkraft für alle Paneele festlegen (Launcher, Control Center, Einstellungen usw.).",
"description": "Hintergrunddeckkraft für alle Paneele festlegen (Launcher, Kontrollzentrum, Einstellungen usw.).",
"label": "Paneel-Hintergrunddeckkraft"
},
"panels-attached-to-bar": {
@@ -2048,6 +2109,11 @@
"unavailable": "Zwischenablageverlauf nicht verfügbar",
"unavailable-desc": "Die 'cliphist' Anwendung ist nicht installiert. Bitte installieren Sie sie, um Zwischenablageverlauf-Features zu nutzen."
},
"dark-mode": {
"dark-mode": "Dunkler Modus",
"enabled": "Aktiviert",
"light-mode": "Heller Modus"
},
"do-not-disturb": {
"disabled": "'Nicht stören' deaktiviert",
"disabled-desc": "Alle Benachrichtigungen werden angezeigt.",
@@ -2128,64 +2194,70 @@
"tooltips": {
"add-widget": "Widget hinzufügen",
"bluetooth-devices": "Bluetooth-Geräte",
"brightness-at": "Helligkeit: {brightness}%\nRechtsklick für Einstellungen.\nScrollen zum Ändern der Helligkeit.",
"cancel-timer": "Timer abbrechen",
"brightness-at": "Helligkeit: {brightness}%",
"cancel-timer": "Timer",
"clear-history": "Verlauf löschen",
"click-to-start-recording": "Klicken zum Starten einer Bildschirmaufnahme",
"click-to-stop-recording": "Klicken zum Stoppen einer Bildschirmaufnahme",
"close": "Schließen",
"connect-disconnect-devices": "Linksklick zum Verbinden. Rechtsklick zum Vergessen.",
"click-to-start-recording": "Bildschirmrekorder (Aufnahme starten)",
"click-to-stop-recording": "Bildschirmrekorder (Aufnahme stoppen)",
"close": "Schließen-Button",
"connect-disconnect-devices": "Bluetooth-Gerät",
"delete-notification": "Benachrichtigung löschen",
"disable-keep-awake": "Klicken, um Wach halten zu deaktivieren.\nScrollen, um Zeitlimit anzupassen.",
"do-not-disturb-disabled": "'Nicht stören' deaktiviert",
"do-not-disturb-enabled": "'Nicht stören' aktiviert",
"enable-keep-awake": "Klicken, um Wach halten zu aktivieren.\nScrollen, um Zeitlimit anzupassen.",
"disable-keep-awake": "Wach halten",
"do-not-disturb-disabled": "Nicht stören",
"do-not-disturb-enabled": "Nicht stören",
"enable-keep-awake": "Wach halten",
"forget-network": "Netzwerk vergessen",
"home": "Home",
"input-muted": "Audio-Eingabe stummschalten",
"grid-view": "Rasteransicht",
"hidden-files-hide": "Versteckte Dateien",
"hidden-files-show": "Versteckte Dateien",
"home": "Home-Verzeichnis",
"input-muted": "Mikrofon",
"keep-awake": "Wach halten",
"keyboard-layout": "{layout} Tastaturlayout",
"manage-vpn": "VPN-Verbindungen verwalten",
"manage-wifi": "WLAN verwalten",
"microphone-volume-at": "Mikrofonlautstärke bei {volume}%.\nLinksklick für Einstellungen. Rechtsklick zum Stummschalten.\nScrollen zum Ändern der Lautstärke.",
"move-to-center-section": "Zur mittleren Sektion verschieben",
"move-to-left-section": "Zur linken Sektion verschieben",
"move-to-right-section": "Zur rechten Sektion verschieben",
"next-media": "chstes Medium",
"list-view": "Listenansicht",
"manage-vpn": "VPN-Verbindungen",
"manage-wifi": "WLAN",
"microphone-volume-at": "Mikrofonlautstärke: {volume}%",
"move-to-center-section": "Mittlere Sektion",
"move-to-left-section": "Linke Sektion",
"move-to-right-section": "Rechte Sektion",
"next-media": "Nächster Titel",
"next-month": "Nächster Monat",
"night-light-disabled": "Nachtlicht ist deaktiviert.\nLinksklick zum Wechseln des Modus.\nRechtsklick für Einstellungen.",
"night-light-enabled": "Nachtlicht ist aktiviert.\nLinksklick zum Wechseln des Modus.\nRechtsklick für Einstellungen.",
"night-light-forced": "Nachtlicht ist erzwungen.\nLinksklick zum Wechseln des Modus.\nRechtsklick für Einstellungen.",
"night-light-not-installed": "Nachtlicht ist nicht verfügbar.\nwlsunset ist nicht installiert.",
"noctalia-performance-disabled": "Noctalia-Leistungsmodus ist deaktiviert.\nLinksklick zum Aktivieren.",
"noctalia-performance-enabled": "Noctalia-Leistungsmodus ist aktiviert.\nLinksklick zum Deaktivieren.",
"open-control-center": "Kontrollzentrum öffnen",
"open-notification-history-disable-dnd": "Benachrichtigungsverlauf öffnen\nRechtsklick um 'Nicht stören' zu deaktivieren.",
"open-notification-history-enable-dnd": "Benachrichtigungsverlauf öffnen\nRechtsklick um 'Nicht stören' zu aktivieren.",
"open-settings": "Einstellungen öffnen",
"open-tray-dropdown": "Tray-Dropdown öffnen",
"open-wallpaper-selector": "Hintergrundbild-Auswahl öffnen",
"output-muted": "Audio-Ausgabe stummschalten",
"pause": "Pausieren",
"play": "Wiedergeben",
"power-profile": "'{profile}' Energieprofil",
"previous-media": "Vorheriges Medium",
"night-light-disabled": "Nachtlicht",
"night-light-enabled": "Nachtlicht",
"night-light-forced": "Nachtlicht",
"night-light-not-installed": "Nachtlicht (nicht verfügbar)",
"noctalia-performance-disabled": "Noctalia-Leistungsmodus",
"noctalia-performance-enabled": "Noctalia-Leistungsmodus",
"open-control-center": "Kontrollzentrum",
"open-notification-history-disable-dnd": "Benachrichtigungsverlauf",
"open-notification-history-enable-dnd": "Benachrichtigungsverlauf",
"open-settings": "Einstellungen",
"open-tray-dropdown": "System-Tray",
"open-wallpaper-selector": "Hintergrundbild-Auswahl",
"output-muted": "Audio-Ausgabe",
"pause": "Pause-Button",
"play": "Wiedergabe-Button",
"power-profile": "{profile} Energieprofil",
"previous-media": "Vorheriger Titel",
"previous-month": "Vorheriger Monat",
"refresh": "Aktualisieren",
"refresh-devices": "Geräte aktualisieren",
"refresh-wallhaven": "Wallhaven-Ergebnisse aktualisieren",
"refresh-wallpaper-list": "Hintergrundbild-Liste aktualisieren",
"remove-widget": "Widget entfernen",
"screen-recorder-not-installed": "Bildschirmrekorder ist nicht installiert",
"screen-recorder-not-installed": "Bildschirmrekorder (nicht installiert)",
"search": "Suchen",
"search-close": "Suche schließen",
"session-menu": "Sitzungsmenü",
"set-power-profile": "'{profile}' Energieprofil setzen",
"start-screen-recording": "Bildschirmaufnahme starten",
"stop-screen-recording": "Bildschirmaufnahme stoppen",
"switch-to-dark-mode": "Zum dunklen Modus wechseln",
"switch-to-light-mode": "Zum hellen Modus wechseln",
"up": "Nach oben",
"volume-at": "Lautstärke bei {volume}%.\nLinksklick für Einstellungen. Rechtsklick zum Stummschalten.\nScrollen zum Ändern der Lautstärke.",
"wallpaper-selector": "Linksklick: Hintergrundbild-Auswahl öffnen.\nRechtsklick: Zufälliges Hintergrundbild setzen.",
"set-power-profile": "{profile} Energieprofil",
"start-screen-recording": "Bildschirmrekorder",
"stop-screen-recording": "Bildschirmrekorder",
"switch-to-dark-mode": "Dunkler Modus",
"switch-to-light-mode": "Heller Modus",
"up": "Übergeordnetes Verzeichnis",
"volume-at": "Lautstärke: {volume}%",
"wallpaper-selector": "Hintergrundbild-Auswahl",
"widget-settings": "Widget-Einstellungen"
},
"wallpaper": {

File diff suppressed because it is too large Load Diff

View File

@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Predeterminado (Dispositivo de visualización)",
"description": "Seleccione qué dispositivo de batería mostrar.",
"label": "Dispositivo de batería"
},
"display-mode": {
"description": "Elige cómo te gustaría que apareciera este valor.",
"label": "Modo de visualización"
@@ -97,9 +102,13 @@
"control-center": {
"browse-file": "Explorar archivo",
"browse-library": "Explorar biblioteca",
"colorize-distro-logo": {
"description": "Aplica los colores del tema a tu logotipo de distribución.",
"label": "Colorear logotipo de la distribución"
"color-selection": {
"description": "Aplica colores del tema a los íconos.",
"label": "Seleccionar Color"
},
"enable-colorization": {
"description": "Habilita la coloración para el ícono del centro de control, aplicando colores del tema.",
"label": "Habilitar Coloración"
},
"icon": {
"description": "Selecciona un icono de la biblioteca o un archivo personalizado.",
@@ -227,14 +236,14 @@
"description": "Mostrar artista - título en lugar de título - artista.",
"label": "Mostrar primero al artista"
},
"show-visualizer": {
"description": "Mostrar un visualizador de audio cuando se reproduce música.",
"label": "Mostrar visualizador"
},
"show-progress-ring": {
"description": "Mostrar un indicador de progreso circular que muestre el progreso de la pista.",
"label": "Mostrar anillo de progreso"
},
"show-visualizer": {
"description": "Mostrar un visualizador de audio cuando se reproduce música.",
"label": "Mostrar visualizador"
},
"use-fixed-width": {
"description": "Cuando está activado, el widget siempre usará el ancho máximo en lugar de ajustarse dinámicamente al contenido.",
"label": "Usar Ancho Fijo"
@@ -271,9 +280,6 @@
}
},
"system-monitor": {
"cpu-critical-threshold": {
"label": "CPU Usage (Critical)"
},
"cpu-temperature": {
"description": "Mostrar lecturas de temperatura de CPU si están disponibles.",
"label": "Temperatura de la CPU"
@@ -282,25 +288,10 @@
"description": "Mostrar el porcentaje actual de uso de CPU.",
"label": "Uso de CPU"
},
"cpu-warning-threshold": {
"label": "CPU Usage (Warning)"
},
"disk-critical-threshold": {
"label": "Storage Space (Critical)"
},
"disk-path": {
"description": "Seleccione qué punto de montaje de disco desea supervisar.",
"label": "Ruta del disco"
},
"disk-warning-threshold": {
"label": "Storage Space (Warning)"
},
"mem-critical-threshold": {
"label": "Memory Usage (Critical)"
},
"mem-warning-threshold": {
"label": "Memory Usage (Warning)"
},
"memory-percentage": {
"description": "Mostrar el uso de memoria como porcentaje en lugar de valores absolutos.",
"label": "Memoria como porcentaje"
@@ -316,16 +307,6 @@
"storage-usage": {
"description": "Mostrar información del uso del espacio en disco.",
"label": "Uso de almacenamiento"
},
"temp-critical-threshold": {
"label": "CPU Temperature (Critical)"
},
"temp-warning-threshold": {
"label": "CPU Temperature (Warning)"
},
"thresholds": {
"description": "Establece umbrales para alertas de métricas del sistema. Resalta cuando se superan.",
"header": "Configuración de Umbrales"
}
},
"taskbar": {
@@ -373,14 +354,14 @@
"description": "Número de caracteres a mostrar de los nombres de espacios de trabajo (1-10).",
"label": "Número de caracteres"
},
"hide-unoccupied": {
"description": "No mostrar espacios de trabajo sin ventanas.",
"label": "Ocultar desocupados"
},
"follow-focused-screen": {
"description": "Mostrar espacios de trabajo de la pantalla actualmente enfocada, en lugar de la pantalla donde se encuentra la barra.",
"label": "Seguir Pantalla Enfocada"
},
"hide-unoccupied": {
"description": "No mostrar espacios de trabajo sin ventanas.",
"label": "Ocultar desocupados"
},
"label-mode": {
"description": "Elegir cómo se muestran las etiquetas de los espacios de trabajo.",
"label": "Modo de etiqueta"
@@ -389,8 +370,8 @@
}
},
"battery": {
"battery-level": "Nivel de batería",
"brightness": "Brillo",
"charge-level": "Nivel de carga",
"charging": "Cargando.",
"charging-rate": "Tasa de carga: {rate} W.",
"discharging": "Descargando.",
@@ -412,6 +393,7 @@
"blocked": "Bloqueado",
"connect": "Conectar",
"connected-devices": "Dispositivos conectados",
"connecting": "Conectando...",
"disabled": "Bluetooth está desactivado",
"disconnect": "Desconectar",
"enable-message": "Activa Bluetooth para ver los dispositivos disponibles.",
@@ -426,6 +408,19 @@
"panel": {
"week": "Semana"
},
"timer": {
"countdown": "Cuenta regresiva",
"duration": "Duración",
"hours": "h",
"minutes": "m",
"pause": "Pausa",
"reset": "Restablecer",
"seconds": "s",
"start": "Comenzar",
"stopwatch": "Cronómetro",
"timer": "Temporizador",
"title": "Temporizador"
},
"weather": {
"loading": "Cargando el clima…"
}
@@ -479,7 +474,7 @@
"open-mixer": "Mezclador de audio",
"open-settings": "Abrir ajustes",
"pause": "Pausa",
"play": "Jugar",
"play": "Reproducir",
"previous": "Anterior",
"random-wallpaper": "Fondo de pantalla aleatorio",
"toggle-mute": "Activar/desactivar silencio",
@@ -518,14 +513,20 @@
"no-notifications": "No hay notificaciones",
"title": "Notificaciones"
},
"range": {
"all": "Todas",
"earlier": "Anteriores",
"today": "Hoy",
"yesterday": "Ayer"
},
"time": {
"now": "ahora",
"diffM": "hace 1 minuto",
"diffMM": "hace {diff} minutos",
"diffD": "hace 1 día",
"diffDD": "hace {diff} días",
"diffH": "hace 1 hora",
"diffHH": "hace {diff} horas",
"diffD": "hace 1 día",
"diffDD": "hace {diff} días"
"diffM": "hace 1 minuto",
"diffMM": "hace {diff} minutos",
"now": "ahora"
}
},
"options": {
@@ -545,6 +546,7 @@
},
"colors": {
"error": "Error",
"none": "Ninguno",
"onSurface": "En la superficie",
"primary": "Primario",
"secondary": "Secundario",
@@ -706,7 +708,7 @@
"enabled": "Bluetooth"
},
"tooltip": {
"action": "Hacer clic para gestionar los dispositivos Bluetooth"
"action": "Bluetooth"
}
},
"keepAwake": {
@@ -715,7 +717,7 @@
"enabled": "Mantener despierto"
},
"tooltip": {
"action": "Hacer clic para alternar el modo Mantener despierto"
"action": "Mantener despierto"
}
},
"nightLight": {
@@ -725,7 +727,7 @@
"forced": "Luz nocturna"
},
"tooltip": {
"action": "Hacer clic para alternar el modo Luz nocturna\nClic derecho: Abrir configuración"
"action": "Luz nocturna"
}
},
"notifications": {
@@ -734,7 +736,7 @@
"enabled": "Notificaciones"
},
"tooltip": {
"action": "Clic izquierdo: Abrir historial de notificaciones\nClic derecho: Alternar No molestar"
"action": "Notificaciones"
}
},
"powerProfile": {
@@ -742,7 +744,7 @@
"unavailable": "Perfil de energía"
},
"tooltip": {
"action": "Hacer clic para cambiar el perfil de energía",
"action": "Perfil de energía",
"disabled": "Instala power-profiles-daemon para usar perfiles de energía"
}
},
@@ -752,13 +754,13 @@
"stopped": "Grabar"
},
"tooltip": {
"action": "Hacer clic para iniciar/detener la grabación de pantalla"
"action": "Grabadora de pantalla"
}
},
"wallpaperSelector": {
"label": "Fondo de pantalla",
"tooltip": {
"action": "Clic izquierdo: Abrir selector de fondo de pantalla\nClic derecho: Establecer fondo de pantalla aleatorio"
"action": "Selector de fondos de pantalla"
}
},
"wifi": {
@@ -768,7 +770,7 @@
"wifi": "Wi-Fi"
},
"tooltip": {
"action": "Hacer clic para gestionar las conexiones Wi-Fi"
"action": "Wi-Fi"
}
}
},
@@ -794,6 +796,8 @@
},
"noctalia": {
"download-latest": "Descargar la última versión",
"git-commit": "Commit de Git:",
"git-commit-loading": "Cargando...",
"installed-version": "Versión instalada:",
"latest-version": "Última versión:",
"section": {
@@ -1040,15 +1044,27 @@
}
},
"templates": {
"compositors": {
"description": "Tematización del compositor.",
"label": "Compositores",
"niri": {
"description": "Escribir {filepath}. Requiere niri v25.11+",
"description-missing": "Requiere que {app} esté instalada."
}
},
"misc": {
"description": "Opciones de configuración adicionales.",
"label": "Varios",
"description": "Crea tus propias plantillas",
"label": "Avanzado",
"user-templates": {
"description": "Habilitar configuración de Matugen definida por el usuario. Se creará un archivo de plantilla en ~/.config/noctalia/user-templates.toml al activar por primera vez",
"label": "Plantillas de usuario"
"description": "Solo habilita si sabes lo que estás haciendo, consulta nuestra documentación en línea",
"label": "Habilitar plantillas de usuario"
}
},
"programs": {
"cava": {
"description": "Escribe {filepath}.",
"description-missing": "Requiere que {app} esté instalado/a."
},
"code": {
"description": "Escribe {filepath}. El tema Hyprluna debe ser instalado y activado manualmente.",
"description-missing": "No se detectó cliente de Code. Instala VSCode o VSCodium."
@@ -1075,10 +1091,6 @@
"description": "Escribe {filepath}.",
"description-missing": "Requiere que {app} esté instalado/a."
},
"cava": {
"description": "Escribe {filepath}.",
"description-missing": "Requiere que {app} esté instalado/a."
},
"vicinae": {
"description": "Escribir {filepath} y recargar",
"description-missing": "Requiere que {app} esté instalado"
@@ -1262,9 +1274,11 @@
},
"temperature": {
"day": "Día",
"day-description": "Controla la temperatura durante el día.",
"description": "Establece la calidez del color para la noche y el día.",
"label": "Temperatura de color",
"night": "Noche"
"night": "Noche",
"night-description": "Controla la temperatura durante la noche."
}
},
"title": "Pantalla"
@@ -1368,7 +1382,8 @@
"description": "Edita los detalles de tu usuario y tu avatar.",
"label": "Perfil"
},
"select-avatar": "Seleccionar imagen de avatar"
"select-avatar": "Seleccionar imagen de avatar",
"tooltip": "Buscar imagen de avatar"
},
"screen-corners": {
"radius": {
@@ -1446,6 +1461,10 @@
"description": "Usar un prefijo personalizado para lanzar aplicaciones en lugar del método predeterminado.",
"label": "Habilitar prefijo de lanzamiento personalizado"
},
"grid-view": {
"description": "Mostrar elementos en una cuadrícula en lugar de una lista.",
"label": "Vista de cuadrícula"
},
"position": {
"description": "Elige dónde aparece el panel del lanzador.",
"label": "Posición"
@@ -1470,6 +1489,20 @@
"title": "Lanzador"
},
"location": {
"calendar": {
"cards": {
"section": {
"description": "Organizar y activar/desactivar tarjetas en el panel del calendario.",
"label": "Tarjetas de calendario"
}
},
"header": {
"label": "Encabezado del calendario"
},
"month": {
"label": "Mes del calendario"
}
},
"date-time": {
"12hour-format": {
"description": "Muestra la hora en formato de 12 horas en la pantalla de bloqueo y el calendario. El reloj de la barra tiene su propia configuración.",
@@ -1671,7 +1704,29 @@
"label": "General"
}
},
"title": "Visualización en pantalla"
"title": "Visualización en pantalla",
"types": {
"brightness": {
"description": "Mostrar el OSD cuando cambie el brillo de la pantalla.",
"label": "Brillo"
},
"input-volume": {
"description": "Mostrar el OSD cuando cambie el volumen del micrófono.",
"label": "Volumen de entrada"
},
"lockkey": {
"description": "Mostrar el OSD cuando se activen o desactiven Bloq Mayús, Bloq Num o Bloq Despl.",
"label": "Teclas de bloqueo"
},
"section": {
"description": "Seleccione los eventos que activan el OSD. Si no se selecciona ningún evento, todos los eventos disponibles activarán el OSD.",
"label": "Eventos de activación OSD"
},
"volume": {
"description": "Mostrar el OSD cuando cambie el volumen de salida de audio.",
"label": "Volumen de salida"
}
}
},
"screen-recorder": {
"audio": {
@@ -1806,6 +1861,12 @@
"memory-section": {
"label": "Uso de memoria"
},
"network-section": {
"label": "Red"
},
"polling-interval": {
"label": "Intervalo de sondeo"
},
"temp-critical-threshold": {
"label": "Umbral crítico"
},
@@ -1816,7 +1877,7 @@
"label": "Temperatura de la CPU"
},
"thresholds-section": {
"description": "Establece umbrales para métricas del sistema; los valores por encima se resaltarán.",
"description": "Ajusta los umbrales de advertencia/crítico y los intervalos de sondeo para cada métrica del sistema.",
"label": "Umbrales"
},
"title": "Monitor del sistema",
@@ -2048,6 +2109,11 @@
"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."
},
"dark-mode": {
"dark-mode": "Modo oscuro",
"enabled": "Activado",
"light-mode": "Modo claro"
},
"do-not-disturb": {
"disabled": "'No molestar' desactivado",
"disabled-desc": "Mostrando todas las notificaciones.",
@@ -2128,64 +2194,70 @@
"tooltips": {
"add-widget": "Añadir widget",
"bluetooth-devices": "Dispositivos Bluetooth",
"brightness-at": "Brillo: {brightness}%\nClic derecho para configuración.\nDesplaza para modificar el brillo.",
"cancel-timer": "Cancelar temporizador",
"brightness-at": "Brillo: {brightness}%",
"cancel-timer": "Timer",
"clear-history": "Limpiar historial",
"click-to-start-recording": "Haz clic para iniciar la grabación",
"click-to-stop-recording": "Haz clic para detener la grabación",
"close": "Cerrar",
"connect-disconnect-devices": "Clic izquierdo para conectar. Clic derecho para olvidar.",
"click-to-start-recording": "Grabadora de pantalla (iniciar grabación)",
"click-to-stop-recording": "Grabadora de pantalla (detener grabación)",
"close": "Botón cerrar",
"connect-disconnect-devices": "Dispositivo Bluetooth",
"delete-notification": "Eliminar notificación",
"disable-keep-awake": "Haz clic para desactivar mantener despierto.\nDesplázate para ajustar el tiempo de espera.",
"do-not-disturb-disabled": "'No molestar' desactivado",
"do-not-disturb-enabled": "'No molestar' activado",
"enable-keep-awake": "Haz clic para activar mantener despierto.\nDesplázate para ajustar el tiempo de espera.",
"disable-keep-awake": "Mantener despierto",
"do-not-disturb-disabled": "No molestar",
"do-not-disturb-enabled": "No molestar",
"enable-keep-awake": "Mantener despierto",
"forget-network": "Olvidar red",
"home": "Inicio",
"input-muted": "Silenciar entrada de audio",
"grid-view": "Vista de cuadrícula",
"hidden-files-hide": "Archivos ocultos",
"hidden-files-show": "Archivos ocultos",
"home": "Directorio home",
"input-muted": "Micrófono",
"keep-awake": "Mantener despierto",
"keyboard-layout": "Distribución de teclado {layout}",
"manage-vpn": "Administrar conexiones VPN",
"manage-wifi": "Gestionar Wi-Fi",
"microphone-volume-at": "Volumen del micrófono al {volume}%.\nClic izquierdo para ajustes. Clic derecho para activar/desactivar el silencio.\nDesplázate para modificar el volumen.",
"move-to-center-section": "Mover a la sección central",
"move-to-left-section": "Mover a la sección izquierda",
"move-to-right-section": "Mover a la sección derecha",
"next-media": "Siguiente medio",
"list-view": "Vista de lista",
"manage-vpn": "Conexiones VPN",
"manage-wifi": "Wi-Fi",
"microphone-volume-at": "Volumen del micrófono: {volume}%",
"move-to-center-section": "Sección central",
"move-to-left-section": "Sección izquierda",
"move-to-right-section": "Sección derecha",
"next-media": "Siguiente pista",
"next-month": "Mes siguiente",
"night-light-disabled": "Luz nocturna desactivada.\nClic izquierdo para cambiar de modo.\nClic derecho para acceder a la configuración.",
"night-light-enabled": "Luz nocturna activada.\nClic izquierdo para cambiar de modo.\nClic derecho para acceder a la configuración.",
"night-light-forced": "Luz nocturna forzada.\nClic izquierdo para cambiar de modo.\nClic derecho para acceder a la configuración.",
"night-light-not-installed": "Luz nocturna no disponible.\nwlsunset no está instalado.",
"noctalia-performance-disabled": "El modo de rendimiento de Noctalia está desactivado.\nClic izquierdo para activar.",
"noctalia-performance-enabled": "El modo de rendimiento de Noctalia está activado.\nClic izquierdo para desactivar.",
"open-control-center": "Abrir el centro de control",
"open-notification-history-disable-dnd": "Abrir historial de notificaciones\nClic derecho para desactivar \"No molestar\".",
"open-notification-history-enable-dnd": "Abrir historial de notificaciones\nClic derecho para activar \"No molestar\".",
"open-settings": "Abrir configuración",
"open-tray-dropdown": "Abrir menú desplegable de bandeja",
"open-wallpaper-selector": "Abrir selector de fondos de pantalla",
"output-muted": "Silenciar salida de audio",
"pause": "Pausa",
"play": "Reproducir",
"power-profile": "Perfil de energía '{profile}'",
"previous-media": "Medio anterior",
"night-light-disabled": "Luz nocturna",
"night-light-enabled": "Luz nocturna",
"night-light-forced": "Luz nocturna",
"night-light-not-installed": "Luz nocturna (no disponible)",
"noctalia-performance-disabled": "Modo de rendimiento Noctalia",
"noctalia-performance-enabled": "Modo de rendimiento Noctalia",
"open-control-center": "Centro de control",
"open-notification-history-disable-dnd": "Historial de notificaciones",
"open-notification-history-enable-dnd": "Historial de notificaciones",
"open-settings": "Configuración",
"open-tray-dropdown": "Bandeja del sistema",
"open-wallpaper-selector": "Selector de fondos de pantalla",
"output-muted": "Salida de audio",
"pause": "Botón pausa",
"play": "Botón reproducir",
"power-profile": "{profile} perfil de energía",
"previous-media": "Pista anterior",
"previous-month": "Mes anterior",
"refresh": "Actualizar",
"refresh-devices": "Actualizar dispositivos",
"refresh-wallhaven": "Actualizar resultados de Wallhaven",
"refresh-wallpaper-list": "Actualizar lista de fondos de pantalla",
"remove-widget": "Eliminar widget",
"screen-recorder-not-installed": "La grabadora de pantalla no está instalada",
"screen-recorder-not-installed": "Grabadora de pantalla (no instalada)",
"search": "Buscar",
"search-close": "Cerrar búsqueda",
"session-menu": "Menú de sesión",
"set-power-profile": "Establecer perfil de energía \"{profile}\"",
"start-screen-recording": "Iniciar grabación de pantalla",
"stop-screen-recording": "Detener grabación de pantalla",
"switch-to-dark-mode": "Cambiar a modo oscuro",
"switch-to-light-mode": "Cambiar a modo claro",
"up": "Arriba",
"volume-at": "Volumen de salida al {volume}%.\nClic izquierdo para ajustes. Clic derecho para activar/desactivar el silencio.\nDesplázate para modificar el volumen.",
"wallpaper-selector": "Clic izquierdo: Abrir selector de fondos de pantalla.\nClic derecho: Establecer fondo de pantalla aleatorio.",
"set-power-profile": "{profile} perfil de energía",
"start-screen-recording": "Grabadora de pantalla",
"stop-screen-recording": "Grabadora de pantalla",
"switch-to-dark-mode": "Modo oscuro",
"switch-to-light-mode": "Modo claro",
"up": "Directorio superior",
"volume-at": "Volumen de salida: {volume}%",
"wallpaper-selector": "Selector de fondos de pantalla",
"widget-settings": "Configuración del widget"
},
"wallpaper": {

View File

@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Par défaut (périphérique d'affichage)",
"description": "Sélectionnez le périphérique de batterie à afficher.",
"label": "Dispositif à pile"
},
"display-mode": {
"description": "Choisissez comment vous souhaitez que cette valeur apparaisse.",
"label": "Mode d'affichage"
@@ -97,9 +102,13 @@
"control-center": {
"browse-file": "Parcourir les fichiers",
"browse-library": "Parcourir la bibliothèque",
"colorize-distro-logo": {
"description": "Appliquez les couleurs du thème au logo de votre distribution.",
"label": "Coloriser le logo de la distribution"
"color-selection": {
"description": "Applique les couleurs du thème aux icônes.",
"label": "Sélectionner la Couleur"
},
"enable-colorization": {
"description": "Active la coloration pour l'icône du centre de contrôle, en appliquant les couleurs du thème.",
"label": "Activer la Coloration"
},
"icon": {
"description": "Sélectionnez une icône de la bibliothèque ou un fichier personnalisé.",
@@ -227,14 +236,14 @@
"description": "Afficher artiste - titre au lieu de titre - artiste.",
"label": "Afficher l'artiste en premier"
},
"show-visualizer": {
"description": "Afficher un visualiseur audio quand la musique est en cours de lecture.",
"label": "Afficher le visualiseur"
},
"show-progress-ring": {
"description": "Afficher un indicateur de progression circulaire montrant la progression de la piste.",
"label": "Afficher l'anneau de progression"
},
"show-visualizer": {
"description": "Afficher un visualiseur audio quand la musique est en cours de lecture.",
"label": "Afficher le visualiseur"
},
"use-fixed-width": {
"description": "Lorsque activé, le widget utilisera toujours la largeur maximale au lieu de s'ajuster dynamiquement au contenu.",
"label": "Utiliser une Largeur Fixe"
@@ -271,9 +280,6 @@
}
},
"system-monitor": {
"cpu-critical-threshold": {
"label": "CPU Usage (Critical)"
},
"cpu-temperature": {
"description": "Afficher les lectures de température du CPU si disponibles.",
"label": "Température du CPU"
@@ -282,25 +288,10 @@
"description": "Afficher le pourcentage d'utilisation actuel du CPU.",
"label": "Utilisation du CPU"
},
"cpu-warning-threshold": {
"label": "CPU Usage (Warning)"
},
"disk-critical-threshold": {
"label": "Storage Space (Critical)"
},
"disk-path": {
"description": "Sélectionnez le point de montage du disque à surveiller.",
"label": "Chemin du disque"
},
"disk-warning-threshold": {
"label": "Storage Space (Warning)"
},
"mem-critical-threshold": {
"label": "Memory Usage (Critical)"
},
"mem-warning-threshold": {
"label": "Memory Usage (Warning)"
},
"memory-percentage": {
"description": "Afficher l'utilisation de la mémoire en pourcentage au lieu de valeurs absolues.",
"label": "Mémoire en pourcentage"
@@ -316,16 +307,6 @@
"storage-usage": {
"description": "Afficher les informations d'utilisation de l'espace disque.",
"label": "Utilisation du stockage"
},
"temp-critical-threshold": {
"label": "CPU Temperature (Critical)"
},
"temp-warning-threshold": {
"label": "CPU Temperature (Warning)"
},
"thresholds": {
"description": "Configurez les seuils des indicateurs visuels. Les indicateurs d'avertissement/critique apparaissent en couleurs mises en surbrillance lorsque les valeurs dépassent ces limites.",
"header": "Seuils"
}
},
"taskbar": {
@@ -373,14 +354,14 @@
"description": "Nombre de caractères à afficher des noms d'espaces de travail (1-10).",
"label": "Nombre de caractères"
},
"hide-unoccupied": {
"description": "Ne pas afficher les espaces de travail sans fenêtres.",
"label": "Masquer les inoccupés"
},
"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.",
"label": "Suivre l'Écran Ciblé"
},
"hide-unoccupied": {
"description": "Ne pas afficher les espaces de travail sans fenêtres.",
"label": "Masquer les inoccupés"
},
"label-mode": {
"description": "Choisir comment les étiquettes d'espace de travail sont affichées.",
"label": "Mode d'étiquette"
@@ -389,8 +370,8 @@
}
},
"battery": {
"battery-level": "Niveau de batterie",
"brightness": "Luminosité",
"charge-level": "Niveau de charge",
"charging": "En charge.",
"charging-rate": "Taux de charge : {rate} W.",
"discharging": "En décharge.",
@@ -412,11 +393,12 @@
"blocked": "Bloqué",
"connect": "Connecter",
"connected-devices": "Appareils connectés",
"connecting": "Connexion en cours...",
"disabled": "Le Bluetooth est désactivé",
"disconnect": "Déconnecter",
"enable-message": "Activez le Bluetooth pour voir les appareils disponibles.",
"known-devices": "Appareils connus",
"pairing": "Appairage...",
"pairing": "Appairage en cours...",
"pairing-mode": "Assurez-vous que votre appareil est en mode d'appairage.",
"scanning": "Recherche d'appareils en cours...",
"title": "Bluetooth"
@@ -426,6 +408,19 @@
"panel": {
"week": "Semaine"
},
"timer": {
"countdown": "Compte à rebours",
"duration": "Durée",
"hours": "h",
"minutes": "m",
"pause": "Pause",
"reset": "Réinitialiser",
"seconds": "s",
"start": "Commencer",
"stopwatch": "Chronomètre",
"timer": "Minuteur",
"title": "Minuteur"
},
"weather": {
"loading": "Chargement de la météo…"
}
@@ -518,14 +513,20 @@
"no-notifications": "Aucune notification",
"title": "Notifications"
},
"range": {
"all": "Tout",
"earlier": "Plus anciennes",
"today": "Aujourd'hui",
"yesterday": "Hier"
},
"time": {
"now": "maintenant",
"diffM": "il y a 1 minute",
"diffMM": "il y a {diff} minutes",
"diffD": "il y a 1 jour",
"diffDD": "il y a {diff} jours",
"diffH": "il y a 1 heure",
"diffHH": "il y a {diff} heures",
"diffD": "il y a 1 jour",
"diffDD": "il y a {diff} jours"
"diffM": "il y a 1 minute",
"diffMM": "il y a {diff} minutes",
"now": "maintenant"
}
},
"options": {
@@ -545,6 +546,7 @@
},
"colors": {
"error": "Erreur",
"none": "Aucun",
"onSurface": "En surface",
"primary": "Primaire",
"secondary": "Secondaire",
@@ -706,7 +708,7 @@
"enabled": "Bluetooth"
},
"tooltip": {
"action": "Cliquer pour gérer les appareils Bluetooth"
"action": "Bluetooth"
}
},
"keepAwake": {
@@ -715,7 +717,7 @@
"enabled": "Rester éveillé"
},
"tooltip": {
"action": "Cliquer pour basculer le mode Rester éveillé"
"action": "Rester éveillé"
}
},
"nightLight": {
@@ -725,7 +727,7 @@
"forced": "Lumière nocturne"
},
"tooltip": {
"action": "Cliquer pour basculer le mode Lumière nocturne\nClic droit : Ouvrir les paramètres"
"action": "Lumière nocturne"
}
},
"notifications": {
@@ -734,7 +736,7 @@
"enabled": "Notifications"
},
"tooltip": {
"action": "Clic gauche : Ouvrir l'historique des notifications\nClic droit : Basculer Ne pas déranger"
"action": "Notifications"
}
},
"powerProfile": {
@@ -742,7 +744,7 @@
"unavailable": "Profil d'alimentation"
},
"tooltip": {
"action": "Cliquer pour changer de profil d'alimentation",
"action": "Profil d'alimentation",
"disabled": "Installez power-profiles-daemon pour utiliser les profils d'alimentation"
}
},
@@ -752,13 +754,13 @@
"stopped": "Enregistrer"
},
"tooltip": {
"action": "Cliquer pour démarrer/arrêter l'enregistrement d'écran"
"action": "Enregistreur d'écran"
}
},
"wallpaperSelector": {
"label": "Fond d'écran",
"tooltip": {
"action": "Clic gauche : Ouvrir le sélecteur de fond d'écran\nClic droit : Définir un fond d'écran aléatoire"
"action": "Sélecteur de fond d'écran"
}
},
"wifi": {
@@ -768,7 +770,7 @@
"wifi": "Wi-Fi"
},
"tooltip": {
"action": "Cliquer pour gérer les connexions Wi-Fi"
"action": "Wi-Fi"
}
}
},
@@ -794,6 +796,8 @@
},
"noctalia": {
"download-latest": "Télécharger la dernière version",
"git-commit": "Commit Git :",
"git-commit-loading": "Chargement...",
"installed-version": "Version installée :",
"latest-version": "Dernière version :",
"section": {
@@ -1040,15 +1044,27 @@
}
},
"templates": {
"compositors": {
"description": "Thématisation du compositeur.",
"label": "Compositeurs",
"niri": {
"description": "Écrire {filepath}. Requiert niri v25.11+",
"description-missing": "Nécessite l'installation de {app}"
}
},
"misc": {
"description": "Options de configuration supplémentaires.",
"label": "Divers",
"description": "Créez vos propres modèles",
"label": "Avancé",
"user-templates": {
"description": "Activer la configuration Matugen définie par l'utilisateur. Un fichier modèle sera créé dans ~/.config/noctalia/user-templates.toml lors de la première activation",
"label": "Modèles utilisateur"
"description": "N'activez que si vous savez ce que vous faites, consultez notre documentation en ligne",
"label": "Activer les modèles utilisateur"
}
},
"programs": {
"cava": {
"description": "Écrire {filepath}.",
"description-missing": "Nécessite l'installation de {app}"
},
"code": {
"description": "Écrire {filepath}. Le thème Hyprluna doit être installé et activé manuellement.",
"description-missing": "Aucun client Code détecté. Installez VSCode ou VSCodium."
@@ -1075,10 +1091,6 @@
"description": "Écrire {filepath}.",
"description-missing": "Nécessite l'installation de {app}"
},
"cava": {
"description": "Écrire {filepath}.",
"description-missing": "Nécessite l'installation de {app}"
},
"vicinae": {
"description": "Écrire {filepath} et recharger",
"description-missing": "Nécessite que le lanceur {app} soit installé"
@@ -1262,9 +1274,11 @@
},
"temperature": {
"day": "Jour",
"day-description": "Contrôle la température pendant la journée.",
"description": "Réglez la chaleur des couleurs pour la nuit et le jour.",
"label": "Température de couleur",
"night": "Nuit"
"night": "Nuit",
"night-description": "Contrôle la température pendant la nuit."
}
},
"title": "Affichage"
@@ -1368,7 +1382,8 @@
"description": "Modifiez vos informations utilisateur et votre avatar.",
"label": "Profil"
},
"select-avatar": "Sélectionner une image d'avatar"
"select-avatar": "Sélectionner une image d'avatar",
"tooltip": "Parcourir pour une image davatar"
},
"screen-corners": {
"radius": {
@@ -1446,6 +1461,10 @@
"description": "Utiliser un préfixe personnalisé pour lancer les applications au lieu de la méthode par défaut.",
"label": "Activer le préfixe de lancement personnalisé"
},
"grid-view": {
"description": "Afficher les éléments dans une grille au lieu d'une liste.",
"label": "Vue grille"
},
"position": {
"description": "Choisissez où le panneau du lanceur apparaît.",
"label": "Position"
@@ -1470,6 +1489,20 @@
"title": "Lanceur"
},
"location": {
"calendar": {
"cards": {
"section": {
"description": "Organiser et activer/désactiver les cartes dans le panneau de calendrier.",
"label": "Cartes de calendrier"
}
},
"header": {
"label": "En-tête du calendrier"
},
"month": {
"label": "Mois calendaire"
}
},
"date-time": {
"12hour-format": {
"description": "Affiche l'heure au format 12 heures sur l'écran de verrouillage et dans le calendrier. L'horloge de la barre possède ses propres paramètres.",
@@ -1671,7 +1704,29 @@
"label": "Général"
}
},
"title": "Affichage à l'écran"
"title": "Affichage à l'écran",
"types": {
"brightness": {
"description": "Afficher l'OSD lorsque la luminosité de l'écran change.",
"label": "Luminosité"
},
"input-volume": {
"description": "Afficher l'OSD lorsque le volume du microphone change.",
"label": "Volume d'entrée"
},
"lockkey": {
"description": "Afficher l'OSD lorsque Verr Maj, Verr Num ou Arrêt défil est activée ou désactivée.",
"label": "Touches de verrouillage"
},
"section": {
"description": "Sélectionnez les événements qui déclenchent l'OSD. Si aucun événement n'est sélectionné, tous les événements disponibles déclencheront l'OSD.",
"label": "Événements de déclenchement OSD"
},
"volume": {
"description": "Afficher l'OSD lorsque le volume de sortie audio change.",
"label": "Volume de sortie"
}
}
},
"screen-recorder": {
"audio": {
@@ -1806,6 +1861,12 @@
"memory-section": {
"label": "Utilisation mémoire"
},
"network-section": {
"label": "Réseau"
},
"polling-interval": {
"label": "Intervalle d'interrogation"
},
"temp-critical-threshold": {
"label": "Seuil critique"
},
@@ -1816,7 +1877,7 @@
"label": "Température CPU"
},
"thresholds-section": {
"description": "Définir des seuils pour les métriques système ; les valeurs supérieures à ces seuils seront mises en évidence.",
"description": "Ajustez les seuils davertissement/critiques et les intervalles dinterrogation pour chaque métrique système.",
"label": "Seuils"
},
"title": "Moniteur système",
@@ -2048,6 +2109,11 @@
"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."
},
"dark-mode": {
"dark-mode": "Mode sombre",
"enabled": "Activé",
"light-mode": "Mode clair"
},
"do-not-disturb": {
"disabled": "'Ne pas déranger' désactivé",
"disabled-desc": "Affichage de toutes les notifications.",
@@ -2128,64 +2194,70 @@
"tooltips": {
"add-widget": "Ajouter un widget",
"bluetooth-devices": "Appareils Bluetooth",
"brightness-at": "Luminosité : {brightness}%\nClic droit pour les paramètres.\nFaites défiler pour modifier la luminosité.",
"cancel-timer": "Annuler le minuteur",
"brightness-at": "Luminosité : {brightness}%",
"cancel-timer": "Minuteur",
"clear-history": "Effacer l'historique",
"click-to-start-recording": "Cliquez pour démarrer l'enregistrement",
"click-to-stop-recording": "Cliquez pour arrêter l'enregistrement",
"close": "Fermer",
"connect-disconnect-devices": "Clic gauche pour connecter. Clic droit pour oublier.",
"click-to-start-recording": "Enregistreur d'écran (démarrer l'enregistrement)",
"click-to-stop-recording": "Enregistreur d'écran (arrêter l'enregistrement)",
"close": "Bouton fermer",
"connect-disconnect-devices": "Appareil Bluetooth",
"delete-notification": "Supprimer la notification",
"disable-keep-awake": "Cliquez pour désactiver le mode 'rester éveillé'.\nFaites défiler pour ajuster le délai.",
"do-not-disturb-disabled": "'Ne pas déranger' désactivé",
"do-not-disturb-enabled": "'Ne pas déranger' activé",
"enable-keep-awake": "Cliquez pour activer le mode 'rester éveillé'.\nFaites défiler pour ajuster le délai.",
"disable-keep-awake": "Rester éveillé",
"do-not-disturb-disabled": "Ne pas déranger",
"do-not-disturb-enabled": "Ne pas déranger",
"enable-keep-awake": "Rester éveillé",
"forget-network": "Oublier le réseau",
"home": "Accueil",
"input-muted": "Couper l'entrée audio",
"grid-view": "Vue en grille",
"hidden-files-hide": "Fichiers cachés",
"hidden-files-show": "Fichiers cachés",
"home": "Répertoire home",
"input-muted": "Microphone",
"keep-awake": "Rester éveillé",
"keyboard-layout": "Disposition du clavier {layout}",
"manage-vpn": "Gérer les connexions VPN",
"manage-wifi": "Gérer le Wi-Fi",
"microphone-volume-at": "Volume du microphone à {volume}%.\nClic gauche pour les paramètres. Clic droit pour activer/désactiver le mode muet.\nFaites défiler pour modifier le volume.",
"move-to-center-section": "Déplacer vers la section centrale",
"move-to-left-section": "Déplacer vers la section de gauche",
"move-to-right-section": "Déplacer vers la section de droite",
"next-media": "Média suivant",
"list-view": "Vue en liste",
"manage-vpn": "Connexions VPN",
"manage-wifi": "Wi-Fi",
"microphone-volume-at": "Volume du microphone : {volume}%",
"move-to-center-section": "Section centrale",
"move-to-left-section": "Section de gauche",
"move-to-right-section": "Section de droite",
"next-media": "Piste suivante",
"next-month": "Mois suivant",
"night-light-disabled": "L'éclairage nocturne est désactivé.\nClic gauche pour changer de mode.\nClic droit pour accéder aux paramètres.",
"night-light-enabled": "L'éclairage nocturne est activé.\nClic gauche pour changer de mode.\nClic droit pour accéder aux paramètres.",
"night-light-forced": "L'éclairage nocturne est forcé.\nClic gauche pour changer de mode.\nClic droit pour accéder aux paramètres.",
"night-light-not-installed": "L'éclairage nocturne n'est pas disponible.\nwlsunset n'est pas installé.",
"noctalia-performance-disabled": "Le mode performance Noctalia est désactivé.\nClic gauche pour activer.",
"noctalia-performance-enabled": "Le mode performance Noctalia est activé.\nClic gauche pour désactiver.",
"open-control-center": "Ouvrir le centre de contrôle",
"open-notification-history-disable-dnd": "Ouvrir l'historique des notifications\nClic droit pour désactiver \"Ne pas déranger\".",
"open-notification-history-enable-dnd": "Ouvrir l'historique des notifications\nClic droit pour activer \"Ne pas déranger\".",
"open-settings": "Ouvrir les paramètres",
"open-tray-dropdown": "Ouvrir le menu déroulant de la barre d'état",
"open-wallpaper-selector": "Ouvrir le sélecteur de fond d'écran",
"output-muted": "Couper la sortie audio",
"pause": "Pause",
"play": "Lecture",
"power-profile": "Profil d'alimentation '{profile}'",
"previous-media": "Média précédent",
"night-light-disabled": "Éclairage nocturne",
"night-light-enabled": "Éclairage nocturne",
"night-light-forced": "Éclairage nocturne",
"night-light-not-installed": "Éclairage nocturne (non disponible)",
"noctalia-performance-disabled": "Mode performance Noctalia",
"noctalia-performance-enabled": "Mode performance Noctalia",
"open-control-center": "Centre de contrôle",
"open-notification-history-disable-dnd": "Historique des notifications",
"open-notification-history-enable-dnd": "Historique des notifications",
"open-settings": "Paramètres",
"open-tray-dropdown": "Barre d'état système",
"open-wallpaper-selector": "Sélecteur de fond d'écran",
"output-muted": "Sortie audio",
"pause": "Bouton pause",
"play": "Bouton lecture",
"power-profile": "{profile} profil d'alimentation",
"previous-media": "Piste précédente",
"previous-month": "Mois précédent",
"refresh": "Actualiser",
"refresh-devices": "Actualiser les appareils",
"refresh-wallhaven": "Actualiser les résultats de Wallhaven",
"refresh-wallpaper-list": "Actualiser la liste des fonds d'écran",
"remove-widget": "Supprimer le widget",
"screen-recorder-not-installed": "L'enregistreur d'écran n'est pas installé",
"screen-recorder-not-installed": "Enregistreur d'écran (non installé)",
"search": "Rechercher",
"search-close": "Fermer la recherche",
"session-menu": "Menu de session",
"set-power-profile": "Définir le profil d'alimentation \"{profile}\"",
"start-screen-recording": "Démarrer l'enregistrement d'écran",
"stop-screen-recording": "Arrêter l'enregistrement d'écran",
"switch-to-dark-mode": "Passer en mode sombre",
"switch-to-light-mode": "Passer en mode clair",
"up": "Remonter",
"volume-at": "Volume de sortie à {volume}%.\nClic gauche pour les paramètres. Clic droit pour activer/désactiver le mode muet.\nFaites défiler pour modifier le volume.",
"wallpaper-selector": "Clic gauche : Ouvrir le sélecteur de fond d'écran.\nClic droit : Définir un fond d'écran aléatoire.",
"set-power-profile": "{profile} profil d'alimentation",
"start-screen-recording": "Enregistreur d'écran",
"stop-screen-recording": "Enregistreur d'écran",
"switch-to-dark-mode": "Mode sombre",
"switch-to-light-mode": "Mode clair",
"up": "Répertoire parent",
"volume-at": "Volume de sortie : {volume}%",
"wallpaper-selector": "Sélecteur de fond d'écran",
"widget-settings": "Paramètres du widget"
},
"wallpaper": {

2455
Assets/Translations/ja.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Standaard (weergaveapparaat)",
"description": "Selecteer welk batterijapparaat u wilt weergeven.",
"label": "Batterijapparaat"
},
"display-mode": {
"description": "Kies hoe je wilt dat deze waarde wordt weergegeven.",
"label": "Weergavemodus"
@@ -97,9 +102,13 @@
"control-center": {
"browse-file": "Bestand kiezen",
"browse-library": "Bibliotheek doorbladeren",
"colorize-distro-logo": {
"description": "Pas themakleuren toe op je distributielogo.",
"label": "Distributielogo inkleuren"
"color-selection": {
"description": "Themakleuren toepassen op pictogrammen.",
"label": "Kleur selecteren"
},
"enable-colorization": {
"description": "Schakel kleuring in voor het pictogram van het controlecentrum, waarbij themakleuren worden toegepast.",
"label": "Kleuren inschakelen"
},
"icon": {
"description": "Selecteer een pictogram uit de bibliotheek of een aangepast bestand.",
@@ -227,14 +236,14 @@
"description": "Toon artiest - titel in plaats van titel - artiest.",
"label": "Toon eerst de artiest"
},
"show-visualizer": {
"description": "Toon een audiovisualizer wanneer muziek wordt afgespeeld.",
"label": "Visualizer tonen"
},
"show-progress-ring": {
"description": "Toon een circulaire voortgangsindicator die het bestandsspoor voortgang toont.",
"label": "Voortgangscirkel tonen"
},
"show-visualizer": {
"description": "Toon een audiovisualizer wanneer muziek wordt afgespeeld.",
"label": "Visualizer tonen"
},
"use-fixed-width": {
"description": "Indien ingeschakeld gebruikt de widget altijd de maximale breedte in plaats van zich aan te passen aan de inhoud.",
"label": "Vaste breedte gebruiken"
@@ -271,9 +280,6 @@
}
},
"system-monitor": {
"cpu-critical-threshold": {
"label": "CPU-gebruik (Kritiek)"
},
"cpu-temperature": {
"description": "Toon CPU-temperatuur indien beschikbaar.",
"label": "CPU-temperatuur"
@@ -282,25 +288,10 @@
"description": "Toon het huidige CPU-gebruik in procent.",
"label": "CPU-gebruik"
},
"cpu-warning-threshold": {
"label": "CPU-gebruik (Waarschuwing)"
},
"disk-critical-threshold": {
"label": "Opslagruimte (Kritiek)"
},
"disk-path": {
"description": "Selecteer welk schijfkoppelpunt u wilt monitoren.",
"label": "Schijfpad"
},
"disk-warning-threshold": {
"label": "Opslagruimte (Waarschuwing)"
},
"mem-critical-threshold": {
"label": "Geheugengebruik (Kritiek)"
},
"mem-warning-threshold": {
"label": "Geheugengebruik (Waarschuwing)"
},
"memory-percentage": {
"description": "Toon geheugengebruik als percentage in plaats van absolute waarden.",
"label": "Geheugen als percentage"
@@ -316,16 +307,6 @@
"storage-usage": {
"description": "Toon informatie over schijfruimtegebruik.",
"label": "Opslaggebruik"
},
"temp-critical-threshold": {
"label": "CPU-temperatuur (kritiek)"
},
"temp-warning-threshold": {
"label": "CPU-temperatuur (Waarschuwing)"
},
"thresholds": {
"description": "Drempels instellen voor systeemmetriekwaarschuwingen. Markeert wanneer overschreden.",
"header": "Drempelinstellingen"
}
},
"taskbar": {
@@ -373,14 +354,14 @@
"description": "Aantal tekens dat wordt weergegeven van werkruimtenamen (1-10).",
"label": "Aantal tekens"
},
"hide-unoccupied": {
"description": "Werkruimten zonder vensters niet weergeven.",
"label": "Ongebruikte verbergen"
},
"follow-focused-screen": {
"description": "Werkruimten weergeven van het momenteel gefocuste scherm, in plaats van het scherm waar de balk zich bevindt.",
"label": "Gefocust Scherm Volgen"
},
"hide-unoccupied": {
"description": "Werkruimten zonder vensters niet weergeven.",
"label": "Ongebruikte verbergen"
},
"label-mode": {
"description": "Kies hoe labels van werkruimten worden weergegeven.",
"label": "Labelmodus"
@@ -389,8 +370,8 @@
}
},
"battery": {
"battery-level": "Batterijniveau",
"brightness": "Helderheid",
"charge-level": "Laadniveau",
"charging": "Opladen.",
"charging-rate": "Laadsnelheid: {rate} W.",
"discharging": "Ontladen.",
@@ -412,6 +393,7 @@
"blocked": "Geblokkeerd",
"connect": "Verbinden",
"connected-devices": "Verbonden apparaten",
"connecting": "Verbinden...",
"disabled": "Bluetooth is uitgeschakeld",
"disconnect": "Verbreken",
"enable-message": "Schakel Bluetooth in om beschikbare apparaten te zien.",
@@ -426,6 +408,19 @@
"panel": {
"week": "Week"
},
"timer": {
"countdown": "Aftellen",
"duration": "Duur",
"hours": "h",
"minutes": "m",
"pause": "Pauze",
"reset": "Resetten",
"seconds": "s",
"start": "Begin",
"stopwatch": "Stopwatch",
"timer": "Timer",
"title": "Timer"
},
"weather": {
"loading": "Weer laden…"
}
@@ -518,14 +513,20 @@
"no-notifications": "Geen meldingen",
"title": "Meldingen"
},
"range": {
"all": "Alle",
"earlier": "Eerder",
"today": "Vandaag",
"yesterday": "Gisteren"
},
"time": {
"now": "nu",
"diffM": "1 minuut geleden",
"diffMM": "{diff} minuten geleden",
"diffD": "1 dag geleden",
"diffDD": "{diff} dagen geleden",
"diffH": "1 uur geleden",
"diffHH": "{diff} uur geleden",
"diffD": "1 dag geleden",
"diffDD": "{diff} dagen geleden"
"diffM": "1 minuut geleden",
"diffMM": "{diff} minuten geleden",
"now": "nu"
}
},
"options": {
@@ -545,6 +546,7 @@
},
"colors": {
"error": "Fout",
"none": "Geen",
"onSurface": "Op oppervlak",
"primary": "Primair",
"secondary": "Secundair",
@@ -706,7 +708,7 @@
"enabled": "Bluetooth"
},
"tooltip": {
"action": "Klik om Bluetooth-apparaten te beheren"
"action": "Bluetooth"
}
},
"keepAwake": {
@@ -715,7 +717,7 @@
"enabled": "Wakker houden"
},
"tooltip": {
"action": "Klik om de 'wakker houden'-modus te schakelen"
"action": "Wakker houden"
}
},
"nightLight": {
@@ -725,7 +727,7 @@
"forced": "Nachtlicht"
},
"tooltip": {
"action": "Linker muisknop: door Nachtlicht-modi bladeren\nRechter muisknop: Instellingen openen"
"action": "Nachtlicht"
}
},
"notifications": {
@@ -734,7 +736,7 @@
"enabled": "Meldingen"
},
"tooltip": {
"action": "Linker muisknop: meldingsgeschiedenis openen\nRechter muisknop: 'Niet storen' schakelen"
"action": "Meldingen"
}
},
"powerProfile": {
@@ -742,7 +744,7 @@
"unavailable": "Energieprofiel"
},
"tooltip": {
"action": "Klik om door energieprofielen te bladeren",
"action": "Energieprofiel",
"disabled": "Installeer power-profiles-daemon om energieprofielen te gebruiken"
}
},
@@ -752,13 +754,13 @@
"stopped": "Opnemen"
},
"tooltip": {
"action": "Klik om schermopname te starten/stoppen"
"action": "Schermrecorder"
}
},
"wallpaperSelector": {
"label": "Achtergrond",
"tooltip": {
"action": "Linker muisknop: achtergrondkiezer openen\nRechter muisknop: willekeurige achtergrond instellen"
"action": "Achtergrondkiezer"
}
},
"wifi": {
@@ -768,7 +770,7 @@
"wifi": "Wi-Fi"
},
"tooltip": {
"action": "Klik om Wi-Fi-verbindingen te beheren"
"action": "Wi-Fi"
}
}
},
@@ -794,6 +796,8 @@
},
"noctalia": {
"download-latest": "Nieuwste release downloaden",
"git-commit": "Git commit:",
"git-commit-loading": "Laden...",
"installed-version": "Geïnstalleerde versie:",
"latest-version": "Nieuwste versie:",
"section": {
@@ -1040,15 +1044,27 @@
}
},
"templates": {
"compositors": {
"description": "Theming van compositor.",
"label": "Compositors",
"niri": {
"description": "Schrijf {filepath}. Vereist niri v25.11+",
"description-missing": "Vereist dat {app} is geïnstalleerd."
}
},
"misc": {
"description": "Aanvullende configuratie-opties.",
"label": "Diversen",
"description": "Maak uw eigen sjablonen",
"label": "Geavanceerd",
"user-templates": {
"description": "Schakel door de gebruiker gedefinieerde Matugen-configuratie in. Er wordt een sjabloonbestand aangemaakt op ~/.config/noctalia/user-templates.toml bij de eerste keer inschakelen.",
"label": "Gebruikerssjablonen"
"description": "Alleen inschakelen als u weet wat u doet, raadpleeg onze online documentatie",
"label": "Gebruikerssjablonen inschakelen"
}
},
"programs": {
"cava": {
"description": "Schrijf {filepath}.",
"description-missing": "Vereist dat {app} is geïnstalleerd."
},
"code": {
"description": "Schrijf {filepath}. Het Hyprluna-thema moet handmatig worden geïnstalleerd en geactiveerd.",
"description-missing": "Geen Code-client gedetecteerd. Installeer VSCode of VSCodium."
@@ -1075,10 +1091,6 @@
"description": "Schrijf {filepath}.",
"description-missing": "Vereist dat {app} is geïnstalleerd."
},
"cava": {
"description": "Schrijf {filepath}.",
"description-missing": "Vereist dat {app} is geïnstalleerd."
},
"vicinae": {
"description": "Schrijf {filepath} en herlaad.",
"description-missing": "Vereist dat {app} is geïnstalleerd."
@@ -1262,9 +1274,11 @@
},
"temperature": {
"day": "Dag",
"day-description": "Regelt de kleurtemperatuur overdag.",
"description": "Stel de kleurwarmte voor nacht en dag in.",
"label": "Kleurtemperatuur",
"night": "Nacht"
"night": "Nacht",
"night-description": "Regelt de kleurtemperatuur 's nachts."
}
},
"title": "Beeldscherm"
@@ -1368,7 +1382,8 @@
"description": "Bewerk je gebruikersgegevens en avatar.",
"label": "Profiel"
},
"select-avatar": "Avatarafbeelding selecteren"
"select-avatar": "Avatarafbeelding selecteren",
"tooltip": "Bladeren naar avatarafbeelding"
},
"screen-corners": {
"radius": {
@@ -1446,6 +1461,10 @@
"description": "Gebruik een aangepaste prefix om applicaties te starten in plaats van de standaardmethode.",
"label": "Aangepaste startprefix inschakelen"
},
"grid-view": {
"description": "Items in een raster weergeven in plaats van een lijst.",
"label": "Rasterweergave"
},
"position": {
"description": "Kies waar het launcher-paneel verschijnt.",
"label": "Positie"
@@ -1470,6 +1489,20 @@
"title": "Launcher"
},
"location": {
"calendar": {
"cards": {
"section": {
"description": "Kaarten organiseren en in-/uitschakelen in het kalenderpaneel.",
"label": "Kalenderkaarten"
}
},
"header": {
"label": "Kalenderkop"
},
"month": {
"label": "Kalendermaand"
}
},
"date-time": {
"12hour-format": {
"description": "Toont tijd in 12-uurs formaat op het vergrendelscherm en in de kalender. De klok op de balk heeft zijn eigen instellingen.",
@@ -1671,7 +1704,29 @@
"label": "Algemeen"
}
},
"title": "On-screenweergave"
"title": "On-screenweergave",
"types": {
"brightness": {
"description": "Toon het OSD wanneer de schermhelderheid verandert.",
"label": "Helderheid"
},
"input-volume": {
"description": "Toon het OSD wanneer het microfoonvolume verandert.",
"label": "Invoervolume"
},
"lockkey": {
"description": "Toon het OSD wanneer Caps Lock, Num Lock of Scroll Lock wordt omgeschakeld.",
"label": "Vergrendeltoetsen"
},
"section": {
"description": "Selecteer de gebeurtenissen die de OSD activeren. Als er geen gebeurtenissen zijn geselecteerd, activeren alle beschikbare gebeurtenissen de OSD.",
"label": "OSD triggergebeurtenissen"
},
"volume": {
"description": "Toon het OSD wanneer het uitvoervolume verandert.",
"label": "Uitvoervolume"
}
}
},
"screen-recorder": {
"audio": {
@@ -1806,6 +1861,12 @@
"memory-section": {
"label": "Geheugengebruik"
},
"network-section": {
"label": "Netwerk"
},
"polling-interval": {
"label": "Peilingsinterval"
},
"temp-critical-threshold": {
"label": "Kritieke drempel"
},
@@ -1816,7 +1877,7 @@
"label": "CPU-temperatuur"
},
"thresholds-section": {
"description": "Stel drempelwaarden in voor systeemstatistieken; waarden die hierboven liggen worden gemarkeerd.",
"description": "Pas de waarschuwing/kritieke drempels en de pollingsintervallen voor elke systeemmetriek aan.",
"label": "Drempels"
},
"title": "Systeemmonitor",
@@ -2048,6 +2109,11 @@
"unavailable": "Klembordgeschiedenis niet beschikbaar",
"unavailable-desc": "De toepassing 'cliphist' is niet geïnstalleerd. Installeer deze om functies voor klembordgeschiedenis te gebruiken."
},
"dark-mode": {
"dark-mode": "Donkere modus",
"enabled": "Ingeschakeld",
"light-mode": "Lichte modus"
},
"do-not-disturb": {
"disabled": "\"Niet storen\" uitgeschakeld",
"disabled-desc": "Alle meldingen worden weergegeven.",
@@ -2128,64 +2194,70 @@
"tooltips": {
"add-widget": "Widget toevoegen",
"bluetooth-devices": "Bluetooth-apparaten",
"brightness-at": "Helderheid: {brightness}%\nKlik met rechts voor instellingen.\nScroll om de helderheid aan te passen.",
"cancel-timer": "Timer annuleren",
"brightness-at": "Helderheid: {brightness}%",
"cancel-timer": "Timer",
"clear-history": "Geschiedenis wissen",
"click-to-start-recording": "Klik om met opnemen te beginnen",
"click-to-stop-recording": "Klik om te stoppen met opnemen",
"close": "Sluiten",
"connect-disconnect-devices": "Links klikken om te verbinden. Rechts klikken om te vergeten.",
"click-to-start-recording": "Schermrecorder (opname starten)",
"click-to-stop-recording": "Schermrecorder (opname stoppen)",
"close": "Sluiten-knop",
"connect-disconnect-devices": "Bluetooth-apparaat",
"delete-notification": "Melding verwijderen",
"disable-keep-awake": "\"Wakker houden\" uitschakelen",
"do-not-disturb-disabled": "\"Niet storen\" uitgeschakeld",
"do-not-disturb-enabled": "\"Niet storen\" ingeschakeld",
"enable-keep-awake": "\"Wakker houden\" inschakelen",
"disable-keep-awake": "Wakker houden",
"do-not-disturb-disabled": "Niet storen",
"do-not-disturb-enabled": "Niet storen",
"enable-keep-awake": "Wakker houden",
"forget-network": "Netwerk vergeten",
"home": "Home",
"input-muted": "Invoermicrofoon dempen in-/uitschakelen",
"grid-view": "Rasterweergave",
"hidden-files-hide": "Verborgen bestanden",
"hidden-files-show": "Verborgen bestanden",
"home": "Home-map",
"input-muted": "Microfoon",
"keep-awake": "Wakker houden",
"keyboard-layout": "{layout}-toetsenbordindeling",
"manage-vpn": "VPN-verbindingen beheren",
"manage-wifi": "Wi-Fi beheren",
"microphone-volume-at": "Microfoonvolume {volume}%\nLinks klikken voor instellingen. Rechts klikken om te dempen.\nScroll om het volume aan te passen.",
"move-to-center-section": "Verplaatsen naar middelste sectie",
"move-to-left-section": "Verplaatsen naar linker sectie",
"move-to-right-section": "Verplaatsen naar rechter sectie",
"next-media": "Volgend media-item",
"list-view": "Lijstweergave",
"manage-vpn": "VPN-verbindingen",
"manage-wifi": "Wi-Fi",
"microphone-volume-at": "Microfoonvolume: {volume}%",
"move-to-center-section": "Middelste sectie",
"move-to-left-section": "Linker sectie",
"move-to-right-section": "Rechter sectie",
"next-media": "Volgende track",
"next-month": "Volgende maand",
"night-light-disabled": "Nachtlicht is uitgeschakeld.\nLinks klikken om de modus te doorlopen.\nRechts klikken voor instellingen.",
"night-light-enabled": "Nachtlicht is ingeschakeld.\nLinks klikken om de modus te doorlopen.\nRechts klikken voor instellingen.",
"night-light-forced": "Nachtlicht is geforceerd.\nLinks klikken om de modus te doorlopen.\nRechts klikken voor instellingen.",
"night-light-not-installed": "Nachtlicht is niet beschikbaar.\nwlsunset is niet geïnstalleerd.",
"noctalia-performance-disabled": "Noctalia-prestatiemodus is uitgeschakeld.\nLinks klikken om in te schakelen.",
"noctalia-performance-enabled": "Noctalia-prestatiemodus is ingeschakeld.\nLinks klikken om uit te schakelen.",
"open-control-center": "Bedieningscentrum openen",
"open-notification-history-disable-dnd": "Meldingsgeschiedenis openen\nKlik met rechts om \"Niet storen\" uit te schakelen.",
"open-notification-history-enable-dnd": "Meldingsgeschiedenis openen\nKlik met rechts om \"Niet storen\" in te schakelen.",
"open-settings": "Instellingen openen",
"open-tray-dropdown": "Systeemvakmenu openen",
"open-wallpaper-selector": "Achtergrondkiezer openen",
"output-muted": "Uitvoervolume dempen in-/uitschakelen",
"pause": "Pauzeren",
"play": "Afspelen",
"power-profile": "'{profile}'-energieprofiel",
"previous-media": "Vorig media-item",
"night-light-disabled": "Nachtlicht",
"night-light-enabled": "Nachtlicht",
"night-light-forced": "Nachtlicht",
"night-light-not-installed": "Nachtlicht (niet beschikbaar)",
"noctalia-performance-disabled": "Noctalia-prestatiemodus",
"noctalia-performance-enabled": "Noctalia-prestatiemodus",
"open-control-center": "Bedieningscentrum",
"open-notification-history-disable-dnd": "Meldingsgeschiedenis",
"open-notification-history-enable-dnd": "Meldingsgeschiedenis",
"open-settings": "Instellingen",
"open-tray-dropdown": "Systeemvak",
"open-wallpaper-selector": "Achtergrondkiezer",
"output-muted": "Audio-uitvoer",
"pause": "Pauze-knop",
"play": "Afspelen-knop",
"power-profile": "{profile} energieprofiel",
"previous-media": "Vorige track",
"previous-month": "Vorige maand",
"refresh": "Verversen",
"refresh-devices": "Apparaten verversen",
"refresh-wallhaven": "Wallhaven-resultaten verversen",
"refresh-wallpaper-list": "Achtergrondlijst verversen",
"remove-widget": "Widget verwijderen",
"screen-recorder-not-installed": "Schermrecorder is niet geïnstalleerd",
"screen-recorder-not-installed": "Schermrecorder (niet geïnstalleerd)",
"search": "Zoeken",
"search-close": "Zoeken sluiten",
"session-menu": "Sessiemenu",
"set-power-profile": "\"{profile}\"-energieprofiel instellen",
"start-screen-recording": "Schermopname starten",
"stop-screen-recording": "Schermopname stoppen",
"switch-to-dark-mode": "Overschakelen naar donkere modus",
"switch-to-light-mode": "Overschakelen naar lichte modus",
"up": "Omhoog",
"volume-at": "Uitvoervolume {volume}%\nLinks klikken voor instellingen. Rechts klikken om te dempen.\nScroll om het volume aan te passen.",
"wallpaper-selector": "Linker muisknop: achtergrondkiezer openen.\nRechter muisknop: willekeurige achtergrond instellen.",
"set-power-profile": "{profile} energieprofiel",
"start-screen-recording": "Schermrecorder",
"stop-screen-recording": "Schermrecorder",
"switch-to-dark-mode": "Donkere modus",
"switch-to-light-mode": "Lichte modus",
"up": "Bovenliggende map",
"volume-at": "Uitvoervolume: {volume}%",
"wallpaper-selector": "Achtergrondkiezer",
"widget-settings": "Widget-instellingen"
},
"wallpaper": {

View File

@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Padrão (Dispositivo de Exibição)",
"description": "Selecione qual dispositivo de bateria exibir.",
"label": "Dispositivo de bateria"
},
"display-mode": {
"description": "Escolha como você gostaria que este valor aparecesse.",
"label": "Modo de exibição"
@@ -97,9 +102,13 @@
"control-center": {
"browse-file": "Navegar por Arquivo",
"browse-library": "Navegar na Biblioteca",
"colorize-distro-logo": {
"description": "Aplicar as cores do tema ao logotipo da sua distribuição.",
"label": "Colorir logo da distribuição"
"color-selection": {
"description": "Aplica as cores do tema aos ícones.",
"label": "Selecionar cor"
},
"enable-colorization": {
"description": "Ativa a colorização para o ícone do centro de controlo, aplicando as cores do tema.",
"label": "Ativar colorização"
},
"icon": {
"description": "Selecione um ícone da biblioteca ou um arquivo personalizado.",
@@ -227,14 +236,14 @@
"description": "Exibir artista - título em vez de título - artista.",
"label": "Mostrar o artista primeiro"
},
"show-visualizer": {
"description": "Exibir um visualizador de áudio quando música está sendo reproduzida.",
"label": "Mostrar visualizador"
},
"show-progress-ring": {
"description": "Exibir um indicador de progresso circular mostrando o progresso da faixa.",
"label": "Mostrar anel de progresso"
},
"show-visualizer": {
"description": "Exibir um visualizador de áudio quando música está sendo reproduzida.",
"label": "Mostrar visualizador"
},
"use-fixed-width": {
"description": "Quando ativado, o widget sempre usará a largura máxima em vez de ajustar dinamicamente ao conteúdo.",
"label": "Usar Largura Fixa"
@@ -271,9 +280,6 @@
}
},
"system-monitor": {
"cpu-critical-threshold": {
"label": "CPU Usage (Critical)"
},
"cpu-temperature": {
"description": "Mostrar leituras de temperatura da CPU se disponíveis.",
"label": "Temperatura da CPU"
@@ -282,25 +288,10 @@
"description": "Exibir o percentual atual de uso da CPU.",
"label": "Uso da CPU"
},
"cpu-warning-threshold": {
"label": "CPU Usage (Warning)"
},
"disk-critical-threshold": {
"label": "Storage Space (Critical)"
},
"disk-path": {
"description": "Selecione qual ponto de montagem de disco monitorar.",
"label": "Caminho do disco"
},
"disk-warning-threshold": {
"label": "Storage Space (Warning)"
},
"mem-critical-threshold": {
"label": "Memory Usage (Critical)"
},
"mem-warning-threshold": {
"label": "Memory Usage (Warning)"
},
"memory-percentage": {
"description": "Mostrar o uso de memória como percentual em vez de valores absolutos.",
"label": "Memória como percentual"
@@ -316,16 +307,6 @@
"storage-usage": {
"description": "Mostrar informações de uso do espaço em disco.",
"label": "Uso de armazenamento"
},
"temp-critical-threshold": {
"label": "CPU Temperature (Critical)"
},
"temp-warning-threshold": {
"label": "CPU Temperature (Warning)"
},
"thresholds": {
"description": "Estabelece limites para alertas de métricas do sistema. Destaca quando superados.",
"header": "Configuração de Limiares"
}
},
"taskbar": {
@@ -373,14 +354,14 @@
"description": "Número de caracteres a exibir dos nomes de espaços de trabalho (1-10).",
"label": "Número de caracteres"
},
"hide-unoccupied": {
"description": "Não exibir áreas de trabalho sem janelas.",
"label": "Ocultar desocupados"
},
"follow-focused-screen": {
"description": "Exibir áreas de trabalho da tela atualmente em foco, em vez da tela onde a barra está localizada.",
"label": "Seguir Tela em Foco"
},
"hide-unoccupied": {
"description": "Não exibir áreas de trabalho sem janelas.",
"label": "Ocultar desocupados"
},
"label-mode": {
"description": "Escolher como os rótulos de espaço de trabalho são exibidos.",
"label": "Modo de rótulo"
@@ -389,8 +370,8 @@
}
},
"battery": {
"battery-level": "Nível da bateria",
"brightness": "Brilho",
"charge-level": "Nível de carga",
"charging": "Carregando.",
"charging-rate": "Taxa de carregamento: {rate} W.",
"discharging": "Descarregando.",
@@ -412,6 +393,7 @@
"blocked": "Bloqueado",
"connect": "Conectar",
"connected-devices": "Dispositivos conectados",
"connecting": "Conectando...",
"disabled": "O Bluetooth está desativado",
"disconnect": "Desconectar",
"enable-message": "Ative o Bluetooth para ver os dispositivos disponíveis.",
@@ -426,6 +408,19 @@
"panel": {
"week": "Semana"
},
"timer": {
"countdown": "Contagem regressiva",
"duration": "Duração",
"hours": "h",
"minutes": "m",
"pause": "Pausar",
"reset": "Reiniciar",
"seconds": "s",
"start": "Começar",
"stopwatch": "Cronômetro",
"timer": "Temporizador",
"title": "Temporizador"
},
"weather": {
"loading": "Carregando o clima…"
}
@@ -518,14 +513,20 @@
"no-notifications": "Nenhuma notificação",
"title": "Notificações"
},
"range": {
"all": "Todas",
"earlier": "Mais antigas",
"today": "Hoje",
"yesterday": "Ontem"
},
"time": {
"now": "agora",
"diffM": "há 1 minuto",
"diffMM": "há {diff} minutos",
"diffD": "há 1 dia",
"diffDD": "há {diff} dias",
"diffH": "há 1 hora",
"diffHH": "há {diff} horas",
"diffD": "há 1 dia",
"diffDD": "há {diff} dias"
"diffM": "há 1 minuto",
"diffMM": "há {diff} minutos",
"now": "agora"
}
},
"options": {
@@ -545,6 +546,7 @@
},
"colors": {
"error": "Erro",
"none": "Nenhum",
"onSurface": "Na Superfície",
"primary": "Primário",
"secondary": "Secundário",
@@ -706,7 +708,7 @@
"enabled": "Bluetooth"
},
"tooltip": {
"action": "Clique para gerenciar dispositivos Bluetooth"
"action": "Bluetooth"
}
},
"keepAwake": {
@@ -715,7 +717,7 @@
"enabled": "Manter acordado"
},
"tooltip": {
"action": "Clique para alternar o modo Manter acordado"
"action": "Manter acordado"
}
},
"nightLight": {
@@ -725,7 +727,7 @@
"forced": "Luz noturna"
},
"tooltip": {
"action": "Clique para alternar o modo Luz noturna\nClique direito: Abrir configurações"
"action": "Luz noturna"
}
},
"notifications": {
@@ -734,7 +736,7 @@
"enabled": "Notificações"
},
"tooltip": {
"action": "Clique esquerdo: Abrir histórico de notificações\nClique direito: Alternar Não perturbar"
"action": "Notificações"
}
},
"powerProfile": {
@@ -742,7 +744,7 @@
"unavailable": "Perfil de energia"
},
"tooltip": {
"action": "Clique para alternar o perfil de energia",
"action": "Perfil de energia",
"disabled": "Instale power-profiles-daemon para usar perfis de energia"
}
},
@@ -752,13 +754,13 @@
"stopped": "Gravar"
},
"tooltip": {
"action": "Clique para iniciar/parar a gravação da tela"
"action": "Gravador de tela"
}
},
"wallpaperSelector": {
"label": "Papel de parede",
"tooltip": {
"action": "Clique esquerdo: Abrir seletor de papel de parede\nClique direito: Definir papel de parede aleatório"
"action": "Seletor de papel de parede"
}
},
"wifi": {
@@ -768,7 +770,7 @@
"wifi": "Wi-Fi"
},
"tooltip": {
"action": "Clique para gerenciar conexões Wi-Fi"
"action": "Wi-Fi"
}
}
},
@@ -794,6 +796,8 @@
},
"noctalia": {
"download-latest": "Baixar a versão mais recente",
"git-commit": "Commit Git:",
"git-commit-loading": "Carregando...",
"installed-version": "Versão instalada:",
"latest-version": "Última versão:",
"section": {
@@ -1040,15 +1044,27 @@
}
},
"templates": {
"compositors": {
"description": "Tematização do compositor.",
"label": "Compositores",
"niri": {
"description": "Escrever {filepath}. Requer Niri v25.11+",
"description-missing": "Requer que o {app} esteja instalado."
}
},
"misc": {
"description": "Opções de configuração adicionais.",
"label": "Diversos",
"description": "Crie seus próprios modelos",
"label": "Avançado",
"user-templates": {
"description": "Ativa a configuração do Matugen definida pelo usuário. Um arquivo de modelo será criado em ~/.config/noctalia/user-templates.toml na primeira ativação",
"label": "Modelos do usuário"
"description": "Ative apenas se souber o que está fazendo, consulte nossa documentação online",
"label": "Ativar modelos do usuário"
}
},
"programs": {
"cava": {
"description": "Escreva em {filepath}.",
"description-missing": "Requer que o {app} esteja instalado."
},
"code": {
"description": "Escreva em {filepath}. O tema Hyprluna precisa ser instalado e ativado manualmente.",
"description-missing": "Nenhum cliente Code detectado. Instale VSCode ou VSCodium."
@@ -1075,10 +1091,6 @@
"description": "Escreva em {filepath}.",
"description-missing": "Requer que o {app} esteja instalado."
},
"cava": {
"description": "Escreva em {filepath}.",
"description-missing": "Requer que o {app} esteja instalado."
},
"vicinae": {
"description": "Escrever {filepath} e recarregar",
"description-missing": "Requer que o {app} esteja instalado"
@@ -1262,9 +1274,11 @@
},
"temperature": {
"day": "Dia",
"day-description": "Controla a temperatura durante o dia.",
"description": "Defina o quão quente a cor será durante a noite e o dia.",
"label": "Temperatura da cor",
"night": "Noite"
"night": "Noite",
"night-description": "Controla a temperatura durante a noite."
}
},
"title": "Tela"
@@ -1368,7 +1382,8 @@
"description": "Edite os detalhes do seu usuário e avatar.",
"label": "Perfil"
},
"select-avatar": "Selecionar imagem de avatar"
"select-avatar": "Selecionar imagem de avatar",
"tooltip": "Procurar imagem de avatar"
},
"screen-corners": {
"radius": {
@@ -1446,6 +1461,10 @@
"description": "Usar um prefixo personalizado para inicializar aplicativos em vez do método padrão.",
"label": "Habilitar prefixo de inicialização personalizado"
},
"grid-view": {
"description": "Exibir itens em uma grade em vez de uma lista.",
"label": "Visualização em grade"
},
"position": {
"description": "Escolha onde o painel do lançador aparece.",
"label": "Posição"
@@ -1470,6 +1489,20 @@
"title": "Lançador"
},
"location": {
"calendar": {
"cards": {
"section": {
"description": "Organize e ative/desative cartões no painel do calendário.",
"label": "Cartões de calendário"
}
},
"header": {
"label": "Cabeçalho do Calendário"
},
"month": {
"label": "Mês do calendário"
}
},
"date-time": {
"12hour-format": {
"description": "Exibe a hora no formato de 12 horas na tela de bloqueio e no calendário. O relógio da barra tem suas próprias configurações.",
@@ -1671,7 +1704,29 @@
"label": "Geral"
}
},
"title": "Exibição na tela"
"title": "Exibição na tela",
"types": {
"brightness": {
"description": "Mostrar o OSD quando o brilho da tela mudar.",
"label": "Brilho"
},
"input-volume": {
"description": "Mostrar o OSD quando o volume do microfone mudar.",
"label": "Volume de entrada"
},
"lockkey": {
"description": "Mostrar o OSD quando Caps Lock, Num Lock ou Scroll Lock forem alternadas.",
"label": "Teclas de bloqueio"
},
"section": {
"description": "Selecione os eventos que acionam o OSD. Se nenhum evento for selecionado, todos os eventos disponíveis acionarão o OSD.",
"label": "Eventos de disparo OSD"
},
"volume": {
"description": "Mostrar o OSD quando o volume de saída de áudio mudar.",
"label": "Volume de saída"
}
}
},
"screen-recorder": {
"audio": {
@@ -1806,6 +1861,12 @@
"memory-section": {
"label": "Uso de memória"
},
"network-section": {
"label": "Rede"
},
"polling-interval": {
"label": "Intervalo de pesquisa"
},
"temp-critical-threshold": {
"label": "Limite crítico"
},
@@ -1816,7 +1877,7 @@
"label": "Temperatura da CPU"
},
"thresholds-section": {
"description": "Defina limiares para métricas do sistema; valores acima serão destacados.",
"description": "Ajuste os limites de aviso/crítico e os intervalos de consulta para cada métrica do sistema.",
"label": "Limiares"
},
"title": "Monitor do Sistema",
@@ -2048,6 +2109,11 @@
"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."
},
"dark-mode": {
"dark-mode": "Modo escuro",
"enabled": "Ativado",
"light-mode": "Modo claro"
},
"do-not-disturb": {
"disabled": "'Não perturbe' desativado",
"disabled-desc": "Mostrando todas as notificações.",
@@ -2128,64 +2194,70 @@
"tooltips": {
"add-widget": "Adicionar widget",
"bluetooth-devices": "Dispositivos Bluetooth",
"brightness-at": "Brilho: {brightness}%\nClique direito para configurações.\nRole para modificar o brilho.",
"cancel-timer": "Cancelar temporizador",
"brightness-at": "Brilho: {brightness}%",
"cancel-timer": "Temporizador",
"clear-history": "Limpar histórico",
"click-to-start-recording": "Clique para iniciar a gravação",
"click-to-stop-recording": "Clique para parar a gravação",
"close": "Fechar",
"connect-disconnect-devices": "Clique esquerdo para conectar. Clique direito para esquecer.",
"click-to-start-recording": "Gravador de tela (iniciar gravação)",
"click-to-stop-recording": "Gravador de tela (parar gravação)",
"close": "Botão fechar",
"connect-disconnect-devices": "Dispositivo Bluetooth",
"delete-notification": "Excluir notificação",
"disable-keep-awake": "Clique para desativar 'manter acordado'.\nRole para ajustar o tempo limite.",
"do-not-disturb-disabled": "'Não perturbe' desativado",
"do-not-disturb-enabled": "'Não perturbe' ativado",
"enable-keep-awake": "Clique para ativar 'manter acordado'.\nRole para ajustar o tempo limite.",
"disable-keep-awake": "Manter acordado",
"do-not-disturb-disabled": "Não perturbe",
"do-not-disturb-enabled": "Não perturbe",
"enable-keep-awake": "Manter acordado",
"forget-network": "Esquecer rede",
"home": "Início",
"input-muted": "Silenciar entrada de áudio",
"grid-view": "Visualização em grade",
"hidden-files-hide": "Arquivos ocultos",
"hidden-files-show": "Arquivos ocultos",
"home": "Diretório home",
"input-muted": "Microfone",
"keep-awake": "Manter acordado",
"keyboard-layout": "Layout de teclado {layout}",
"manage-vpn": "Gerenciar conexões VPN",
"manage-wifi": "Gerenciar Wi-Fi",
"microphone-volume-at": "Volume do microfone em {volume}%.\nClique esquerdo para configurações. Clique direito para ativar/desativar o mudo.\nRole para modificar o volume.",
"move-to-center-section": "Mover para a seção central",
"move-to-left-section": "Mover para a seção esquerda",
"move-to-right-section": "Mover para a seção direita",
"next-media": "Próxima mídia",
"list-view": "Visualização em lista",
"manage-vpn": "Conexões VPN",
"manage-wifi": "Wi-Fi",
"microphone-volume-at": "Volume do microfone: {volume}%",
"move-to-center-section": "Seção central",
"move-to-left-section": "Seção esquerda",
"move-to-right-section": "Seção direita",
"next-media": "Próxima faixa",
"next-month": "Próximo mês",
"night-light-disabled": "Luz noturna desativada.\nClique esquerdo para alternar o modo.\nClique direito para acessar as configurações.",
"night-light-enabled": "Luz noturna ativada.\nClique esquerdo para alternar o modo.\nClique direito para acessar as configurações.",
"night-light-forced": "Luz noturna forçada.\nClique esquerdo para alternar o modo.\nClique direito para acessar as configurações.",
"night-light-not-installed": "Luz noturna não disponível.\nwlsunset não está instalado.",
"noctalia-performance-disabled": "O modo de desempenho Noctalia está desativado.\nClique esquerdo para ativar.",
"noctalia-performance-enabled": "O modo de desempenho Noctalia está ativado.\nClique esquerdo para desativar.",
"open-control-center": "Abrir central de controle",
"open-notification-history-disable-dnd": "Abrir histórico de notificações\nClique direito para desativar \"Não perturbe\".",
"open-notification-history-enable-dnd": "Abrir histórico de notificações\nClique direito para ativar \"Não perturbe\".",
"open-settings": "Abrir configurações",
"open-tray-dropdown": "Abrir menu suspenso da bandeja",
"open-wallpaper-selector": "Abrir seletor de papel de parede",
"output-muted": "Silenciar saída de áudio",
"pause": "Pausar",
"play": "Reproduzir",
"power-profile": "Perfil de energia '{profile}'",
"previous-media": "Mídia anterior",
"night-light-disabled": "Luz noturna",
"night-light-enabled": "Luz noturna",
"night-light-forced": "Luz noturna",
"night-light-not-installed": "Luz noturna (não disponível)",
"noctalia-performance-disabled": "Modo de desempenho Noctalia",
"noctalia-performance-enabled": "Modo de desempenho Noctalia",
"open-control-center": "Central de controle",
"open-notification-history-disable-dnd": "Histórico de notificações",
"open-notification-history-enable-dnd": "Histórico de notificações",
"open-settings": "Configurações",
"open-tray-dropdown": "Bandeja do sistema",
"open-wallpaper-selector": "Seletor de papel de parede",
"output-muted": "Saída de áudio",
"pause": "Botão pausar",
"play": "Botão reproduzir",
"power-profile": "{profile} perfil de energia",
"previous-media": "Faixa anterior",
"previous-month": "Mês anterior",
"refresh": "Atualizar",
"refresh-devices": "Atualizar dispositivos",
"refresh-wallhaven": "Atualizar resultados do Wallhaven",
"refresh-wallpaper-list": "Atualizar lista de papéis de parede",
"remove-widget": "Remover widget",
"screen-recorder-not-installed": "O gravador de tela não está instalado",
"screen-recorder-not-installed": "Gravador de tela (não instalado)",
"search": "Pesquisar",
"search-close": "Fechar pesquisa",
"session-menu": "Menu da Sessão",
"set-power-profile": "Definir perfil de energia \"{profile}\"",
"start-screen-recording": "Iniciar gravação de tela",
"stop-screen-recording": "Parar gravação de tela",
"switch-to-dark-mode": "Mudar para o modo escuro",
"switch-to-light-mode": "Mudar para o modo claro",
"up": "Acima",
"volume-at": "Volume de saída em {volume}%.\nClique esquerdo para configurações. Clique direito para alternar o mudo.\nRole para modificar o volume.",
"wallpaper-selector": "Clique esquerdo: Abrir seletor de papel de parede.\nClique direito: Definir papel de parede aleatório.",
"set-power-profile": "{profile} perfil de energia",
"start-screen-recording": "Gravador de tela",
"stop-screen-recording": "Gravador de tela",
"switch-to-dark-mode": "Modo escuro",
"switch-to-light-mode": "Modo claro",
"up": "Diretório superior",
"volume-at": "Volume de saída: {volume}%",
"wallpaper-selector": "Seletor de papel de parede",
"widget-settings": "Configurações do widget"
},
"wallpaper": {

View File

@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Устройство отображения по умолчанию",
"description": "Выберите, какое устройство с батареей отображать.",
"label": "Батарейное устройство"
},
"display-mode": {
"description": "Выберите, как это значение должно отображаться.",
"label": "Режим отображения"
@@ -97,9 +102,13 @@
"control-center": {
"browse-file": "Обзор файла",
"browse-library": "Обзор библиотеки",
"colorize-distro-logo": {
"description": "Применить цвета темы к логотипу вашего дистрибутива.",
"label": "Раскрасить логотип дистрибутива"
"color-selection": {
"description": "Применяет цвета темы к значкам.",
"label": "Выбор цвета"
},
"enable-colorization": {
"description": "Включает окрашивание для значка центра управления, применяя цвета темы.",
"label": "Включить окрашивание"
},
"icon": {
"description": "Выберите иконку из библиотеки или пользовательский файл.",
@@ -227,14 +236,14 @@
"description": "Исполнитель - название вместо название - исполнитель.",
"label": "Сначала исполнитель"
},
"show-visualizer": {
"description": "Отображать аудиовизуализатор при воспроизведении музыки.",
"label": "Показывать визуализатор"
},
"show-progress-ring": {
"description": "Отображать круговой индикатор прогресса воспроизведения трека.",
"label": "Показывать кольцо прогресса"
},
"show-visualizer": {
"description": "Отображать аудиовизуализатор при воспроизведении музыки.",
"label": "Показывать визуализатор"
},
"use-fixed-width": {
"description": "Если включено, виджет всегда будет использовать максимальную ширину вместо динамической подстройки под содержимое.",
"label": "Использовать фиксированную ширину"
@@ -271,9 +280,6 @@
}
},
"system-monitor": {
"cpu-critical-threshold": {
"label": "Использование CPU (Критично)"
},
"cpu-temperature": {
"description": "Показывать показания температуры процессора, если доступны.",
"label": "Температура CPU"
@@ -282,25 +288,10 @@
"description": "Отображать текущий процент использования CPU.",
"label": "Использование CPU"
},
"cpu-warning-threshold": {
"label": "Использование CPU (Предупреждение)"
},
"disk-critical-threshold": {
"label": "Место на диске (Критично)"
},
"disk-path": {
"description": "Выберите точку монтирования диска для мониторинга.",
"label": "Путь к диску"
},
"disk-warning-threshold": {
"label": "Место на диске (Предупреждение)"
},
"mem-critical-threshold": {
"label": "Использование памяти (Критично)"
},
"mem-warning-threshold": {
"label": "Использование памяти (Предупреждение)"
},
"memory-percentage": {
"description": "Показывать использование памяти в процентах вместо абсолютных значений.",
"label": "Память в процентах"
@@ -316,16 +307,6 @@
"storage-usage": {
"description": "Показывать информацию об использовании дискового пространства.",
"label": "Использование хранилища"
},
"temp-critical-threshold": {
"label": "Температура CPU: (Критично)"
},
"temp-warning-threshold": {
"label": "Температура CPU (Внимание)"
},
"thresholds": {
"description": "Установите пороги для оповещений системных метрик. Выделяет при превышении.",
"header": "Пороговые значения"
}
},
"taskbar": {
@@ -373,14 +354,14 @@
"description": "Количество символов для отображения из имен рабочих пространств (1-10).",
"label": "Количество символов"
},
"hide-unoccupied": {
"description": "Не отображать рабочие пространства без окон.",
"label": "Скрыть незанятые"
},
"follow-focused-screen": {
"description": "Отображать рабочие пространства с текущего активного экрана, а не с экрана, на котором расположена панель.",
"label": "Следовать за Активным Экраном"
},
"hide-unoccupied": {
"description": "Не отображать рабочие пространства без окон.",
"label": "Скрыть незанятые"
},
"label-mode": {
"description": "Выберите, как отображаются метки рабочих пространств.",
"label": "Режим метки"
@@ -389,8 +370,8 @@
}
},
"battery": {
"battery-level": "Уровень заряда батареи",
"brightness": "Яркость",
"charge-level": "Уровень заряда",
"charging": "Зарядка.",
"charging-rate": "Скорость зарядки: {rate} Вт.",
"discharging": "Разрядка.",
@@ -412,6 +393,7 @@
"blocked": "Заблокировано",
"connect": "Подключить",
"connected-devices": "Подключенные устройства",
"connecting": "Подключение…",
"disabled": "Bluetooth отключен",
"disconnect": "Отключить",
"enable-message": "Включите Bluetooth, чтобы увидеть доступные устройства.",
@@ -426,6 +408,19 @@
"panel": {
"week": "Неделя"
},
"timer": {
"countdown": "Обратный отсчёт",
"duration": "Продолжительность",
"hours": "ч",
"minutes": "м",
"pause": "Пауза",
"reset": "Сброс",
"seconds": "с",
"start": "Начать",
"stopwatch": "Секундомер",
"timer": "Таймер",
"title": "Таймер"
},
"weather": {
"loading": "Загрузка погоды…"
}
@@ -518,14 +513,20 @@
"no-notifications": "Нет уведомлений",
"title": "Уведомления"
},
"range": {
"all": "Все",
"earlier": "Ранее",
"today": "Сегодня",
"yesterday": "Вчера"
},
"time": {
"now": "сейчас",
"diffM": "1 минуту назад",
"diffMM": "{diff} минут назад",
"diffD": "1 день назад",
"diffDD": "{diff} дней назад",
"diffH": "1 час назад",
"diffHH": "{diff} часов назад",
"diffD": "1 день назад",
"diffDD": "{diff} дней назад"
"diffM": "1 минуту назад",
"diffMM": "{diff} минут назад",
"now": "сейчас"
}
},
"options": {
@@ -545,6 +546,7 @@
},
"colors": {
"error": "Ошибка",
"none": "Ничего",
"onSurface": "На поверхности",
"primary": "Основной",
"secondary": "Второстепенный",
@@ -706,7 +708,7 @@
"enabled": "Bluetooth"
},
"tooltip": {
"action": "Нажмите, чтобы управлять устройствами Bluetooth"
"action": "Bluetooth"
}
},
"keepAwake": {
@@ -715,7 +717,7 @@
"enabled": "Не засыпать"
},
"tooltip": {
"action": "Нажмите, чтобы переключить режим 'Не засыпать'"
"action": "Не засыпать"
}
},
"nightLight": {
@@ -725,7 +727,7 @@
"forced": "Ночной свет"
},
"tooltip": {
"action": "Нажмите, чтобы переключить режим Ночного света\nПравая кнопка мыши: Открыть настройки"
"action": "Ночной свет"
}
},
"notifications": {
@@ -734,7 +736,7 @@
"enabled": "Уведомления"
},
"tooltip": {
"action": "Левая кнопка мыши: Открыть историю уведомлений\nПравая кнопка мыши: Переключить 'Не беспокоить'"
"action": "Уведомления"
}
},
"powerProfile": {
@@ -742,7 +744,7 @@
"unavailable": "Профиль питания"
},
"tooltip": {
"action": "Нажмите, чтобы переключить профиль питания",
"action": "Профиль питания",
"disabled": "Установите power-profiles-daemon, чтобы использовать профили питания"
}
},
@@ -752,13 +754,13 @@
"stopped": "Записать"
},
"tooltip": {
"action": "Нажмите, чтобы начать/остановить запись экрана"
"action": "Запись экрана"
}
},
"wallpaperSelector": {
"label": "Обои",
"tooltip": {
"action": "Левая кнопка мыши: Открыть выбор обоев\nПравая кнопка мыши: Установить случайные обои"
"action": "Выбор обоев"
}
},
"wifi": {
@@ -768,7 +770,7 @@
"wifi": "Wi-Fi"
},
"tooltip": {
"action": "Нажмите, чтобы управлять Wi-Fi подключениями"
"action": "Wi-Fi"
}
}
},
@@ -794,6 +796,8 @@
},
"noctalia": {
"download-latest": "Скачать последний выпуск",
"git-commit": "Git коммит:",
"git-commit-loading": "Загрузка...",
"installed-version": "Установленная версия:",
"latest-version": "Последняя версия:",
"section": {
@@ -1040,15 +1044,27 @@
}
},
"templates": {
"compositors": {
"description": "Оформление композитора.",
"label": "Компоновщики",
"niri": {
"description": "Записать {filepath}. Требуется niri v25.11+",
"description-missing": "Требуется установка {app}"
}
},
"misc": {
"description": "Дополнительные параметры конфигурации.",
"label": "Разное",
"description": "Создайте свои собственные шаблоны",
"label": "Дополнительно",
"user-templates": {
"description": "Включить пользовательскую конфигурацию Matugen. Файл шаблона будет создан в ~/.config/noctalia/user-templates.toml при первом включении.",
"label": "Пользовательские шаблоны"
"description": "Включайте только если вы знаете, что делаете, обратитесь к нашей онлайн-документации",
"label": "Включить пользовательские шаблоны"
}
},
"programs": {
"cava": {
"description": "Записать {filepath}.",
"description-missing": "Требуется установка {app}"
},
"code": {
"description": "Записать {filepath}. Тему Hyprluna нужно установить и активировать вручную.",
"description-missing": "Клиент Code не обнаружен. Установите VSCode или VSCodium."
@@ -1075,10 +1091,6 @@
"description": "Записать {filepath}.",
"description-missing": "Требуется установка {app}"
},
"cava": {
"description": "Записать {filepath}.",
"description-missing": "Требуется установка {app}"
},
"vicinae": {
"description": "Записать {filepath} и перезагрузить",
"description-missing": "Требуется установка {app}"
@@ -1262,9 +1274,11 @@
},
"temperature": {
"day": "День",
"day-description": "Управляет цветовой температурой в дневное время.",
"description": "Установите цветовую температуру для ночного и дневного времени.",
"label": "Цветовая температура",
"night": "Ночь"
"night": "Ночь",
"night-description": "Управляет цветовой температурой в ночное время."
}
},
"title": "Дисплей"
@@ -1368,7 +1382,8 @@
"description": "Редактируйте данные пользователя и аватар.",
"label": "Профиль"
},
"select-avatar": "Выбрать изображение аватара"
"select-avatar": "Выбрать изображение аватара",
"tooltip": "Выбрать изображение аватара"
},
"screen-corners": {
"radius": {
@@ -1446,6 +1461,10 @@
"description": "Использовать пользовательский префикс для запуска приложений вместо метода по умолчанию.",
"label": "Включить пользовательский префикс запуска"
},
"grid-view": {
"description": "Показывать элементы в виде сетки вместо списка.",
"label": "Вид сетки"
},
"position": {
"description": "Выберите, где появляется панель запуска.",
"label": "Положение"
@@ -1470,6 +1489,20 @@
"title": "Запуск"
},
"location": {
"calendar": {
"cards": {
"section": {
"description": "Организуйте и включайте/выключайте карточки в панели календаря.",
"label": "Карточки календаря"
}
},
"header": {
"label": "Заголовок календаря"
},
"month": {
"label": "Календарный месяц"
}
},
"date-time": {
"12hour-format": {
"description": "Отображает время в 12-часовом формате на экране блокировки и в календаре. Часы на панели имеют собственные настройки.",
@@ -1671,7 +1704,29 @@
"label": "Общие"
}
},
"title": "Экранное отображение (OSD)"
"title": "Экранное отображение (OSD)",
"types": {
"brightness": {
"description": "Показывать OSD при изменении яркости экрана.",
"label": "Яркость"
},
"input-volume": {
"description": "Показывать OSD при изменении громкости микрофона.",
"label": "Входная громкость"
},
"lockkey": {
"description": "Показывать OSD при переключении клавиш Caps Lock, Num Lock или Scroll Lock.",
"label": "Клавиши блокировки"
},
"section": {
"description": "Выберите события, которые должны запускать экранное меню (OSD). Если события не выбраны, экранное меню будет запускаться при любом доступном событии.",
"label": "События, запускающие экранное меню"
},
"volume": {
"description": "Показывать OSD при изменении громкости аудиовыхода.",
"label": "Выходная громкость"
}
}
},
"screen-recorder": {
"audio": {
@@ -1806,6 +1861,12 @@
"memory-section": {
"label": "Использование памяти"
},
"network-section": {
"label": "Сеть"
},
"polling-interval": {
"label": "Интервал опроса"
},
"temp-critical-threshold": {
"label": "Критический порог"
},
@@ -1816,7 +1877,7 @@
"label": "Температура ЦП"
},
"thresholds-section": {
"description": "Установите пороги для системных метрик; значения выше будут выделены.",
"description": "Настройте пороги предупреждения/критические пороги и интервалы опроса для каждой системной метрики.",
"label": "Пороги"
},
"title": "Системный монитор",
@@ -2048,6 +2109,11 @@
"unavailable": "История буфера обмена недоступна",
"unavailable-desc": "Приложение 'cliphist' не установлено. Пожалуйста, установите его, чтобы использовать функции истории буфера обмена."
},
"dark-mode": {
"dark-mode": "Тёмный режим",
"enabled": "Включен",
"light-mode": "Светлый режим"
},
"do-not-disturb": {
"disabled": "Режим 'Не беспокоить' отключен",
"disabled-desc": "Отображаются все уведомления.",
@@ -2128,64 +2194,70 @@
"tooltips": {
"add-widget": "Добавить виджет",
"bluetooth-devices": "Устройства Bluetooth",
"brightness-at": "Яркость: {brightness}%\nПравый клик для настроек.\nПрокрутка для изменения яркости.",
"cancel-timer": "Отменить таймер",
"brightness-at": "Яркость: {brightness}%",
"cancel-timer": "Таймер",
"clear-history": "Очистить историю",
"click-to-start-recording": "Нажмите, чтобы начать запись",
"click-to-stop-recording": "Нажмите, чтобы остановить запись",
"close": "Закрыть",
"connect-disconnect-devices": "Левый клик для подключения. Правый клик, чтобы забыть.",
"click-to-start-recording": "Запись экрана (начать запись)",
"click-to-stop-recording": "Запись экрана (остановить запись)",
"close": "Кнопка закрыть",
"connect-disconnect-devices": "Устройство Bluetooth",
"delete-notification": "Удалить уведомление",
"disable-keep-awake": "Нажмите, чтобы отключить режим 'Не засыпать'.\nПрокрутка для настройки таймаута.",
"do-not-disturb-disabled": "Режим 'Не беспокоить' отключен",
"do-not-disturb-enabled": "Режим 'Не беспокоить' включен",
"enable-keep-awake": "Нажмите, чтобы включить режим 'Не засыпать'.\nПрокрутка для настройки таймаута.",
"disable-keep-awake": "Не засыпать",
"do-not-disturb-disabled": "Не беспокоить",
"do-not-disturb-enabled": "Не беспокоить",
"enable-keep-awake": "Не засыпать",
"forget-network": "Забыть сеть",
"home": "Домой",
"input-muted": "Переключить заглушение ввода",
"grid-view": "Вид сеткой",
"hidden-files-hide": "Скрытые файлы",
"hidden-files-show": "Скрытые файлы",
"home": "Домашний каталог",
"input-muted": "Микрофон",
"keep-awake": "Не засыпать",
"keyboard-layout": "Раскладка клавиатуры {layout}",
"manage-vpn": "Управлять VPN-подключениями",
"manage-wifi": "Управление Wi-Fi",
"microphone-volume-at": "Громкость микрофона {volume}%\nЛевый клик для настроек. Правый клик для переключения заглушения.\nПрокрутка для изменения громкости.",
"move-to-center-section": "Переместить в центральную секцию",
"move-to-left-section": "Переместить в левую секцию",
"move-to-right-section": "Переместить в правую секцию",
"next-media": "Следующее медиа",
"list-view": "Вид списком",
"manage-vpn": "VPN-подключения",
"manage-wifi": "Wi-Fi",
"microphone-volume-at": "Громкость микрофона: {volume}%",
"move-to-center-section": "Центральная секция",
"move-to-left-section": "Левая секция",
"move-to-right-section": "Правая секция",
"next-media": "Следующий трек",
"next-month": "Следующий месяц",
"night-light-disabled": "Ночной свет отключен.\nЛевый клик для переключения режима.\nПравый клик для доступа к настройкам.",
"night-light-enabled": "Ночной свет включен.\nЛевый клик для переключения режима.\nПравый клик для доступа к настройкам.",
"night-light-forced": "Ночной свет принудительно включен.\nЛевый клик для переключения режима.\nПравый клик для доступа к настройкам.",
"night-light-not-installed": "Ночной свет недоступен.\nwlsunset не установлен.",
"noctalia-performance-disabled": "Режим производительности Noctalia отключен.\nЛевый клик для включения.",
"noctalia-performance-enabled": "Режим производительности Noctalia включен.\nЛевый клик для отключения.",
"open-control-center": "Открыть центр управления",
"open-notification-history-disable-dnd": "Открыть историю уведомлений\nПравый клик, чтобы отключить 'Не беспокоить'.",
"open-notification-history-enable-dnd": "Открыть историю уведомлений\nПравый клик, чтобы включить 'Не беспокоить'.",
"open-settings": "Открыть настройки",
"open-tray-dropdown": "Открыть выпадающий список трея",
"open-wallpaper-selector": "Открыть выбор обоев",
"output-muted": "Переключить заглушение вывода",
"pause": "Пауза",
"play": "Воспроизвести",
"power-profile": "Профиль питания '{profile}'",
"previous-media": "Предыдущее медиа",
"night-light-disabled": "Ночной свет",
"night-light-enabled": "Ночной свет",
"night-light-forced": "Ночной свет",
"night-light-not-installed": "Ночной свет (недоступен)",
"noctalia-performance-disabled": "Режим производительности Noctalia",
"noctalia-performance-enabled": "Режим производительности Noctalia",
"open-control-center": "Центр управления",
"open-notification-history-disable-dnd": "История уведомлений",
"open-notification-history-enable-dnd": "История уведомлений",
"open-settings": "Настройки",
"open-tray-dropdown": "Системный трей",
"open-wallpaper-selector": "Выбор обоев",
"output-muted": "Аудиовыход",
"pause": "Кнопка пауза",
"play": "Кнопка воспроизвести",
"power-profile": "{profile} профиль питания",
"previous-media": "Предыдущий трек",
"previous-month": "Предыдущий месяц",
"refresh": "Обновить",
"refresh-devices": "Обновить устройства",
"refresh-wallhaven": "Обновить результаты Wallhaven",
"refresh-wallpaper-list": "Обновить список обоев",
"remove-widget": "Удалить виджет",
"screen-recorder-not-installed": "Запись экрана не установлена",
"screen-recorder-not-installed": "Запись экрана (не установлена)",
"search": "Поиск",
"search-close": "Закрыть поиск",
"session-menu": "Меню сеанса",
"set-power-profile": "Установить профиль питания \"{profile}\"",
"start-screen-recording": "Начать запись экрана",
"stop-screen-recording": "Остановить запись экрана",
"switch-to-dark-mode": "Переключить на темный режим",
"switch-to-light-mode": "Переключить на светлый режим",
"up": "Вверх",
"volume-at": "Громкость вывода {volume}%\nЛевый клик для настроек. Правый клик для переключения заглушения.\nПрокрутка для изменения громкости.",
"wallpaper-selector": "Левый клик: Открыть выбор обоев.\nПравый клик: Установить случайные обои.",
"set-power-profile": "{profile} профиль питания",
"start-screen-recording": "Запись экрана",
"stop-screen-recording": "Запись экрана",
"switch-to-dark-mode": "Темный режим",
"switch-to-light-mode": "Светлый режим",
"up": "Родительский каталог",
"volume-at": "Громкость вывода: {volume}%",
"wallpaper-selector": "Выбор обоев",
"widget-settings": "Настройки виджета"
},
"wallpaper": {

View File

@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Varsayılan (Görüntü Aygıtı)",
"description": "Görüntülenecek pil cihazını seçin.",
"label": "Pil cihazı"
},
"display-mode": {
"description": "Bu değerin nasıl görünmesini istediğinizi seçin.",
"label": "Görüntüleme modu"
@@ -97,9 +102,13 @@
"control-center": {
"browse-file": "Dosyaya Göz At",
"browse-library": "Kütüphaneye Göz At",
"colorize-distro-logo": {
"description": "Dağıtım logonuza tema renklerini uygula.",
"label": "Dağıtım logosunu renklendir"
"color-selection": {
"description": "Simgeye tema renklerini uygular.",
"label": "Renk Seç"
},
"enable-colorization": {
"description": "Kontrol merkezi simgesi için renklendirmeyi etkinleştirir, tema renklerini uygular.",
"label": "Renklendirmeyi Etkinleştir"
},
"icon": {
"description": "Kütüphaneden veya özel bir dosyadan bir ikon seçin.",
@@ -227,14 +236,14 @@
"description": "Sanatçı - başlık yerine başlık - sanatçı olarak göster.",
"label": "Önce sanatçıyı göster"
},
"show-visualizer": {
"description": "Müzik çalarken bir ses görselleştirici göster.",
"label": "Görselleştiriciyi göster"
},
"show-progress-ring": {
"description": "Parça ilerlemesini gösteren dairesel bir ilerleme göstergesi gösterin.",
"label": "İlerleme halkası göster"
},
"show-visualizer": {
"description": "Müzik çalarken bir ses görselleştirici göster.",
"label": "Görselleştiriciyi göster"
},
"use-fixed-width": {
"description": "Etkinleştirildiğinde, widget dinamik olarak içerik göre ayarlamak yerine her zaman maksimum genişliği kullanır.",
"label": "Sabit Genişlik Kullan"
@@ -271,9 +280,6 @@
}
},
"system-monitor": {
"cpu-critical-threshold": {
"label": "CPU Kullanımı (Kritik)"
},
"cpu-temperature": {
"description": "Mevcut CPU sıcaklık okumalarını gösterilir.",
"label": "CPU sıcaklığı"
@@ -282,25 +288,10 @@
"description": "Mevcut CPU kullanım yüzdesini göster.",
"label": "CPU kullanımı"
},
"cpu-warning-threshold": {
"label": "CPU Kullanımı (Uyarı)"
},
"disk-critical-threshold": {
"label": "Depolama Alanı (Kritik)"
},
"disk-path": {
"description": "İzlenecek disk bağlama noktasını seçin.",
"label": "Disk yolu"
},
"disk-warning-threshold": {
"label": "Depolama Alanı (Uyarı)"
},
"mem-critical-threshold": {
"label": "Bellek Kullanımı (Kritik)"
},
"mem-warning-threshold": {
"label": "Bellek Kullanımı (Uyarı)"
},
"memory-percentage": {
"description": "Mutlak değerler yerine bellek kullanımını yüzde olarak göster.",
"label": "Bellek yüzde olarak"
@@ -316,16 +307,6 @@
"storage-usage": {
"description": "Disk alanı kullanım bilgilerini göster.",
"label": "Depolama kullanımı"
},
"temp-critical-threshold": {
"label": "CPU Sıcaklığı (Kritik)"
},
"temp-warning-threshold": {
"label": "CPU Sıcaklığı (Uyarı)"
},
"thresholds": {
"description": "Sistem metrik eşiklerini ayarlayın. Aşıldığında vurgular.",
"header": "Eşik Ayarları"
}
},
"taskbar": {
@@ -373,14 +354,14 @@
"description": "Çalışma alanı adlarından gösterilecek karakter sayısı (1-10).",
"label": "Karakter sayısı"
},
"hide-unoccupied": {
"description": "Penceresi olmayan çalışma alanlarını gösterme.",
"label": "Dolu olmayanları gizle"
},
"follow-focused-screen": {
"description": "Çubuğun bulunduğu ekran yerine, şu anda odaklanmış ekrandaki çalışma alanlarını göster.",
"label": "Odaklanmış Ekranı Takip Et"
},
"hide-unoccupied": {
"description": "Penceresi olmayan çalışma alanlarını gösterme.",
"label": "Dolu olmayanları gizle"
},
"label-mode": {
"description": "Çalışma alanı etiketlerinin nasıl gösterileceğini seçin.",
"label": "Etiket Modu"
@@ -389,8 +370,8 @@
}
},
"battery": {
"battery-level": "Pil seviyesi",
"brightness": "Parlaklık",
"charge-level": "Şarj seviyesi",
"charging": "Şarj oluyor.",
"charging-rate": "Şarj oranı: {rate} W.",
"discharging": "Deşarj oluyor.",
@@ -412,6 +393,7 @@
"blocked": "Engellendi",
"connect": "Bağlan",
"connected-devices": "Bağlı cihazlar",
"connecting": "Bağlanılıyor…",
"disabled": "Bluetooth devre dışı",
"disconnect": "Bağlantıyı Kes",
"enable-message": "Mevcut cihazları görmek için Bluetooth'u etkinleştirin.",
@@ -426,6 +408,19 @@
"panel": {
"week": "Hafta"
},
"timer": {
"countdown": "Geri sayım",
"duration": "Süre",
"hours": "h",
"minutes": "m",
"pause": "Duraklat",
"reset": "Sıfırla",
"seconds": "s",
"start": "Başla",
"stopwatch": "Kronometre",
"timer": "Zamanlayıcı",
"title": "Zamanlayıcı"
},
"weather": {
"loading": "Hava durumu yükleniyor..."
}
@@ -518,14 +513,20 @@
"no-notifications": "Bildirim yok",
"title": "Bildirimler"
},
"range": {
"all": "Tümü",
"earlier": "Öncekiler",
"today": "Bugün",
"yesterday": "Dün"
},
"time": {
"now": "şimdi",
"diffM": "1 dakika önce",
"diffMM": "{diff} dakika önce",
"diffD": "1 gün önce",
"diffDD": "{diff} gün önce",
"diffH": "1 saat önce",
"diffHH": "{diff} saat önce",
"diffD": "1 gün önce",
"diffDD": "{diff} gün önce"
"diffM": "1 dakika önce",
"diffMM": "{diff} dakika önce",
"now": "şimdi"
}
},
"options": {
@@ -545,6 +546,7 @@
},
"colors": {
"error": "Hata",
"none": "Hiçbiri",
"onSurface": "Üstünde",
"primary": "Birincil",
"secondary": "İkincil",
@@ -706,7 +708,7 @@
"enabled": "Bluetooth"
},
"tooltip": {
"action": "Tıklayarak Bluetooth cihazlarını yönet"
"action": "Bluetooth"
}
},
"keepAwake": {
@@ -715,7 +717,7 @@
"enabled": "Uyanık Tut"
},
"tooltip": {
"action": "Tıklayarak uyanık kalma modunu aç/kapat"
"action": "Uyanık Tut"
}
},
"nightLight": {
@@ -725,7 +727,7 @@
"forced": "Gece Işığı"
},
"tooltip": {
"action": "Tıklayarak gece ışığı modunu değiştir\nSağ tık: Ayarları"
"action": "Gece ışığı"
}
},
"notifications": {
@@ -734,7 +736,7 @@
"enabled": "Bildirimler"
},
"tooltip": {
"action": "Sol tık: Bildirim geçmişini aç\nSağ tık: Rahatsız etmeyi aç/kapat"
"action": "Bildirimler"
}
},
"powerProfile": {
@@ -742,7 +744,7 @@
"unavailable": "Güç Profili"
},
"tooltip": {
"action": "Tıklayarak güç profilini değiştir",
"action": "Güç profili",
"disabled": "Güç profillerini kullanmak için power-profiles-daemon kur"
}
},
@@ -752,13 +754,13 @@
"stopped": "Kaydet"
},
"tooltip": {
"action": "Tıklayarak ekran kaydını başlat/durdur"
"action": "Ekran kaydedici"
}
},
"wallpaperSelector": {
"label": "Duvar Kağıdı",
"tooltip": {
"action": "Sol tık: Duvar kağıdı seçiciyi aç\nSağ tık: Rastgele duvar kağıdı ayarla"
"action": "Duvar kağıdı seçici"
}
},
"wifi": {
@@ -768,7 +770,7 @@
"wifi": "Wi-Fi"
},
"tooltip": {
"action": "Tıklayarak Wi-Fi bağlantılarını yönet"
"action": "Wi-Fi"
}
}
},
@@ -794,6 +796,8 @@
},
"noctalia": {
"download-latest": "En son sürümü indir",
"git-commit": "Git commit:",
"git-commit-loading": "Yükleniyor...",
"installed-version": "Yüklü sürüm:",
"latest-version": "En son sürüm:",
"section": {
@@ -1040,15 +1044,27 @@
}
},
"templates": {
"compositors": {
"description": "Birleştirici temalandırma.",
"label": "Birleştiriciler",
"niri": {
"description": "{filepath} dosyasına yaz. niri v25.11+ gerektirir",
"description-missing": "{app} yüklü olmalıdır"
}
},
"misc": {
"description": "Ek yapılandırma seçenekleri.",
"label": "Çeşitli",
"description": "Kendi şablonlarınızı oluşturun",
"label": "Gelişmiş",
"user-templates": {
"description": "Kullanıcı tanımlı Matugen yapılandırmasını etkinleştir. İlk etkinleştirmede ~/.config/noctalia/user-templates.toml dosyası oluşturulacaktır",
"label": "Kullanıcı şablonları"
"description": "Yalnızca ne yaptığınızı biliyorsanız etkinleştirin, çevrimiçi belgelerimize bakın",
"label": "Kullanıcı şablonlarını etkinleştir"
}
},
"programs": {
"cava": {
"description": "{filepath} dosyasına yaz.",
"description-missing": "Kurulum için {app} gereklidir"
},
"code": {
"description": "{filepath} dosyasına yaz. Hyprluna temasının kurulu ve manuel olarak etkinleştirilmiş olması gerekir.",
"description-missing": "Code istemcisi tespit edilmedi. VSCode veya VSCodium kurun."
@@ -1075,10 +1091,6 @@
"description": "{filepath} dosyasına yaz.",
"description-missing": "Kurulum için {app} gereklidir"
},
"cava": {
"description": "{filepath} dosyasına yaz.",
"description-missing": "Kurulum için {app} gereklidir"
},
"vicinae": {
"description": "{filepath} dosyasına yaz ve yeniden yükle",
"description-missing": "Kurulum için {app} gereklidir"
@@ -1262,9 +1274,11 @@
},
"temperature": {
"day": "Gündüz",
"day-description": "Gündüz saatlerindeki renk sıcaklığını kontrol eder.",
"description": "Gece ve gündüz için renk sıcaklığını ayarlayın.",
"label": "Renk sıcaklığı",
"night": "Gece"
"night": "Gece",
"night-description": "Gece saatlerindeki renk sıcaklığını kontrol eder."
}
},
"title": "Görüntü"
@@ -1368,7 +1382,8 @@
"description": "Kullanıcı detaylarınızı ve avatarınızı düzenleyin.",
"label": "Profil"
},
"select-avatar": "Avatar görüntüsü seç"
"select-avatar": "Avatar görüntüsü seç",
"tooltip": "Avatar resmi ara"
},
"screen-corners": {
"radius": {
@@ -1446,6 +1461,10 @@
"description": "Uygulamaları başlatmak için varsayılan yöntem yerine özel bir ön ek kullanın.",
"label": "Özel başlatma önekini etkinleştir"
},
"grid-view": {
"description": "Öğeleri liste yerine ızgara düzeninde görüntüle.",
"label": "Izgara görünümü"
},
"position": {
"description": "Başlatıcı panelinin nerede görüneceğini seçin.",
"label": "Konum"
@@ -1470,6 +1489,20 @@
"title": "Başlatıcı"
},
"location": {
"calendar": {
"cards": {
"section": {
"description": "Takvim panelinde kartları düzenleyin ve etkinleştirin/devre dışı bırakın.",
"label": "Takvim kartları"
}
},
"header": {
"label": "Takvim Başlığı"
},
"month": {
"label": "Takvim Ayı"
}
},
"date-time": {
"12hour-format": {
"description": "Zamanı kilit ekranında ve takvimde 12 saatlik formatta gösterir. Çubuk saatinin kendi ayarları vardır.",
@@ -1671,7 +1704,29 @@
"label": "Genel"
}
},
"title": "Ekran Görüntüsü"
"title": "Ekran Görüntüsü",
"types": {
"brightness": {
"description": "Ekran parlaklığı değiştiğinde OSD'yi göster.",
"label": "Parlaklık"
},
"input-volume": {
"description": "Mikrofon ses düzeyi değiştiğinde OSD'yi göster.",
"label": "Giriş sesi"
},
"lockkey": {
"description": "Caps Lock, Num Lock veya Scroll Lock değiştirildiğinde OSD'yi göster.",
"label": "Kilit tuşları"
},
"section": {
"description": "OSD'yi tetikleyecek olayları seçin. Hiçbir olay seçilmezse, mevcut tüm olaylar OSD'yi tetikleyecektir.",
"label": "OSD tetikleme olayları"
},
"volume": {
"description": "Ses çıkış düzeyi değiştiğinde OSD'yi göster.",
"label": ıkış sesi"
}
}
},
"screen-recorder": {
"audio": {
@@ -1806,6 +1861,12 @@
"memory-section": {
"label": "Bellek Kullanımı"
},
"network-section": {
"label": "Ağ"
},
"polling-interval": {
"label": "Yoklama aralığı"
},
"temp-critical-threshold": {
"label": "Kritik Eşiği"
},
@@ -1816,7 +1877,7 @@
"label": "CPU Sıcaklığı"
},
"thresholds-section": {
"description": "Sistem metrikleri için eşikler ayarlayın; bu değerlerin üzerindekiler vurgulanacaktır.",
"description": "Her sistem metriği için uyarı/kritik eşiklerini ve yoklama aralıklarını ayarlayın.",
"label": "Eşikler"
},
"title": "Sistem İzleme",
@@ -2048,6 +2109,11 @@
"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."
},
"dark-mode": {
"dark-mode": "Karanlık mod",
"enabled": "Etkin",
"light-mode": "Aydınlık mod"
},
"do-not-disturb": {
"disabled": "'Rahatsız etme' devre dışı",
"disabled-desc": "Tüm bildirimler gösteriliyor.",
@@ -2128,64 +2194,70 @@
"tooltips": {
"add-widget": "Widget ekle",
"bluetooth-devices": "Bluetooth cihazları",
"brightness-at": "Parlaklık: %{brightness}\nAyarlar için sağ tık.\nParlaklığı değiştirmek için kaydırın.",
"cancel-timer": "Zamanlayıcıyı iptal et",
"brightness-at": "Parlaklık: %{brightness}",
"cancel-timer": "Zamanlayıcı",
"clear-history": "Geçmişi temizle",
"click-to-start-recording": "Kaydı başlatmak için tıklayın",
"click-to-stop-recording": "Kaydı durdurmak için tıklayın",
"close": "Kapat",
"connect-disconnect-devices": "Bağlanmak için sol tık. Unutmak için sağ tık.",
"click-to-start-recording": "Ekran kaydedici (kaydı başlat)",
"click-to-stop-recording": "Ekran kaydedici (kaydı durdur)",
"close": "Kapat düğmesi",
"connect-disconnect-devices": "Bluetooth cihazı",
"delete-notification": "Bildiriyi sil",
"disable-keep-awake": "Uyanık kalmayı devre dışı bırakmak için tıklayın.\nZaman aşımını ayarlamak için kaydırın.",
"do-not-disturb-disabled": "'Rahatsız etme' devre dışı",
"do-not-disturb-enabled": "'Rahatsız etme' etkin",
"enable-keep-awake": "Uyanık kalmayı etkinleştirmek için tıklayın.\nZaman aşımını ayarlamak için kaydırın.",
"disable-keep-awake": "Uyanık kal",
"do-not-disturb-disabled": "Rahatsız etme",
"do-not-disturb-enabled": "Rahatsız etme",
"enable-keep-awake": "Uyanık kal",
"forget-network": "Ağı unut",
"home": "Ana Sayfa",
"input-muted": "Giriş sessizliğini değiştir",
"grid-view": "Izgara görünümü",
"hidden-files-hide": "Gizli dosyalar",
"hidden-files-show": "Gizli dosyalar",
"home": "Ana dizin",
"input-muted": "Mikrofon",
"keep-awake": "Uyanık kal",
"keyboard-layout": "{layout} klavye düzeni",
"manage-vpn": "VPN bağlantılarını yönet",
"manage-wifi": "Wi-Fi yönet",
"microphone-volume-at": "Mikrofon sesi %{volume}\nAyarlar için sol tık. Sessize almak için sağ tık.\nSesi değiştirmek için kaydırın.",
"move-to-center-section": "Orta bölüme taşı",
"move-to-left-section": "Sol bölüme taşı",
"move-to-right-section": "S bölüme taşı",
"next-media": "Sonraki medya",
"list-view": "Liste görünümü",
"manage-vpn": "VPN bağlantıları",
"manage-wifi": "Wi-Fi",
"microphone-volume-at": "Mikrofon sesi: %{volume}",
"move-to-center-section": "Orta bölüm",
"move-to-left-section": "Sol bölüm",
"move-to-right-section": "Sağ bölüm",
"next-media": "Sonraki parça",
"next-month": "Sonraki ay",
"night-light-disabled": "Gece ışığı devre dışı.\nMod arasında geçiş yapmak için sol tık.\nAyarlara erişmek için sağ tık.",
"night-light-enabled": "Gece ışığı etkin.\nMod arasında geçiş yapmak için sol tık.\nAyarlara erişmek için sağ tık.",
"night-light-forced": "Gece ışığı zorla.\nMod arasında geçiş yapmak için sol tık.\nAyarlara erişmek için sağ tık.",
"night-light-not-installed": "Gece ışığı mevcut değil.\nwlsunset yüklü değil.",
"noctalia-performance-disabled": "Noctalia performans modu devre dışı.\nEtkinleştirmek için sol tıklayın.",
"noctalia-performance-enabled": "Noctalia performans modu etkin.\nDevre dışı bırakmak için sol tıklayın.",
"open-control-center": "Kontrol merkezini aç",
"open-notification-history-disable-dnd": "Bildirim geçmişini aç\n\"Rahatsız etmeyi\" devre dışı bırakmak için sağ tık.",
"open-notification-history-enable-dnd": "Bildirim geçmişini aç\n\"Rahatsız etmeyi\" etkinleştirmek için sağ tık.",
"open-settings": "Ayarları",
"open-tray-dropdown": "Tepsi açılır menüsünü aç",
"open-wallpaper-selector": "Duvar kağıdı seçiciyi aç",
"output-muted": "Çıkış sessizliğini değiştir",
"pause": "Duraklat",
"play": "Oynat",
"power-profile": "'{profile}' güç profili",
"previous-media": "Önceki medya",
"night-light-disabled": "Gece ışığı",
"night-light-enabled": "Gece ışığı",
"night-light-forced": "Gece ışığı",
"night-light-not-installed": "Gece ışığı (mevcut değil)",
"noctalia-performance-disabled": "Noctalia performans modu",
"noctalia-performance-enabled": "Noctalia performans modu",
"open-control-center": "Kontrol merkezi",
"open-notification-history-disable-dnd": "Bildirim geçmişi",
"open-notification-history-enable-dnd": "Bildirim geçmişi",
"open-settings": "Ayarlar",
"open-tray-dropdown": "Sistem tepsisı",
"open-wallpaper-selector": "Duvar kağıdı seçici",
"output-muted": "Ses çıkışı",
"pause": "Duraklat düğmesi",
"play": "Oynat düğmesi",
"power-profile": "{profile} güç profili",
"previous-media": "Önceki parça",
"previous-month": "Önceki ay",
"refresh": "Yenile",
"refresh-devices": "Cihazları yenile",
"refresh-wallhaven": "Wallhaven sonuçlarını yenile",
"refresh-wallpaper-list": "Duvar kağıdı listesini yenile",
"remove-widget": "Widget kaldır",
"screen-recorder-not-installed": "Ekran kaydedici yüklü değil",
"screen-recorder-not-installed": "Ekran kaydedici (yüklü değil)",
"search": "Ara",
"search-close": "Aramayı kapat",
"session-menu": "Oturum Menüsü",
"set-power-profile": "\"{profile}\" güç profilini ayarla",
"start-screen-recording": "Ekran kaydını başlat",
"stop-screen-recording": "Ekran kaydını durdur",
"switch-to-dark-mode": "Koyu moda geç",
"switch-to-light-mode": "Açık moda geç",
"up": "Yukarı",
"volume-at": ıkış sesi %{volume}\nAyarlar için sol tık. Sessize almak için sağ tık.\nSesi değiştirmek için kaydırın.",
"wallpaper-selector": "Sol tık: Duvar kağıdı seçiciyi aç.\nSağ tık: Rastgele duvar kağıdı ayarla.",
"set-power-profile": "{profile} güç profili",
"start-screen-recording": "Ekran kaydedici",
"stop-screen-recording": "Ekran kaydedici",
"switch-to-dark-mode": "Koyu mod",
"switch-to-light-mode": "Açık mod",
"up": "Üst dizin",
"volume-at": ıkış sesi: %{volume}",
"wallpaper-selector": "Duvar kağıdı seçici",
"widget-settings": "Widget ayarları"
},
"wallpaper": {

View File

@@ -1,7 +1,7 @@
{
"authentication": {
"error": "Помилка автентифікації",
"failed": "Помилка автентифікації"
"failed": "Невдала автентифікація"
},
"bar": {
"widget-settings": {
@@ -23,8 +23,8 @@
"label": "Режим прокрутки"
},
"show-app-icon": {
"description": "Відображати значок програми біля заголовка вікна.",
"label": "Показувати значок програми"
"description": "Відображати значок застосунка біля заголовка вікна.",
"label": "Показувати значок застосунка"
},
"use-fixed-width": {
"description": "Коли увімкнено, віджет завжди використовуватиме максимальну ширину замість динамічного налаштування до вмісту.",
@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "Пристрій відображення за замовчуванням",
"description": "Виберіть пристрій з акумулятором для відображення.",
"label": "Пристрій живлення від батареї"
},
"display-mode": {
"description": "Виберіть, як ви хочете, щоб це значення відображалося.",
"label": "Режим відображення"
@@ -97,9 +102,13 @@
"control-center": {
"browse-file": "Огляд файлу",
"browse-library": "Огляд бібліотеки",
"colorize-distro-logo": {
"description": "Застосувати кольори теми до логотипа вашого дистрибутива.",
"label": "Розфарбовувати логотип дистрибутива"
"color-selection": {
"description": "Застосовує кольори теми до іконок.",
"label": "Вибір кольору"
},
"enable-colorization": {
"description": "Вмикає розфарбовування для іконки центру керування, застосовуючи кольори теми.",
"label": "Увімкнути розфарбовування"
},
"icon": {
"description": "Вибрати значок з бібліотеки або власний файл.",
@@ -227,14 +236,14 @@
"description": "Відображати виконавець - назва замість назва - виконавець.",
"label": "Показувати спочатку виконавця"
},
"show-visualizer": {
"description": "Відображати аудіовізуалізатор під час відтворення музики.",
"label": "Показувати візуалізатор"
},
"show-progress-ring": {
"description": "Відображати круговий індикатор прогресу, що показує просування треку.",
"label": "Показувати кільце прогресу"
},
"show-visualizer": {
"description": "Відображати аудіовізуалізатор під час відтворення музики.",
"label": "Показувати візуалізатор"
},
"use-fixed-width": {
"description": "Коли увімкнено, віджет завжди використовуватиме максимальну ширину замість динамічного налаштування до вмісту.",
"label": "Використовувати фіксовану ширину"
@@ -271,9 +280,6 @@
}
},
"system-monitor": {
"cpu-critical-threshold": {
"label": "Використання ЦП (Критичний)"
},
"cpu-temperature": {
"description": "Показувати показники температури ЦП, якщо доступно.",
"label": "Температура ЦП"
@@ -282,25 +288,10 @@
"description": "Відображати поточний відсоток використання ЦП.",
"label": "Використання ЦП"
},
"cpu-warning-threshold": {
"label": "Використання ЦП (Попередження)"
},
"disk-critical-threshold": {
"label": "Місце на диску (Критичний)"
},
"disk-path": {
"description": "Виберіть точку монтування диска для моніторингу.",
"label": "Шлях до диска"
},
"disk-warning-threshold": {
"label": "Місце на диску (Попередження)"
},
"mem-critical-threshold": {
"label": "Використання пам'яті (Критичний)"
},
"mem-warning-threshold": {
"label": "Використання пам'яті (Попередження)"
},
"memory-percentage": {
"description": "Показувати використання пам'яті у відсотках замість абсолютних значень.",
"label": "Пам'ять у відсотках"
@@ -310,22 +301,12 @@
"label": "Використання пам'яті"
},
"network-traffic": {
"description": "Відображати швидкість завантаження та вивантаження в мережі.",
"description": "Відображати швидкість прийому та передачі даних.",
"label": "Мережевий трафік"
},
"storage-usage": {
"description": "Показувати інформацію про використання дискового простору.",
"label": "Використання сховища"
},
"temp-critical-threshold": {
"label": "Температура ЦП (Критичний)"
},
"temp-warning-threshold": {
"label": "Температура ЦП (Попередження)"
},
"thresholds": {
"description": "Встановіть пороги для оповіщень системних метрик. Виділяє при перевищенні.",
"header": "Налаштування порогів"
}
},
"taskbar": {
@@ -338,11 +319,11 @@
"label": "Режим приховування"
},
"only-active-workspaces": {
"description": "Показувати тільки програми з активних робочих просторів.",
"description": "Показувати тільки застосунки з активних робочих просторів.",
"label": "Тільки з активних робочих просторів"
},
"only-same-output": {
"description": "Показувати тільки програми з виходу, де розташована панель.",
"description": "Показувати тільки застосунки з виходу, де розташована панель.",
"label": "Тільки з того ж виходу"
}
},
@@ -359,7 +340,7 @@
},
"drawer-enabled": {
"description": "Коли увімкнено, не закріплені елементи трея відображаються на панелі ящика. Коли вимкнено, всі елементи трея відображаються в рядку.",
"label": "Увімкнути ящик"
"label": "Увімкнути висувну панель"
}
},
"volume": {
@@ -373,14 +354,14 @@
"description": "Кількість символів для відображення з назв робочих просторів (1-10).",
"label": "Кількість символів"
},
"follow-focused-screen": {
"description": "Відображати робочі простори з поточного активного екрана, а не з екрана, на якому розташована панель.",
"label": "Слідувати за активним eкраном"
},
"hide-unoccupied": {
"description": "Не відображати робочі простори без вікон.",
"label": "Приховати незайняті"
},
"follow-focused-screen": {
"description": "Відображати робочі простори з поточного активного екрана, а не з екрана, на якому розташована панель.",
"label": "Слідувати за Активним Екраном"
},
"label-mode": {
"description": "Виберіть, як відображаються мітки робочих просторів.",
"label": "Режим міток"
@@ -389,22 +370,22 @@
}
},
"battery": {
"battery-level": "Рівень заряду акумулятора",
"brightness": "Яскравість",
"charge-level": "Рівень заряду",
"charging": "Зарядка.",
"charging": "Заряджається",
"charging-rate": "Швидкість зарядки: {rate} Вт.",
"discharging": "Розрядка.",
"discharging": "Розряджається",
"discharging-rate": "Швидкість розрядки: {rate} Вт.",
"health": "Здоров'я: {percent}%",
"idle": "Бездіяльність.",
"health": "Стан: {percent}%",
"idle": "Простій",
"inhibit-idle-description": "Підтримує систему активною.",
"inhibit-idle-label": "Не давати заснути",
"no-battery-detected": "Батарею не виявлено.",
"panel-title": "Батарея",
"plugged-in": "Підключено.",
"plugged-in": "Підключено",
"power-profile": "Профіль живлення",
"time-left": "Залишилось часу: {time}.",
"time-until-full": "Час до повного заряду: {time}."
"time-left": "Залишилося: {time}",
"time-until-full": "До повного заряду: {time}"
},
"bluetooth": {
"panel": {
@@ -412,11 +393,12 @@
"blocked": "Заблоковано",
"connect": "Підключити",
"connected-devices": "Підключені пристрої",
"connecting": "Підключення…",
"disabled": "Bluetooth вимкнено",
"disconnect": "Відключити",
"enable-message": "Увімкніть Bluetooth, щоб побачити доступні пристрої.",
"known-devices": "Відомі пристрої",
"pairing": "Спарювання...",
"pairing": "Створення пари...",
"pairing-mode": "Переконайтеся, що ваш пристрій у режимі з'єднання.",
"scanning": "Сканування пристроїв...",
"title": "Bluetooth"
@@ -426,6 +408,19 @@
"panel": {
"week": "Тиждень"
},
"timer": {
"countdown": "Зворотний відлік",
"duration": "Тривалість",
"hours": "г",
"minutes": "м",
"pause": "Пауза",
"reset": "Скинути",
"seconds": "с",
"start": "Почати",
"stopwatch": "Секундомір",
"timer": "Таймер",
"title": "Таймер"
},
"weather": {
"loading": "Завантаження погоди…"
}
@@ -447,7 +442,7 @@
"version": "Версія {version}"
},
"subtitle": {
"fresh": "Дякуємо, що встановили Noctalia! Ось що містить цей білд.",
"fresh": "Дякуємо, що встановили Noctalia! Ось що містить цей збірка.",
"updated": "Оновлено з {previousVersion}"
},
"title": "Що нового у {version}",
@@ -479,7 +474,7 @@
"open-mixer": "Аудіомікшер",
"open-settings": "Відкрити налаштування",
"pause": "Пауза",
"play": "Грати",
"play": "Відтворити",
"previous": "Попередній",
"random-wallpaper": "Випадкові шпалери",
"toggle-mute": "Увімкнути/вимкнути звук",
@@ -495,7 +490,7 @@
},
"general": {
"no-results": "Немає результатів",
"no-summary": "Немає резюме",
"no-summary": "Немає опису",
"unknown": "Невідомо"
},
"launcher": {
@@ -518,20 +513,26 @@
"no-notifications": "Немає сповіщень",
"title": "Сповіщення"
},
"range": {
"all": "Усі",
"earlier": "Раніше",
"today": "Сьогодні",
"yesterday": "Учора"
},
"time": {
"now": "зараз",
"diffM": "1 хвилину тому",
"diffMM": "{diff} хвилин тому",
"diffD": "1 день тому",
"diffDD": "{diff} днів тому",
"diffH": "1 годину тому",
"diffHH": "{diff} годин тому",
"diffD": "1 день тому",
"diffDD": "{diff} днів тому"
"diffM": "1 хвилину тому",
"diffMM": "{diff} хвилин тому",
"now": "зараз"
}
},
"options": {
"bar": {
"density": {
"comfortable": "Зручний",
"comfortable": "Просторий",
"compact": "Компактний",
"default": "Стандартний",
"mini": "Міні"
@@ -545,6 +546,7 @@
},
"colors": {
"error": "Помилка",
"none": "Жоден",
"onSurface": "На поверхні",
"primary": "Основний",
"secondary": "Вторинний",
@@ -665,7 +667,7 @@
"placeholders": {
"cancel": "Скасувати",
"command-example": "echo \"Привіт, Світ\"",
"enter-command": "Введіть команду для виконання (програма або власний скрипт)",
"enter-command": "Введіть команду для виконання (застосунок або власний скрипт)",
"enter-text-to-collapse": "напр., 'нічого не відтворюється'. Використовуйте /regex/ для шаблонів.",
"enter-tooltip": "Введіть підказку",
"enter-width-pixels": "Введіть ширину в пікселях",
@@ -679,7 +681,7 @@
"test": "Тест"
},
"plugins": {
"applications": "Програми",
"applications": "Застосунки",
"calculator": "Калькулятор",
"calculator-description": "Калькулятор - обчислення математичних виразів",
"calculator-enter-expression": "Введіть математичний вираз",
@@ -706,16 +708,16 @@
"enabled": "Bluetooth"
},
"tooltip": {
"action": "Клацніть для керування пристроями Bluetooth"
"action": "Bluetooth"
}
},
"keepAwake": {
"label": {
"disabled": "Не спати",
"enabled": "Не спати"
"disabled": "Не давати заснути",
"enabled": "Не давати заснути"
},
"tooltip": {
"action": "Клацніть для перемикання режиму неспання"
"action": "Заборона сну"
}
},
"nightLight": {
@@ -725,7 +727,7 @@
"forced": "Нічне світло"
},
"tooltip": {
"action": "Клацніть для циклічної зміни режиму нічного світла\nПравий клік: Відкрити налаштування"
"action": "Нічне світло"
}
},
"notifications": {
@@ -734,7 +736,7 @@
"enabled": "Сповіщення"
},
"tooltip": {
"action": "Лівий клік: Відкрити історію сповіщень\nПравий клік: Перемкнути \"Не турбувати\""
"action": "Сповіщення"
}
},
"powerProfile": {
@@ -742,23 +744,23 @@
"unavailable": "Профіль живлення"
},
"tooltip": {
"action": "Клацніть для циклічної зміни профілю живлення",
"action": "Профіль живлення",
"disabled": "Встановіть power-profiles-daemon для використання профілів живлення"
}
},
"screenRecorder": {
"label": {
"recording": "Зупинити",
"stopped": "Записати"
"stopped": "Запис"
},
"tooltip": {
"action": "Клацніть для початку/зупинки запису екрана"
"action": "Запис екрана"
}
},
"wallpaperSelector": {
"label": "Шпалери",
"tooltip": {
"action": "Лівий клік: Відкрити вибір шпалер\nПравий клік: Встановити випадкові шпалери"
"action": "Вибір шпалер"
}
},
"wifi": {
@@ -768,7 +770,7 @@
"wifi": "Wi-Fi"
},
"tooltip": {
"action": "Клацніть для керування підключеннями Wi-Fi"
"action": "Wi-Fi"
}
}
},
@@ -787,13 +789,15 @@
"about": {
"contributors": {
"section": {
"description": "Вітаємо нашого {count} <b>чудового</b> учасника!",
"description_plural": "Вітаємо наших {count} <b>чудових</b> учасників!",
"description": "Подяка нашому {count} <b>чудовому</b> учаснику!",
"description_plural": "Подяка нашим {count} <b>чудовим</b> учасникам!",
"label": "Учасники"
}
},
"noctalia": {
"download-latest": "Завантажити останній реліз",
"git-commit": "Git коміт:",
"git-commit-loading": "Завантаження...",
"installed-version": "Встановлена версія:",
"latest-version": "Остання версія:",
"section": {
@@ -802,7 +806,7 @@
}
},
"support": "Підтримати нас",
"title": "Про програму"
"title": "Про Noctalia"
},
"audio": {
"devices": {
@@ -820,15 +824,15 @@
}
},
"external-mixer": {
"description": "Введіть команду або шлях до програми для запуску при активації функції зовнішнього аудіомікшера.",
"description": "Введіть команду або шлях до застосунку для запуску при активації функції зовнішнього аудіомікшера.",
"label": "Команда зовнішнього аудіомікшера",
"placeholder": "pwvucontrol || pavucontrol"
},
"media": {
"excluded-player": {
"description": "Додайте ключові слова для плеєрів, які система має ігнорувати. Кожне ключове слово на новому рядку.",
"label": "Виключений плеєр",
"placeholder": "введіть підрядок та натисніть +"
"label": "Ігнорований плеєр",
"placeholder": "введіть частину назви та натисніть +"
},
"frame-rate": {
"description": "Вищі значення плавніші, але споживають більше ресурсів.",
@@ -848,7 +852,7 @@
"label": "Прокрутка назви"
},
"section": {
"description": "Встановіть бажані та ігноровані медіапрограми.",
"description": "Встановіть бажані та ігноровані медіазастосунки.",
"label": "Медіаплеєри"
},
"visualizer-quality": {
@@ -888,7 +892,7 @@
},
"volume-overdrive": {
"description": "Дозволити підвищення гучності понад 100%. Може не підтримуватись усім обладнанням.",
"label": "Дозволити перегучність"
"label": "Гучність понад 100%"
}
}
},
@@ -907,8 +911,8 @@
"label": "Щільність панелі"
},
"floating": {
"description": "Відображати панель як плаваючу 'таблетку'. Примітка: Це перемістить кути екрана до країв.",
"label": "Плаваюча панель"
"description": "Відображати панель як плаваючу 'капсулу'. Примітка: Це перемістить кути екрана до країв.",
"label": "Плаваюча панель (Острівець)"
},
"margins": {
"description": "Налаштуйте поля навколо плаваючої панелі.",
@@ -961,7 +965,7 @@
"color-source": {
"matugen-scheme-type": {
"description": {
"scheme-content": "Виводить кольори, що тісно збігаються з базовим зображенням",
"scheme-content": "Генерує кольори, що тісно збігаються з базовим зображенням",
"scheme-expressive": "Яскрава палітра з грайливою насиченістю",
"scheme-fidelity": "Високоточна палітра, що зберігає вихідні відтінки",
"scheme-fruit-salad": "Барвистий мікс яскравих контрастних акцентів",
@@ -1040,20 +1044,32 @@
}
},
"templates": {
"compositors": {
"description": "Оформлення композитора.",
"label": "Композитори",
"niri": {
"description": "Записати {filepath}. Потребує niri v25.11+",
"description-missing": "Потрібно встановити {app}"
}
},
"misc": {
"description": "Додаткові параметри конфігурації.",
"label": "Різне",
"description": "Створіть власні шаблони",
"label": "Розширено",
"user-templates": {
"description": "Увімкнути визначений користувачем конфіг Matugen. Файл шаблону буде створено за адресою ~/.config/noctalia/user-templates.toml при першому увімкненні",
"label": "Користувацькі шаблони"
"description": "Увімкніть лише якщо ви знаєте, що робите, зверніться до нашої онлайн-документації",
"label": "Увімкнути користувацькі шаблони"
}
},
"programs": {
"cava": {
"description": "Записати {filepath}.",
"description-missing": "Потрібна установка {app}"
},
"code": {
"description": "Записати {filepath}. Тему Hyprluna потрібно встановити та активувати вручну.",
"description-missing": "Клієнт Code не виявлено. Встановіть VSCode або VSCodium."
},
"description": "Оформлення окремих програм.",
"description": "Оформлення окремих застосункiв.",
"discord": {
"description": "Записати {filepath} для {client}. Тему Hyprluna потрібно активувати вручну.",
"description-missing": "Клієнт Discord не виявлено. Встановіть vencord, vesktop, webcord, armcord, equibop, lightcord або dorion."
@@ -1062,7 +1078,7 @@
"description": "Записати {filepath} та перезавантажити",
"description-missing": "Потрібна установка {app}"
},
"label": "Програми",
"label": "Застосунки",
"pywalfox": {
"description": "Записати {filepath} та запустити pywalfox update",
"description-missing": "Потрібна установка {app}"
@@ -1075,10 +1091,6 @@
"description": "Записати {filepath}.",
"description-missing": "Потрібна установка {app}"
},
"cava": {
"description": "Записати {filepath}.",
"description-missing": "Потрібна установка {app}"
},
"vicinae": {
"description": "Записати {filepath} та перезавантажити",
"description-missing": "Потрібна установка {app}"
@@ -1089,7 +1101,7 @@
}
},
"section": {
"description": "Застосовувати кольори до зовнішніх програм.",
"description": "Застосовувати кольори до зовнішніх застосункiв.",
"label": "Шаблони"
},
"terminal": {
@@ -1112,7 +1124,7 @@
},
"label": "Термінал",
"wezterm": {
"description": "Запишіть {filepath} та перезавантажте",
"description": "Записати {filepath} та перезавантажити",
"description-missing": "Потрібно встановити {app}"
}
},
@@ -1262,9 +1274,11 @@
},
"temperature": {
"day": "День",
"day-description": "Керує колірною температурою вдень.",
"description": "Встановіть теплоту кольору для нічного та денного часу.",
"label": "Колірна температура",
"night": "Ніч"
"night": "Ніч",
"night-description": "Керує колірною температурою вночі."
}
},
"title": "Дисплей"
@@ -1280,14 +1294,14 @@
"label": "Радіус заокруглення"
},
"colorize-icons": {
"description": "Застосувати кольори теми до значків програм у доці (тільки неактивні програми).",
"description": "Застосувати кольори теми до значків застосункiв у доці (тільки неактивні застосунки).",
"label": "Розфарбувати значки"
},
"display": {
"always-visible": "Завжди видимий",
"auto-hide": "Автоприховування",
"description": "Виберіть, як поводиться док.",
"exclusive": "Ексклюзивний",
"exclusive": "Винятковий",
"label": "Відображення"
},
"floating-distance": {
@@ -1309,8 +1323,8 @@
},
"monitors": {
"only-same-output": {
"description": "Показувати тільки програми з виходу, де розташований док.",
"label": "Тільки програми з того ж виходу"
"description": "Показувати тільки застосунки з виходу, де розташований док.",
"label": "Тільки застосунки з того ж виходу"
},
"section": {
"description": "Показувати док на певних моніторах. За замовчуванням на всіх, якщо не вибрано.",
@@ -1327,7 +1341,7 @@
"placeholder": "Виберіть стандартний шрифт...",
"scale": {
"description": "Збільшити або зменшити розмір звичайного тексту.",
"label": "Розмір стандартного шрифту"
"label": "Масштаб стандартного шрифту"
},
"search-placeholder": "Пошук шрифту..."
},
@@ -1349,26 +1363,27 @@
},
"language": {
"section": {
"description": "Виберіть бажану мову програми.",
"description": "Виберіть бажану мову застосунку.",
"label": "Мова"
},
"select": {
"auto-detect": "Автоматично",
"description": "Виберіть мову інтерфейсу програми.",
"label": "Мова програми"
"description": "Виберіть мову інтерфейсу застосунку.",
"label": "Мова застосунку"
}
},
"launch-setup-wizard": "Запустити майстер налаштування",
"profile": {
"picture": {
"description": "Ваше фото профілю, що відображається в інтерфейсі.",
"label": "Фото профілю {user}"
"label": "Фото профілю користувача {user}"
},
"section": {
"description": "Редагуйте дані користувача та аватар.",
"label": "Профіль"
},
"select-avatar": "Вибрати зображення аватара"
"select-avatar": "Вибрати зображення аватара",
"tooltip": "Переглянути зображення аватара"
},
"screen-corners": {
"radius": {
@@ -1386,7 +1401,7 @@
},
"solid-black": {
"description": "Використовувати суцільний чорний колір замість кольору фону панелі.",
"label": "Суцільно чорні кути"
"label": "Суцільні чорні кути"
}
},
"title": "Загальні"
@@ -1394,7 +1409,7 @@
"hooks": {
"info": {
"command-info": {
"description": "• Команди виконуються через оболонку (sh -c)\n• Команди запускаються у фоні (відокремлено)\n• Кнопки тестування виконують з поточними значеннями",
"description": "• Команди виконуються через оболонку (sh -c)\n• Команди запускаються у фоні (відокремлено)\n• Кнопки тестування виконують команди з поточними значеннями",
"label": "Інформація про команди хуків"
},
"parameters": {
@@ -1427,35 +1442,39 @@
"launcher": {
"settings": {
"background-opacity": {
"description": "Налаштуйте непрозорість фону запускача.",
"description": "Налаштуйте непрозорість фону лаунчера.",
"label": "Непрозорість фону"
},
"clip-preview": {
"description": "Показувати попередній перегляд вмісту буфера обміну при використанні команди >clip.",
"label": "Увімкнути попередній перегляд буфера обміну"
"label": "Попередній перегляд буфера обміну"
},
"clipboard-history": {
"description": "Отримати доступ до раніше скопійованих елементів із запускача.",
"description": "Отримати доступ до раніше скопійованих елементів із лаунчера.",
"label": "Увімкнути історію буфера обміну"
},
"custom-launch-prefix": {
"description": "Додати префікс до команд власним запускачем (напр., 'runapp' для інтеграції з systemd).",
"label": "Власний префікс запуску"
"description": "Додати префікс до команд запуску лаунчером (напр., 'runapp' для інтеграції з systemd).",
"label": "Користувацький префікс запуску"
},
"custom-launch-prefix-enabled": {
"description": "Використовувати власний префікс для запуску програм замість стандартного методу.",
"label": "Увімкнути власний префікс запуску"
"description": "Використовувати власний префікс для запуску застосунків замість стандартного методу.",
"label": "Увімкнути користувацький префікс запуску"
},
"grid-view": {
"description": "Показувати елементи у вигляді сітки замість списку.",
"label": "Режим сітки"
},
"position": {
"description": "Виберіть, де з'являється панель запускача.",
"description": "Виберіть, де з'являється панель лаунчера.",
"label": "Положення"
},
"section": {
"description": "Налаштуйте поведінку та зовнішній вигляд запускача.",
"description": "Налаштуйте поведінку та зовнішній вигляд лаунчера.",
"label": "Зовнішній вигляд"
},
"sort-by-usage": {
"description": "Коли увімкнено, часто запускані програми з'являються першими в списку.",
"description": "Коли увімкнено, часто використовувані застосунки з'являються першими в списку.",
"label": "Сортувати за використанням"
},
"terminal-command": {
@@ -1463,13 +1482,27 @@
"label": "Команда терміналу"
},
"use-app2unit": {
"description": "Використовує альтернативний метод запуску для кращого управління процесами програм і запобігання проблемам.",
"label": "Використовувати App2Unit для запуску програм"
"description": "Використовує альтернативний метод запуску для кращого керування процесами застосунків і запобігання проблемам.",
"label": "Використовувати App2Unit для запуску застосунків"
}
},
"title": "Запускач"
"title": "Лаунчер"
},
"location": {
"calendar": {
"cards": {
"section": {
"description": "Організуйте та вмикайте/вимикайте картки на панелі календаря.",
"label": "Календарні картки"
}
},
"header": {
"label": "Заголовок календаря"
},
"month": {
"label": "Місяць календаря"
}
},
"date-time": {
"12hour-format": {
"description": "Відображати час у 12-годинному форматі на екрані блокування та в календарі. Годинник на панелі має власні налаштування.",
@@ -1575,8 +1608,8 @@
},
"reset": "Скинути тривалість очікування",
"respect-expire": {
"description": "Використовувати час закінчення, встановлений у сповіщенні.",
"label": "Дотримуватись часу закінчення"
"description": "Використовувати тривалість показу, встановлений у сповіщенні.",
"label": "Враховувати тривалість показу"
},
"section": {
"description": "Налаштуйте, як довго сповіщення залишаються видимими залежно від рівня терміновості.",
@@ -1626,8 +1659,8 @@
"label": "Розкладка клавіатури"
},
"section": {
"description": "Налаштуйте зовнішній вигляд і поведінку спливаючих сповіщень.",
"label": "Тост"
"description": "Налаштуйте зовнішній вигляд і поведінку спливаючих повідомлень.",
"label": "Спливаючі повідомлення"
}
}
},
@@ -1671,7 +1704,29 @@
"label": "Загальні"
}
},
"title": "Екранна індикація"
"title": "Екранна індикація",
"types": {
"brightness": {
"description": "Показувати OSD, коли змінюється яскравість екрана.",
"label": "Яскравість"
},
"input-volume": {
"description": "Показувати OSD, коли змінюється гучність мікрофона.",
"label": "Вхідна гучність"
},
"lockkey": {
"description": "Показувати OSD, коли перемикаються Caps Lock, Num Lock або Scroll Lock.",
"label": "Клавіші блокування"
},
"section": {
"description": "Виберіть події, які запускають екранне меню. Якщо жодну подію не вибрано, екранне меню запускатиметься всіма доступними подіями.",
"label": "Події, що запускають OSD"
},
"volume": {
"description": "Показувати OSD, коли змінюється гучність аудіовиходу.",
"label": "Вихідна гучність"
}
}
},
"screen-recorder": {
"audio": {
@@ -1806,6 +1861,12 @@
"memory-section": {
"label": "Використання пам'яті"
},
"network-section": {
"label": "Мережа"
},
"polling-interval": {
"label": "Інтервал опитування"
},
"temp-critical-threshold": {
"label": "Критичний поріг"
},
@@ -1816,7 +1877,7 @@
"label": "Температура ЦП"
},
"thresholds-section": {
"description": "Встановіть пороги для системних метрик; значення вище будуть підсвічені.",
"description": "Налаштуйте пороги попередження/критичні пороги та інтервали опитування для кожного системного показника.",
"label": "Пороги"
},
"title": "Системний монітор",
@@ -1865,7 +1926,7 @@
"label": "Прив'язувати панелі до країв"
},
"panels-overlay": {
"description": "Забезпечує видимість панелей і панелі завдань, навіть поверх повноекранних програм.",
"description": "Забезпечує видимість панелей і панелі завдань, навіть поверх повноекранних застосункiв.",
"label": "Тримати панелі та панель зверху"
},
"scaling": {
@@ -1927,7 +1988,7 @@
"label": "Режим заповнення"
},
"section": {
"label": "Вигляд і відчуття"
"label": "Зовнішній вигляд"
},
"transition-duration": {
"description": "Тривалість анімацій переходу в секундах.",
@@ -1940,8 +2001,8 @@
},
"settings": {
"enable-management": {
"description": "Керувати шпалерами за допомогою Noctalia. Вимкніть, якщо надаєте перевагу іншій програмі.",
"label": "Увімкнути управління шпалерами"
"description": "Керувати шпалерами за допомогою Noctalia. Вимкніть, якщо надаєте перевагу іншому застосунку.",
"label": "Увімкнути керування шпалерами"
},
"enable-overview": {
"description": "Застосовує розмиті та затемнені шпалери до екрана огляду.",
@@ -1954,7 +2015,7 @@
},
"hide-wallpaper-filenames": {
"description": "Приховати назви файлів шпалер у селекторі.",
"label": "Приховати імена файлів"
"label": "Приховати назви файлів"
},
"monitor-specific": {
"description": "Встановити різні теки шпалер для кожного монітора.",
@@ -1962,7 +2023,7 @@
"tooltip": "Огляд теки шпалер"
},
"recursive-search": {
"description": "Також шукати шпалери в підтеках директорії шпалер.",
"description": "Також шукати шпалери в підтеках теки шпалер.",
"label": "Шукати в підтеках"
},
"section": {
@@ -2022,7 +2083,7 @@
"scaling-percentage": "{percentage}%",
"signal-strength": "{signal}%",
"unknown": "Невідомо",
"unknown-app": "Невідома програма",
"unknown-app": "Невідомий застосунок",
"unknown-layout": "Невідомо",
"unknown-version": "Невідомо",
"uptime": "Час роботи: {uptime}",
@@ -2038,7 +2099,7 @@
},
"battery": {
"low": "Низький заряд батареї",
"low-desc": "Батарея на {percent}%. Будь ласка, підключіть зарядний пристрій."
"low-desc": "Рівень заряду: {percent}%. Будь ласка, підключіть зарядний пристрій."
},
"bluetooth": {
"disabled": "Вимкнено",
@@ -2046,7 +2107,12 @@
},
"clipboard": {
"unavailable": "Історія буфера обміну недоступна",
"unavailable-desc": "Програма 'cliphist' не встановлена. Будь ласка, встановіть її для використання функцій історії буфера обміну."
"unavailable-desc": "Застосунок 'cliphist' не встановлений. Будь ласка, встановіть його для використання функцій історії буфера обміну."
},
"dark-mode": {
"dark-mode": "Темний режим",
"enabled": "Увімкнено",
"light-mode": "Світлий режим"
},
"do-not-disturb": {
"disabled": "'Не турбувати' вимкнено",
@@ -2085,14 +2151,14 @@
"noctalia-performance": {
"disabled": "Режим продуктивності вимкнено.",
"enabled": "Режим продуктивності ввімкнено.",
"label": "Виступ Noctalia"
"label": "Продуктивність Noctalia"
},
"power-profile": {
"changed": "Профіль живлення змінено",
"profile-name": "\"{profile}\""
},
"recording": {
"failed-general": "Рекордер завершився з помилкою.",
"failed-general": "Засіб запису завершився з помилкою.",
"failed-gpu": "gpu-screen-recorder несподівано завершився.",
"failed-start": "Не вдалося розпочати запис",
"no-portals": "Портали робочого столу не запущені",
@@ -2128,64 +2194,70 @@
"tooltips": {
"add-widget": "Додати віджет",
"bluetooth-devices": "Пристрої Bluetooth",
"brightness-at": "Яскравість: {brightness}%\nПравий клік для налаштувань.\nПрокрутка для зміни яскравості.",
"cancel-timer": "Скасувати таймер",
"brightness-at": "Яскравість: {brightness}%",
"cancel-timer": "Таймер",
"clear-history": "Очистити історію",
"click-to-start-recording": "Клацніть для початку запису",
"click-to-stop-recording": "Клацніть для зупинки запису",
"close": "Закрити",
"connect-disconnect-devices": "Лівий клік для підключення. Правий клік для забування.",
"click-to-start-recording": "Запис екрана (почати запис)",
"click-to-stop-recording": "Запис екрана (зупинити запис)",
"close": "Кнопка закрити",
"connect-disconnect-devices": "Пристрій Bluetooth",
"delete-notification": "Видалити сповіщення",
"disable-keep-awake": "Клацніть, щоб вимкнути режим неспання.\nПрокрутіть, щоб налаштувати тайм-аут.",
"do-not-disturb-disabled": "'Не турбувати' вимкнено",
"do-not-disturb-enabled": "'Не турбувати' увімкнено",
"enable-keep-awake": "Клацніть, щоб увімкнути режим неспання.\nПрокрутіть, щоб налаштувати тайм-аут.",
"disable-keep-awake": "Заборона сну",
"do-not-disturb-disabled": "Не турбувати",
"do-not-disturb-enabled": "Не турбувати",
"enable-keep-awake": "Заборона сну",
"forget-network": "Забути мережу",
"home": "Додому",
"input-muted": "Перемкнути вимкнення входу",
"keep-awake": "Не спати",
"grid-view": "Мережевий вигляд",
"hidden-files-hide": "Приховані файли",
"hidden-files-show": "Приховані файли",
"home": "Домашній каталог",
"input-muted": "Мікрофон",
"keep-awake": "Заборона сну",
"keyboard-layout": "Розкладка клавіатури {layout}",
"manage-vpn": "Керувати підключеннями VPN",
"manage-wifi": "Керувати Wi-Fi",
"microphone-volume-at": "Гучність мікрофона на {volume}%\nЛівий клік для налаштувань. Правий клік для вимкнення звуку.\nПрокрутка для зміни гучності.",
"move-to-center-section": "Перемістити в центральну секцію",
"move-to-left-section": "Перемістити в ліву секцію",
"move-to-right-section": "Перемістити в праву секцію",
"next-media": "Наступне медіа",
"list-view": "Список",
"manage-vpn": "Підключення VPN",
"manage-wifi": "Wi-Fi",
"microphone-volume-at": "Гучність мікрофона: {volume}%",
"move-to-center-section": "Центральна секція",
"move-to-left-section": "Ліва секція",
"move-to-right-section": "Права секція",
"next-media": "Наступний трек",
"next-month": "Наступний місяць",
"night-light-disabled": "Нічне світло вимкнено.\nЛівий клік для циклічного режиму.\nПравий клік для доступу до налаштувань.",
"night-light-enabled": "Нічне світло увімкнено.\nЛівий клік для циклічного режиму.\nПравий клік для доступу до налаштувань.",
"night-light-forced": "Нічне світло примусово.\nЛівий клік для циклічного режиму.\nПравий клік для доступу до налаштувань.",
"night-light-not-installed": "Нічне світло недоступне.\nwlsunset не встановлено.",
"noctalia-performance-disabled": "Режим продуктивності Noctalia вимкнено.\nЛівий клік для увімкнення.",
"noctalia-performance-enabled": "Режим продуктивності Noctalia увімкнено.\nЛівий клік для вимкнення.",
"open-control-center": "Відкрити центр керування",
"open-notification-history-disable-dnd": "Відкрити історію сповіщень\nПравий клік для вимкнення \"Не турбувати\".",
"open-notification-history-enable-dnd": "Відкрити історію сповіщень\nПравий клік для увімкнення \"Не турбувати\".",
"open-settings": "Відкрити налаштування",
"open-tray-dropdown": "Відкрити спадне меню трею",
"open-wallpaper-selector": "Відкрити вибір шпалер",
"output-muted": "Перемкнути вимкнення виходу",
"pause": "Пауза",
"play": "Відтворити",
"power-profile": "Профіль живлення '{profile}'",
"previous-media": "Попереднє медіа",
"night-light-disabled": "Нічне світло",
"night-light-enabled": "Нічне світло",
"night-light-forced": "Нічне світло",
"night-light-not-installed": "Нічне світло (недоступне)",
"noctalia-performance-disabled": "Режим продуктивності Noctalia",
"noctalia-performance-enabled": "Режим продуктивності Noctalia",
"open-control-center": "Центр керування",
"open-notification-history-disable-dnd": "Історія сповіщень",
"open-notification-history-enable-dnd": "Історія сповіщень",
"open-settings": "Налаштування",
"open-tray-dropdown": "Системний трей",
"open-wallpaper-selector": "Вибір шпалер",
"output-muted": "Аудіовихід",
"pause": "Кнопка пауза",
"play": "Кнопка відтворити",
"power-profile": "{profile} профіль живлення",
"previous-media": "Попередній трек",
"previous-month": "Попередній місяць",
"refresh": "Оновити",
"refresh-devices": "Оновити пристрої",
"refresh-wallhaven": "Оновити результати Wallhaven",
"refresh-wallpaper-list": "Оновити список шпалер",
"remove-widget": "Видалити віджет",
"screen-recorder-not-installed": "Запис екрана не встановлено",
"screen-recorder-not-installed": "Запис екрана (не встановлено)",
"search": "Пошук",
"search-close": "Закрити пошук",
"session-menu": "Меню сеансу",
"set-power-profile": "Встановити профіль живлення \"{profile}\"",
"start-screen-recording": "Почати запис екрана",
"stop-screen-recording": "Зупинити запис екрана",
"switch-to-dark-mode": "Перемкнутися на темний режим",
"switch-to-light-mode": "Перемкнутися на світлий режим",
"up": "Вгору",
"volume-at": "Вихідна гучність на {volume}%\nЛівий клік для налаштувань. Правий клік для вимкнення звуку.\nПрокрутка для зміни гучності.",
"wallpaper-selector": "Лівий клік: Відкрити вибір шпалер.\nПравий клік: Встановити випадкові шпалери.",
"set-power-profile": "{profile} профіль живлення",
"start-screen-recording": "Запис екрана",
"stop-screen-recording": "Запис екрана",
"switch-to-dark-mode": "Темний режим",
"switch-to-light-mode": "Світлий режим",
"up": "Батьківський каталог",
"volume-at": "Вихідна гучність: {volume}%",
"wallpaper-selector": "Вибір шпалер",
"widget-settings": "Налаштування віджета"
},
"wallpaper": {
@@ -2193,7 +2265,7 @@
"fill-modes": {
"center": "Центр",
"crop": "Обрізати (Заповнити)",
"fit": "Вмістити (Містити)",
"fit": "Вмістити (Вписати)",
"stretch": "Розтягнути"
},
"no-match": "Збігів не знайдено.",
@@ -2218,7 +2290,7 @@
"all": "Всі",
"label": "Фільтр контенту",
"sfw": "SFW",
"sketchy": "Sketchy"
"sketchy": "Сумнівний"
},
"resolution": {
"atleast": "Принаймні",
@@ -2240,7 +2312,7 @@
},
"source": {
"label": "Джерело",
"local": "Локальний",
"local": "Локальне",
"wallhaven": "Wallhaven"
},
"title": "Вибір шпалер",
@@ -2300,7 +2372,7 @@
"european-date": "Європейський формат дати",
"iso-date": "Формат дати ISO",
"us-date": "Формат дати США",
"weekday-date": "День тижня з датою",
"weekday-date": "День тижня і дата",
"weekday-month-day": "День тижня, місяць і день"
},
"day": {

View File

@@ -46,6 +46,11 @@
}
},
"battery": {
"device": {
"default": "默认(显示设备)",
"description": "选择要显示的电池设备。",
"label": "电池设备"
},
"display-mode": {
"description": "选择您希望此值显示的方式。",
"label": "显示模式"
@@ -97,9 +102,13 @@
"control-center": {
"browse-file": "浏览文件",
"browse-library": "浏览库",
"colorize-distro-logo": {
"description": "将主题颜色应用到您的发行版徽标。",
"label": "为发行版徽标着色"
"color-selection": {
"description": "将主题颜色应用于图标。",
"label": "选择颜色"
},
"enable-colorization": {
"description": "为控制中心图标启用着色,应用主题颜色。",
"label": "启用着色"
},
"icon": {
"description": "从库中选择图标或自定义文件。",
@@ -227,14 +236,14 @@
"description": "显示艺术家 - 标题,而不是标题 - 艺术家。",
"label": "先显示艺术家"
},
"show-visualizer": {
"description": "播放音乐时显示音频可视化器。",
"label": "显示可视化器"
},
"show-progress-ring": {
"description": "显示显示曲目进度的圆形进度指示器。",
"label": "显示进度环"
},
"show-visualizer": {
"description": "播放音乐时显示音频可视化器。",
"label": "显示可视化器"
},
"use-fixed-width": {
"description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。",
"label": "使用固定宽度"
@@ -271,9 +280,6 @@
}
},
"system-monitor": {
"cpu-critical-threshold": {
"label": "CPU 使用率(严重)"
},
"cpu-temperature": {
"description": "如果可用显示CPU温度读数。",
"label": "CPU温度"
@@ -282,25 +288,10 @@
"description": "显示当前CPU使用百分比。",
"label": "CPU使用率"
},
"cpu-warning-threshold": {
"label": "CPU 使用率(警告)"
},
"disk-critical-threshold": {
"label": "存储空间(严重)"
},
"disk-path": {
"description": "选择要监控的磁盘挂载点。",
"label": "磁盘路径"
},
"disk-warning-threshold": {
"label": "存储空间(警告)"
},
"mem-critical-threshold": {
"label": "内存使用率(严重)"
},
"mem-warning-threshold": {
"label": "内存使用率(警告)"
},
"memory-percentage": {
"description": "以百分比而不是绝对值显示内存使用情况。",
"label": "内存百分比"
@@ -316,16 +307,6 @@
"storage-usage": {
"description": "显示磁盘空间使用信息。",
"label": "存储使用率"
},
"temp-critical-threshold": {
"label": "CPU 温度(严重)"
},
"temp-warning-threshold": {
"label": "CPU 温度(警告)"
},
"thresholds": {
"description": "配置系统指标提示的阈值,超出阈值时将以高亮色进行提示。",
"header": "阈值设置"
}
},
"taskbar": {
@@ -373,14 +354,14 @@
"description": "显示工作区名称的字符数量1-10。",
"label": "字符数量"
},
"hide-unoccupied": {
"description": "不显示没有窗口的工作区。",
"label": "隐藏未占用"
},
"follow-focused-screen": {
"description": "显示当前焦点屏幕的工作区,而不是任务栏所在屏幕的工作区。",
"label": "跟随焦点屏幕"
},
"hide-unoccupied": {
"description": "不显示没有窗口的工作区。",
"label": "隐藏未占用"
},
"label-mode": {
"description": "选择工作区标签的显示方式。",
"label": "标签模式"
@@ -389,8 +370,8 @@
}
},
"battery": {
"battery-level": "电池电量",
"brightness": "亮度",
"charge-level": "电量",
"charging": "正在充电。",
"charging-rate": "充电速率:{rate} W。",
"discharging": "正在放电。",
@@ -412,6 +393,7 @@
"blocked": "已阻止",
"connect": "连接",
"connected-devices": "已连接设备",
"connecting": "正在连接…",
"disabled": "蓝牙已禁用",
"disconnect": "断开",
"enable-message": "启用蓝牙以查看可用设备。",
@@ -426,6 +408,19 @@
"panel": {
"week": "周"
},
"timer": {
"countdown": "倒计时",
"duration": "时长",
"hours": "h",
"minutes": "米",
"pause": "暂停",
"reset": "重置",
"seconds": "s",
"start": "开始",
"stopwatch": "秒表",
"timer": "计时器",
"title": "计时器"
},
"weather": {
"loading": "正在加载天气…"
}
@@ -518,14 +513,20 @@
"no-notifications": "无通知",
"title": "通知"
},
"range": {
"all": "全部",
"earlier": "更早",
"today": "今天",
"yesterday": "昨天"
},
"time": {
"now": "现在",
"diffM": "1 分钟前",
"diffMM": "{diff} 分钟前",
"diffD": "1 天前",
"diffDD": "{diff} 天前",
"diffH": "1 小时前",
"diffHH": "{diff} 小时前",
"diffD": "1 前",
"diffDD": "{diff} 前"
"diffM": "1 分钟前",
"diffMM": "{diff} 分钟前",
"now": "现在"
}
},
"options": {
@@ -545,6 +546,7 @@
},
"colors": {
"error": "错误色",
"none": "无",
"onSurface": "表层对应色",
"primary": "主要色",
"secondary": "次要色",
@@ -706,7 +708,7 @@
"enabled": "蓝牙"
},
"tooltip": {
"action": "点击管理蓝牙设备"
"action": "蓝牙"
}
},
"keepAwake": {
@@ -715,7 +717,7 @@
"enabled": "保持唤醒"
},
"tooltip": {
"action": "点击切换保持唤醒模式"
"action": "保持唤醒"
}
},
"nightLight": {
@@ -725,7 +727,7 @@
"forced": "夜间模式"
},
"tooltip": {
"action": "点击切换夜间模式\n右键打开设置"
"action": "夜间模式"
}
},
"notifications": {
@@ -734,7 +736,7 @@
"enabled": "通知"
},
"tooltip": {
"action": "左键:打开通知历史\n右键切换勿扰模式"
"action": "通知"
}
},
"powerProfile": {
@@ -742,7 +744,7 @@
"unavailable": "电源模式"
},
"tooltip": {
"action": "点击切换电源模式",
"action": "电源模式",
"disabled": "安装 power-profiles-daemon 以使用电源模式"
}
},
@@ -752,13 +754,13 @@
"stopped": "录制"
},
"tooltip": {
"action": "点击开始/停止屏幕录制"
"action": "屏幕录制"
}
},
"wallpaperSelector": {
"label": "壁纸",
"tooltip": {
"action": "左键:打开壁纸选择器\n右键设置随机壁纸"
"action": "壁纸选择器"
}
},
"wifi": {
@@ -768,7 +770,7 @@
"wifi": "Wi-Fi"
},
"tooltip": {
"action": "点击管理 Wi-Fi 连接"
"action": "Wi-Fi"
}
}
},
@@ -794,6 +796,8 @@
},
"noctalia": {
"download-latest": "下载最新版本",
"git-commit": "Git提交:",
"git-commit-loading": "加载中...",
"installed-version": "已安装版本:",
"latest-version": "最新版本:",
"section": {
@@ -1040,15 +1044,27 @@
}
},
"templates": {
"compositors": {
"description": "合成器主题。",
"label": "合成器",
"niri": {
"description": "写入 {filepath}。需要 niri v25.11+",
"description-missing": "需要安装 {app}"
}
},
"misc": {
"description": "其他配置选项。",
"label": "杂项",
"description": "创建您自己的模板",
"label": "高级",
"user-templates": {
"description": "启用用户定义的 Matugen 配置。首次启用时将在 ~/.config/noctalia/user-templates.toml 创建模板文件",
"label": "用户模板"
"description": "仅在您知道自己在做什么时启用,请参阅我们的在线文档",
"label": "启用用户模板"
}
},
"programs": {
"cava": {
"description": "写入 {filepath}。",
"description-missing": "需要安装 {app}"
},
"code": {
"description": "写入 {filepath}。Hyprluna 主题需要手动安装和激活。",
"description-missing": "未检测到 Code 客户端。请安装 VSCode 或 VSCodium。"
@@ -1075,10 +1091,6 @@
"description": "写入 {filepath}。",
"description-missing": "需要安装 {app}"
},
"cava": {
"description": "写入 {filepath}。",
"description-missing": "需要安装 {app}"
},
"vicinae": {
"description": "写入 {filepath} 并重新加载",
"description-missing": "需要安装 {app}"
@@ -1262,9 +1274,11 @@
},
"temperature": {
"day": "白天",
"day-description": "控制白天的色温。",
"description": "设置夜间和白天的颜色温暖度。",
"label": "色温",
"night": "夜间"
"night": "夜间",
"night-description": "控制夜间的色温。"
}
},
"title": "显示"
@@ -1368,7 +1382,8 @@
"description": "编辑您的用户详细信息和头像。",
"label": "个人资料"
},
"select-avatar": "选择头像图片"
"select-avatar": "选择头像图片",
"tooltip": "浏览头像图片"
},
"screen-corners": {
"radius": {
@@ -1446,6 +1461,10 @@
"description": "使用自定义前缀启动应用程序,而不是默认方法。",
"label": "启用自定义启动前缀"
},
"grid-view": {
"description": "以网格布局而非列表显示项目。",
"label": "网格视图"
},
"position": {
"description": "选择启动器面板出现的位置。",
"label": "位置"
@@ -1470,6 +1489,20 @@
"title": "启动器"
},
"location": {
"calendar": {
"cards": {
"section": {
"description": "在日历面板中组织和启用/禁用卡片。",
"label": "日历卡"
}
},
"header": {
"label": "日历标题"
},
"month": {
"label": "日历月"
}
},
"date-time": {
"12hour-format": {
"description": "在锁屏和日历上以 12 小时制格式显示时间。状态栏时钟有其自己的设置。",
@@ -1671,7 +1704,29 @@
"label": "常规"
}
},
"title": "屏显菜单"
"title": "屏显菜单",
"types": {
"brightness": {
"description": "当屏幕亮度发生变化时显示 OSD。",
"label": "亮度"
},
"input-volume": {
"description": "当麦克风音量发生变化时显示 OSD。",
"label": "输入音量"
},
"lockkey": {
"description": "当切换大写锁定、数字锁定或滚动锁定时显示 OSD。",
"label": "锁定键"
},
"section": {
"description": "选择触发OSD的事件。如果未选择任何事件则所有可用事件都将触发OSD。",
"label": "OSD触发事件"
},
"volume": {
"description": "当音频输出音量发生变化时显示 OSD。",
"label": "输出音量"
}
}
},
"screen-recorder": {
"audio": {
@@ -1806,6 +1861,12 @@
"memory-section": {
"label": "内存使用率"
},
"network-section": {
"label": "网络"
},
"polling-interval": {
"label": "轮询间隔"
},
"temp-critical-threshold": {
"label": "严重阈值"
},
@@ -1816,7 +1877,7 @@
"label": "CPU 温度"
},
"thresholds-section": {
"description": "设置各项系统测量指标的阈值,超过该值将以高亮色进行提示。",
"description": "为每个系统指标调整警告/严重阈值和轮询间隔。",
"label": "阈值"
},
"title": "系统监视器",
@@ -2048,6 +2109,11 @@
"unavailable": "剪贴板历史记录不可用",
"unavailable-desc": "未安装 'cliphist' 应用程序。请安装它以使用剪贴板历史记录功能。"
},
"dark-mode": {
"dark-mode": "深色模式",
"enabled": "已启用",
"light-mode": "浅色模式"
},
"do-not-disturb": {
"disabled": "'勿扰模式'已禁用",
"disabled-desc": "显示所有通知。",
@@ -2128,64 +2194,70 @@
"tooltips": {
"add-widget": "添加小部件",
"bluetooth-devices": "蓝牙设备",
"brightness-at": "亮度:{brightness}%\n右键点击进入设置。\n滚动调整亮度。",
"cancel-timer": "取消计时器",
"brightness-at": "亮度:{brightness}%",
"cancel-timer": "计时器",
"clear-history": "清除历史记录",
"click-to-start-recording": "点击开始录制",
"click-to-stop-recording": "点击停止录制",
"close": "关闭",
"connect-disconnect-devices": "左键点击连接。右键点击忘记。",
"click-to-start-recording": "屏幕录制器(开始录制",
"click-to-stop-recording": "屏幕录制器(停止录制",
"close": "关闭按钮",
"connect-disconnect-devices": "蓝牙设备",
"delete-notification": "删除通知",
"disable-keep-awake": "单击以禁用保持唤醒。\n滚动以调整超时。",
"do-not-disturb-disabled": "'勿扰模式'已禁用",
"do-not-disturb-enabled": "'勿扰模式'已启用",
"enable-keep-awake": "单击以启用保持唤醒。\n滚动以调整超时。",
"disable-keep-awake": "保持唤醒",
"do-not-disturb-disabled": "勿扰模式",
"do-not-disturb-enabled": "勿扰模式",
"enable-keep-awake": "保持唤醒",
"forget-network": "忘记网络",
"grid-view": "网格视图",
"hidden-files-hide": "隐藏文件",
"hidden-files-show": "隐藏文件",
"home": "主目录",
"input-muted": "静音输入设备",
"input-muted": "麦克风",
"keep-awake": "保持唤醒",
"keyboard-layout": "{layout} 键盘布局",
"manage-vpn": "管理 VPN 连接",
"manage-wifi": "管理 Wi-Fi",
"microphone-volume-at": "麦克风音量 {volume}%\n左键点击进入设置。右键点击切换静音。\n滚动滚轮调节音量。",
"move-to-center-section": "移动到中央部分",
"move-to-left-section": "移动到左侧部分",
"move-to-right-section": "移动到右侧部分",
"next-media": "下一媒体",
"list-view": "列表视图",
"manage-vpn": "VPN 连接",
"manage-wifi": "Wi-Fi",
"microphone-volume-at": "麦克风音量:{volume}%",
"move-to-center-section": "中央部分",
"move-to-left-section": "侧部分",
"move-to-right-section": "右侧部分",
"next-media": "下一首",
"next-month": "下个月",
"night-light-disabled": "夜间模式已禁用。\n左键点击循环模式。\n右键点击访问设置。",
"night-light-enabled": "夜间模式已启用。\n左键点击循环模式。\n右键点击访问设置。",
"night-light-forced": "夜间模式已强制启用。\n左键点击循环模式。\n右键点击访问设置。",
"night-light-not-installed": "夜间模式不可用。\nwlsunset 未安装。",
"noctalia-performance-disabled": "Noctalia 性能模式已禁用。\n左键点击以启用。",
"noctalia-performance-enabled": "Noctalia 性能模式已启用。\n左键点击以禁用。",
"open-control-center": "打开控制中心",
"open-notification-history-disable-dnd": "打开通知历史记录\n右键点击禁用\"勿扰模式\"。",
"open-notification-history-enable-dnd": "打开通知历史记录\n右键点击启用\"勿扰模式\"。",
"open-settings": "打开设置",
"open-tray-dropdown": "打开系统托盘下拉菜单",
"open-wallpaper-selector": "打开壁纸选择器",
"output-muted": "静音输出设备",
"pause": "暂停",
"play": "播放",
"power-profile": "'{profile}' 电源模式",
"previous-media": "上一媒体",
"night-light-disabled": "夜间模式",
"night-light-enabled": "夜间模式",
"night-light-forced": "夜间模式",
"night-light-not-installed": "夜间模式不可用",
"noctalia-performance-disabled": "Noctalia 性能模式",
"noctalia-performance-enabled": "Noctalia 性能模式",
"open-control-center": "控制中心",
"open-notification-history-disable-dnd": "通知历史记录",
"open-notification-history-enable-dnd": "通知历史记录",
"open-settings": "设置",
"open-tray-dropdown": "系统托盘",
"open-wallpaper-selector": "壁纸选择器",
"output-muted": "音频输出",
"pause": "暂停按钮",
"play": "播放按钮",
"power-profile": "{profile} 电源模式",
"previous-media": "上一",
"previous-month": "上个月",
"refresh": "刷新",
"refresh-devices": "刷新设备",
"refresh-wallhaven": "刷新 Wallhaven 结果",
"refresh-wallpaper-list": "刷新壁纸列表",
"remove-widget": "移除小部件",
"screen-recorder-not-installed": "屏幕录制器未安装",
"screen-recorder-not-installed": "屏幕录制器未安装",
"search": "搜索",
"search-close": "关闭搜索",
"session-menu": "会话菜单",
"set-power-profile": "设置\"{profile}\"电源模式",
"start-screen-recording": "开始屏幕录制",
"stop-screen-recording": "停止屏幕录制",
"switch-to-dark-mode": "切换到深色模式",
"switch-to-light-mode": "切换到浅色模式",
"up": "上",
"volume-at": "输出音量 {volume}%\n左键点击进入设置。右键点击切换静音。\n滚动滚轮调节音量。",
"wallpaper-selector": "左键点击:打开壁纸选择器。\n右键点击设置随机壁纸。",
"set-power-profile": "{profile} 电源模式",
"start-screen-recording": "屏幕录制",
"stop-screen-recording": "屏幕录制",
"switch-to-dark-mode": "深色模式",
"switch-to-light-mode": "浅色模式",
"up": "上级目录",
"volume-at": "输出音量{volume}%",
"wallpaper-selector": "壁纸选择器",
"widget-settings": "小部件设置"
},
"wallpaper": {

View File

@@ -1,6 +1,5 @@
{
"settingsVersion": 23,
"setupCompleted": false,
"settingsVersion": 25,
"bar": {
"position": "top",
"backgroundOpacity": 1,
@@ -79,8 +78,8 @@
"allowPanelsOnScreenWithoutBar": true
},
"ui": {
"fontDefault": "Roboto",
"fontFixed": "DejaVu Sans Mono",
"fontDefault": "",
"fontFixed": "",
"fontDefaultScale": 1,
"fontFixedScale": 1,
"tooltipsEnabled": true,
@@ -100,6 +99,26 @@
"analogClockInCalendar": false,
"firstDayOfWeek": -1
},
"calendar": {
"cards": [
{
"enabled": true,
"id": "banner-card"
},
{
"enabled": true,
"id": "calendar-card"
},
{
"enabled": true,
"id": "timer-card"
},
{
"enabled": true,
"id": "weather-card"
}
]
},
"screenRecorder": {
"directory": "",
"frameRate": 60,
@@ -115,10 +134,10 @@
"enabled": true,
"overviewEnabled": false,
"directory": "",
"monitorDirectories": [],
"enableMultiMonitorDirectories": false,
"recursiveSearch": false,
"setWallpaperOnAllMonitors": true,
"defaultWallpaper": "",
"fillMode": "crop",
"fillColor": "#000000",
"randomEnabled": false,
@@ -126,7 +145,6 @@
"transitionDuration": 1500,
"transitionType": "random",
"transitionEdgeSmoothness": 0.05,
"monitors": [],
"panelPosition": "follow_bar",
"hideWallpaperFilenames": false,
"useWallhaven": false,
@@ -148,7 +166,8 @@
"sortByMostUsed": true,
"terminalCommand": "xterm -e",
"customLaunchPrefixEnabled": false,
"customLaunchPrefix": ""
"customLaunchPrefix": "",
"viewMode": "list"
},
"controlCenter": {
"position": "close_to_bar_button",
@@ -214,13 +233,18 @@
"memCriticalThreshold": 90,
"diskWarningThreshold": 80,
"diskCriticalThreshold": 90,
"cpuPollingInterval": 3000,
"tempPollingInterval": 3000,
"memPollingInterval": 3000,
"diskPollingInterval": 3000,
"networkPollingInterval": 3000,
"useCustomColors": false,
"warningColor": "",
"criticalColor": ""
},
"dock": {
"enabled": true,
"displayMode": "always_visible",
"displayMode": "auto_hide",
"backgroundOpacity": 1,
"radiusRatio": 0.1,
"floatingRatio": 1,
@@ -280,10 +304,15 @@
"osd": {
"enabled": true,
"location": "top_right",
"monitors": [],
"autoHideMs": 2000,
"overlayLayer": true,
"backgroundOpacity": 1
"backgroundOpacity": 1,
"enabledTypes": [
0,
1,
2
],
"monitors": []
},
"audio": {
"volumeStep": 5,
@@ -327,6 +356,9 @@
"code": false,
"spicetify": false,
"telegram": false,
"cava": false,
"emacs": false,
"niri": false,
"enableUserTemplates": false
},
"nightLight": {

View File

@@ -4,7 +4,7 @@
if [ "$#" -ne 1 ]; then
# Print usage information to standard error.
echo "Error: No application specified." >&2
echo "Usage: $0 {kitty|ghostty|foot|alacritty|wezterm|fuzzel|walker|pywalfox|cava}" >&2
echo "Usage: $0 {kitty|ghostty|foot|alacritty|wezterm|fuzzel|walker|pywalfox|cava|niri}" >&2
exit 1
fi
@@ -252,6 +252,29 @@ cava)
fi
;;
niri)
echo "🎨 Applying 'noctalia' theme to niri..."
CONFIG_FILE="$HOME/.config/niri/config.kdl"
INCLUDE_LINE='include "./noctalia.kdl"'
# Check if the config file exists.
if [ ! -f "$CONFIG_FILE" ]; then
echo "Config file not found, creating $CONFIG_FILE..."
mkdir -p "$(dirname "$CONFIG_FILE")"
echo "$INCLUDE_LINE" >"$CONFIG_FILE"
echo "Created new config file with noctalia theme."
else
# Check if include line already exists
if grep -qF "$INCLUDE_LINE" "$CONFIG_FILE"; then
echo "Theme already included, skipping modification."
else
# Add the include line to the end of the file
echo "$INCLUDE_LINE" >>"$CONFIG_FILE"
echo "✅ Added noctalia theme include to config."
fi
fi
;;
*)
# Handle unknown application names.
echo "Error: Unknown application '$APP_NAME'." >&2

48
CREDITS.md Normal file
View File

@@ -0,0 +1,48 @@
# Credits
Noctalia Shell is made possible by the incredible work of many open-source projects and contributors.
## Design & Branding
- **MrDowntempo** - Creator of the Noctalia Owl and moon logo
- **[SaberJ2X](https://www.reddit.com/user/SaberJ64/)** - Creator of Talia, the Noctalia mascot
## Core Framework
- **[Quickshell](https://github.com/outfoxxed/quickshell)** - The Qt/QML-based Wayland shell framework that powers Noctalia
## Runtime Dependencies
### System Integration
- **[brightnessctl](https://github.com/Hummer12007/brightnessctl)** - Screen brightness control
- **[wlsunset](https://sr.ht/~kennylevinsen/wlsunset/)** - Night light and blue light filter support
- **[wl-clipboard](https://github.com/bugaevc/wl-clipboard)** - Wayland clipboard utilities
- **[ddcutil](https://www.ddcutil.com/)** - External display brightness control
- **[power-profiles-daemon](https://gitlab.freedesktop.org/upower/power-profiles-daemon)** - Power profile management
### Media & Audio
- **[gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/)** - Hardware-accelerated screen recording
- **[Cava](https://github.com/karlstav/cava)** - Audio visualizer component
### Theming & Appearance
- **[Matugen](https://github.com/InioX/matugen)** - Material You color scheme generation from wallpapers
### Utilities
- **[cliphist](https://github.com/sentriz/cliphist)** - Clipboard history support
## Audio Assets
- **[DrNI on Freesound](https://freesound.org/people/DrNI/sounds/34562/)** - Notification sound effect
## Special Thanks
- The **Wayland** community for building the future of Linux desktop graphics
- The **Niri**, **Hyprland**, **Sway**, and **MangoWC** teams for their excellent Wayland compositors
- All the contributors and users who have helped make Noctalia better
## License
Noctalia Shell is licensed under the MIT License. See [LICENSE](LICENSE) for details.
Each dependency listed above is governed by its own respective license. Please refer to their individual projects for licensing information.

View File

@@ -49,7 +49,7 @@ Singleton {
readonly property color white: "#ffffff"
// --------------------------------
// Default colors: RosePine
// Default colors: Rose Pine
QtObject {
id: defaultColors

View File

@@ -92,6 +92,7 @@ Singleton {
"microphone": "microphone",
"microphone-mute": "microphone-off",
"volume-mute": "volume-off",
"volume-x": "volume-3",
"volume-zero": "volume-3",
"volume-low": "volume-2",
"volume-high": "volume",

View File

@@ -0,0 +1,44 @@
import QtQuick
QtObject {
id: root
// Migrate from version < 26 to version 26
// Replaces old calendar-card and banner-card with calendar-header-card and calendar-month-card
function migrate(adapter, logger) {
logger.i("Settings", "Migrating settings to v26");
// Replace old calendar-card and banner-card with calendar-header-card and calendar-month-card
if (adapter.calendar !== undefined && adapter.calendar.cards !== undefined) {
const oldCards = adapter.calendar.cards;
const newCards = [];
let anyCalendarEnabled = false;
// Check if any calendar-related card was enabled
for (var i = 0; i < oldCards.length; i++) {
const card = oldCards[i];
if ((card.id === "banner-card" || card.id === "calendar-card") && card.enabled) {
anyCalendarEnabled = true;
} else if (card.id !== "banner-card" && card.id !== "calendar-card") {
// Keep other cards as-is (timer, weather)
newCards.push(card);
}
}
// Add new split cards at the beginning (enabled if any old calendar card was enabled)
newCards.unshift({
"id": "calendar-month-card",
"enabled": anyCalendarEnabled
});
newCards.unshift({
"id": "calendar-header-card",
"enabled": anyCalendarEnabled
});
adapter.calendar.cards = newCards;
logger.i("Settings", "Replaced old calendar cards with calendar-header-card + calendar-month-card");
}
return true;
}
}

View File

@@ -0,0 +1,15 @@
pragma Singleton
import QtQuick
QtObject {
id: root
// Map of version number to migration component
readonly property var migrations: ({
26: migration26Component
})
// Migration components
property Component migration26Component: Migration26 {}
}

View File

@@ -5,33 +5,36 @@ import Quickshell
import Quickshell.Io
import "../Helpers/QtObj2JS.js" as QtObj2JS
import qs.Commons
import qs.Commons.Migrations
import qs.Modules.OSD
import qs.Services.UI
Singleton {
id: root
// Used to access via Settings.data.xxx.yyy
readonly property alias data: adapter
property bool isLoaded: false
property bool directoriesCreated: false
property int settingsVersion: 23
property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1"
property bool shouldOpenSetupWizard: false
// Define our app directories
// Default config directory: ~/.config/noctalia
// Default cache directory: ~/.cache/noctalia
property string shellName: "noctalia"
property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/" + shellName + "/"
property string cacheDirImages: cacheDir + "images/"
property string cacheDirImagesWallpapers: cacheDir + "images/wallpapers/"
property string cacheDirImagesNotifications: cacheDir + "images/notifications/"
property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json")
property string defaultLocation: "Tokyo"
property string defaultAvatar: Quickshell.env("HOME") + "/.face"
property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos"
property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
/*
Shell directories.
- Default config directory: ~/.config/noctalia
- Default cache directory: ~/.cache/noctalia
*/
readonly property alias data: adapter // Used to access via Settings.data.xxx.yyy
readonly property int settingsVersion: 26
readonly 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 + "/"
readonly property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/" + shellName + "/"
readonly property string cacheDirImages: cacheDir + "images/"
readonly property string cacheDirImagesWallpapers: cacheDir + "images/wallpapers/"
readonly property string cacheDirImagesNotifications: cacheDir + "images/notifications/"
readonly property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json")
readonly property string defaultLocation: "Tokyo"
readonly property string defaultAvatar: Quickshell.env("HOME") + "/.face"
readonly property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos"
readonly property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
// Signal emitted when settings are loaded after startupcale changes
signal settingsLoaded
@@ -62,7 +65,8 @@ Singleton {
adapter.general.avatarImage = defaultAvatar;
adapter.screenRecorder.directory = defaultVideosDirectory;
adapter.wallpaper.directory = defaultWallpapersDirectory;
adapter.wallpaper.defaultWallpaper = Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png";
adapter.ui.fontDefault = Qt.application.font.family;
adapter.ui.fontFixed = "monospace";
// Set the adapter to the settingsFileView to trigger the real settings load
settingsFileView.adapter = adapter;
@@ -73,7 +77,7 @@ Singleton {
Timer {
id: saveTimer
running: false
interval: 1000
interval: 500
onTriggered: {
root.saveImmediate();
}
@@ -97,9 +101,13 @@ Singleton {
if (!isLoaded) {
Logger.i("Settings", "Settings loaded");
// -----------------
// Run versioned migrations from MigrationRegistry
runVersionedMigrations();
upgradeSettingsData();
validateMonitorConfigurations();
isLoaded = true;
root.isLoaded = true;
// Emit the signal
root.settingsLoaded();
@@ -112,10 +120,14 @@ Singleton {
if (error.toString().includes("No such file") || error === 2) {
// File doesn't exist, create it with default values
writeAdapter();
// Also write to fallback if set
if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
settingsFallbackFileView.writeAdapter();
}
// We started without settings, we should open the setupWizard
root.shouldOpenSetupWizard = true;
}
}
}
@@ -128,17 +140,17 @@ Singleton {
printErrors: false
watchChanges: false
}
JsonAdapter {
id: adapter
property int settingsVersion: root.settingsVersion
property bool setupCompleted: false
// bar
property JsonObject bar: JsonObject {
property string position: "top" // "top", "bottom", "left", or "right"
property real backgroundOpacity: 1.0
property list<string> monitors: []
property list<string> monitors: [] // holds bar visibility per monitor
property string density: "default" // "compact", "default", "comfortable"
property bool showCapsule: true
property real capsuleOpacity: 1.0
@@ -159,7 +171,13 @@ Singleton {
widgets: JsonObject {
property list<var> left: [
{
"id": "ControlCenter"
"icon": "rocket",
"id": "CustomButton",
"leftClickExec": "qs -c noctalia-shell ipc call launcher toggle"
},
{
"id": "Clock",
"usePrimaryColor": false
},
{
"id": "SystemMonitor"
@@ -196,7 +214,7 @@ Singleton {
"id": "Brightness"
},
{
"id": "Clock"
"id": "ControlCenter"
}
]
}
@@ -226,8 +244,8 @@ Singleton {
// ui
property JsonObject ui: JsonObject {
property string fontDefault: "Roboto"
property string fontFixed: "DejaVu Sans Mono"
property string fontDefault: ""
property string fontFixed: ""
property real fontDefaultScale: 1.0
property real fontFixedScale: 1.0
property bool tooltipsEnabled: true
@@ -250,6 +268,28 @@ Singleton {
property int firstDayOfWeek: -1 // -1 = auto (use locale), 0 = Sunday, 1 = Monday, 6 = Saturday
}
// calendar
property JsonObject calendar: JsonObject {
property list<var> cards: [
{
"id": "calendar-header-card",
"enabled": true
},
{
"id": "calendar-month-card",
"enabled": true
},
{
"id": "timer-card",
"enabled": true
},
{
"id": "weather-card",
"enabled": true
}
]
}
// screen recorder
property JsonObject screenRecorder: JsonObject {
property string directory: ""
@@ -268,10 +308,10 @@ Singleton {
property bool enabled: true
property bool overviewEnabled: false
property string directory: ""
property list<var> monitorDirectories: []
property bool enableMultiMonitorDirectories: false
property bool recursiveSearch: false
property bool setWallpaperOnAllMonitors: true
property string defaultWallpaper: ""
property string fillMode: "crop"
property color fillColor: "#000000"
property bool randomEnabled: false
@@ -279,7 +319,6 @@ Singleton {
property int transitionDuration: 1500 // 1500 ms
property string transitionType: "random"
property real transitionEdgeSmoothness: 0.05
property list<var> monitors: []
property string panelPosition: "follow_bar"
property bool hideWallpaperFilenames: false
// Wallhaven settings
@@ -306,6 +345,8 @@ Singleton {
property string terminalCommand: "xterm -e"
property bool customLaunchPrefixEnabled: false
property string customLaunchPrefix: ""
// View mode: "list" or "grid"
property string viewMode: "list"
}
// control center
@@ -377,6 +418,11 @@ Singleton {
property int memCriticalThreshold: 90
property int diskWarningThreshold: 80
property int diskCriticalThreshold: 90
property int cpuPollingInterval: 3000
property int tempPollingInterval: 3000
property int memPollingInterval: 3000
property int diskPollingInterval: 3000
property int networkPollingInterval: 3000
property bool useCustomColors: false
property string warningColor: ""
property string criticalColor: ""
@@ -385,13 +431,13 @@ Singleton {
// dock
property JsonObject dock: JsonObject {
property bool enabled: true
property string displayMode: "always_visible" // "always_visible", "auto_hide", "exclusive"
property string displayMode: "auto_hide" // "always_visible", "auto_hide", "exclusive"
property real backgroundOpacity: 1.0
property real radiusRatio: 0.1
property real floatingRatio: 1.0
property real size: 1
property bool onlySameOutput: true
property list<string> monitors: []
property list<string> monitors: [] // holds dock visibility per monitor
// Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop")
property list<string> pinnedApps: []
property bool colorizeIcons: false
@@ -439,7 +485,7 @@ Singleton {
// notifications
property JsonObject notifications: JsonObject {
property bool enabled: true
property list<string> monitors: []
property list<string> monitors: [] // holds notifications visibility per monitor
property string location: "top_right"
property bool overlayLayer: true
property real backgroundOpacity: 1.0
@@ -454,10 +500,11 @@ Singleton {
property JsonObject osd: JsonObject {
property bool enabled: true
property string location: "top_right"
property list<string> monitors: []
property int autoHideMs: 2000
property bool overlayLayer: true
property real backgroundOpacity: 1.0
property list<var> enabledTypes: [OSD.Type.Volume, OSD.Type.InputVolume, OSD.Type.Brightness]
property list<string> monitors: [] // holds osd visibility per monitor
}
// audio
@@ -509,6 +556,8 @@ Singleton {
property bool spicetify: false
property bool telegram: false
property bool cava: false
property bool emacs: false
property bool niri: false
property bool enableUserTemplates: false
}
@@ -584,436 +633,42 @@ Singleton {
}
// -----------------------------------------------------
// Function to validate monitor configurations
function validateMonitorConfigurations() {
var availableScreenNames = [];
for (var i = 0; i < Quickshell.screens.length; i++) {
availableScreenNames.push(Quickshell.screens[i].name);
}
// Run versioned migrations using MigrationRegistry
function runVersionedMigrations() {
const currentVersion = adapter.settingsVersion;
const migrations = MigrationRegistry.migrations;
Logger.d("Settings", "Available monitors: [" + availableScreenNames.join(", ") + "]");
Logger.d("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]");
// Get all migration versions and sort them
const versions = Object.keys(migrations).map(v => parseInt(v)).sort((a, b) => a - b);
// Check bar monitors
if (adapter.bar.monitors.length > 0) {
var hasValidBarMonitor = false;
for (var j = 0; j < adapter.bar.monitors.length; j++) {
if (availableScreenNames.includes(adapter.bar.monitors[j])) {
hasValidBarMonitor = true;
break;
}
}
if (!hasValidBarMonitor) {
Logger.w("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens");
adapter.bar.monitors = [];
} else
// Run migrations in order for versions newer than current
for (var i = 0; i < versions.length; i++) {
const version = versions[i];
//Logger.i("Settings", "Found valid bar monitors, keeping configuration")
{}
} else
if (currentVersion < version) {
// Create migration instance and run it
const migrationComponent = migrations[version];
const migration = migrationComponent.createObject(root);
//Logger.i("Settings", "Bar monitor list is empty, will show on all available screens")
{}
}
// -----------------------------------------------------
// If the settings structure has changed, ensure
// backward compatibility by upgrading the settings
function upgradeSettingsData() {
// Wait for BarWidgetRegistry to be ready
if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) {
Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade");
Qt.callLater(upgradeSettingsData);
return;
}
const sections = ["left", "center", "right"];
// -----------------
// 1st. convert old widget id to new id
for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s];
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
var widget = adapter.bar.widgets[sectionName][i];
switch (widget.id) {
case "DarkModeToggle":
widget.id = "DarkMode";
break;
case "PowerToggle":
widget.id = "SessionMenu";
break;
case "ScreenRecorderIndicator":
widget.id = "ScreenRecorder";
break;
case "SidePanelToggle":
widget.id = "ControlCenter";
break;
}
}
}
// -----------------
// 2nd. remove any non existing widget type
var removedWidget = false;
for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s];
const widgets = adapter.bar.widgets[sectionName];
// Iterate backward through the widgets array, so it does not break when removing a widget
for (var i = widgets.length - 1; i >= 0; i--) {
var widget = widgets[i];
if (!BarWidgetRegistry.hasWidget(widget.id)) {
Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`);
widgets.splice(i, 1);
removedWidget = true;
}
}
}
// -----------------
// 3nd. upgrade widget settings
for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s];
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
var widget = adapter.bar.widgets[sectionName][i];
// Check if widget registry supports user settings, if it does not, then there is nothing to do
const reg = BarWidgetRegistry.widgetMetadata[widget.id];
if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) {
continue;
}
if (upgradeWidget(widget)) {
Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget));
}
}
}
// -----------------
// 4th. safety check
// if a widget was deleted, ensure we still have a control center
if (removedWidget) {
var gotControlCenter = false;
for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s];
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
var widget = adapter.bar.widgets[sectionName][i];
if (widget.id === "ControlCenter") {
gotControlCenter = true;
break;
if (migration && typeof migration.migrate === "function") {
const success = migration.migrate(adapter, Logger);
if (!success) {
Logger.e("Settings", "Migration to v" + version + " failed");
}
} else {
Logger.e("Settings", "Invalid migration for v" + version);
}
}
if (!gotControlCenter) {
//const obj = JSON.parse('{"id": "ControlCenter"}');
adapter.bar.widgets["right"].push(({
"id": "ControlCenter"
}));
Logger.w("Settings", "Added a ControlCenter widget to the right section");
}
}
// -----------------
// 5th. Migrate Discord templates (version 20 → 21)
// Consolidate individual discord_* properties into unified discord property
if (adapter.settingsVersion < 21) {
// Read raw JSON file to access properties not in adapter schema
try {
var rawJson = settingsFileView.text();
if (rawJson) {
var parsed = JSON.parse(rawJson);
var anyDiscordEnabled = false;
// Check if any Discord client was enabled
const discordClients = ["discord_vesktop", "discord_webcord", "discord_armcord", "discord_equibop", "discord_lightcord", "discord_dorion", "discord_vencord"];
if (parsed.templates) {
for (var i = 0; i < discordClients.length; i++) {
if (parsed.templates[discordClients[i]]) {
anyDiscordEnabled = true;
break;
}
}
}
// Set unified discord property
adapter.templates.discord = anyDiscordEnabled;
Logger.i("Settings", "Migrated Discord templates to unified 'discord' property (enabled:", anyDiscordEnabled + ")");
// Clean up migration instance
if (migration) {
migration.destroy();
}
} catch (error) {
Logger.w("Settings", "Failed to read raw JSON for Discord migration:", error);
}
}
// -----------------
// 6th. Migrate panel background opacity (version 21 → 22)
// Move appLauncher.backgroundOpacity to ui.panelBackgroundOpacity
if (adapter.settingsVersion < 22) {
// Read raw JSON file to access properties not in adapter schema
try {
var rawJson = settingsFileView.text();
if (rawJson) {
var parsed = JSON.parse(rawJson);
if (parsed.appLauncher && parsed.appLauncher.backgroundOpacity !== undefined) {
var oldOpacity = parsed.appLauncher.backgroundOpacity;
if (adapter.ui) {
adapter.ui.panelBackgroundOpacity = oldOpacity;
Logger.i("Settings", "Migrated appLauncher.backgroundOpacity to ui.panelBackgroundOpacity (value:", oldOpacity + ")");
}
}
}
} catch (error) {
Logger.w("Settings", "Failed to read raw JSON for migration:", error);
}
}
// -----------------
// 7th. Migrate dim desktop settings (version 22 → 23)
// If dimDesktop is enabled, set dimmerOpacity to 0.8 if it's not already set or is 0
// Then remove dimDesktop property as it's no longer needed
if (adapter.settingsVersion < 23) {
// Read raw JSON file to access dimDesktop property
try {
var rawJson = settingsFileView.text();
if (rawJson) {
var parsed = JSON.parse(rawJson);
if (parsed.general && parsed.general.dimDesktop === true) {
// Check if dimmerOpacity exists in raw JSON (not adapter default)
var dimmerOpacityInJson = parsed.general.dimmerOpacity;
// If dimmerOpacity wasn't explicitly set in JSON or was 0, set it to 0.8 (80% dimming)
if (dimmerOpacityInJson === undefined || dimmerOpacityInJson === 0) {
adapter.general.dimmerOpacity = 0.8;
Logger.i("Settings", "Migrated dimDesktop=true: set dimmerOpacity to 0.8 (80% dimming)");
}
}
}
} catch (error) {
Logger.w("Settings", "Failed to read raw JSON for dimDesktop migration:", error);
}
}
// -----------------
// 8th. Migrate ShellState-related files from old cache files to ShellState
// This consolidates migrations that were previously in individual service files
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
migrateShellStateFiles();
} else {
// Wait for ShellState to be ready
Qt.callLater(() => {
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
migrateShellStateFiles();
}
});
}
}
// -----------------------------------------------------
// Migrate old cache files to ShellState
function migrateShellStateFiles() {
// Migrate display.json → ShellState (CompositorService)
migrateDisplayFile();
// Migrate notifications-state.json → ShellState (NotificationService)
migrateNotificationsStateFile();
// Migrate changelog-state.json → ShellState (UpdateService)
migrateChangelogStateFile();
// Migrate color-schemes-list.json → ShellState (SchemeDownloader)
migrateColorSchemesListFile();
// Migrate wallpaper paths from Settings → ShellState (WallpaperService)
migrateWallpaperPaths();
}
function migrateDisplayFile() {
// Check if ShellState already has display data
const cached = ShellState.getDisplay();
if (cached && Object.keys(cached).length > 0) {
return; // Already migrated
}
const oldDisplayPath = cacheDir + "display.json";
const migrationFileView = Qt.createQmlObject(`
import QtQuick
import Quickshell.Io
import qs.Commons
FileView {
id: migrationView
path: "${oldDisplayPath}"
printErrors: false
adapter: JsonAdapter {
property var displays: ({})
}
onLoaded: {
if (adapter.displays && Object.keys(adapter.displays).length > 0) {
ShellState.setDisplay(adapter.displays);
Logger.i("Settings", "Migrated display.json to ShellState");
}
migrationView.destroy();
}
onLoadFailed: {
migrationView.destroy();
}
}
`, root, "displayMigrationView");
}
function migrateNotificationsStateFile() {
// Check if ShellState already has notifications state
const cached = ShellState.getNotificationsState();
if (cached && cached.lastSeenTs && cached.lastSeenTs > 0) {
return; // Already migrated
}
// Also check Settings for lastSeenTs
if (adapter.notifications && adapter.notifications.lastSeenTs) {
ShellState.setNotificationsState({
lastSeenTs: adapter.notifications.lastSeenTs
});
Logger.i("Settings", "Migrated notifications lastSeenTs from Settings to ShellState");
return;
}
const oldStatePath = cacheDir + "notifications-state.json";
const migrationFileView = Qt.createQmlObject(`
import QtQuick
import Quickshell.Io
import qs.Commons
FileView {
id: migrationView
path: "${oldStatePath}"
printErrors: false
adapter: JsonAdapter {
property real lastSeenTs: 0
}
onLoaded: {
if (adapter.lastSeenTs && adapter.lastSeenTs > 0) {
ShellState.setNotificationsState({
lastSeenTs: adapter.lastSeenTs
});
Logger.i("Settings", "Migrated notifications-state.json to ShellState");
}
migrationView.destroy();
}
onLoadFailed: {
migrationView.destroy();
}
}
`, root, "notificationsMigrationView");
}
function migrateChangelogStateFile() {
// Check if ShellState already has changelog state
const cached = ShellState.getChangelogState();
if (cached && cached.lastSeenVersion && cached.lastSeenVersion !== "") {
return; // Already migrated
}
// Also check Settings for lastSeenVersion
if (adapter.changelog && adapter.changelog.lastSeenVersion) {
ShellState.setChangelogState({
lastSeenVersion: adapter.changelog.lastSeenVersion
});
Logger.i("Settings", "Migrated changelog lastSeenVersion from Settings to ShellState");
return;
}
const oldChangelogPath = cacheDir + "changelog-state.json";
const migrationFileView = Qt.createQmlObject(`
import QtQuick
import Quickshell.Io
import qs.Commons
FileView {
id: migrationView
path: "${oldChangelogPath}"
printErrors: false
adapter: JsonAdapter {
property string lastSeenVersion: ""
}
onLoaded: {
if (adapter.lastSeenVersion && adapter.lastSeenVersion !== "") {
ShellState.setChangelogState({
lastSeenVersion: adapter.lastSeenVersion
});
Logger.i("Settings", "Migrated changelog-state.json to ShellState");
}
migrationView.destroy();
}
onLoadFailed: {
migrationView.destroy();
}
}
`, root, "changelogMigrationView");
}
function migrateColorSchemesListFile() {
// Check if ShellState already has color schemes list
const cached = ShellState.getColorSchemesList();
if (cached && cached.schemes && cached.schemes.length > 0) {
return; // Already migrated
}
const oldSchemesPath = cacheDir + "color-schemes-list.json";
const migrationFileView = Qt.createQmlObject(`
import QtQuick
import Quickshell.Io
import qs.Commons
FileView {
id: migrationView
path: "${oldSchemesPath}"
printErrors: false
adapter: JsonAdapter {
property var schemes: []
property real timestamp: 0
}
onLoaded: {
if (adapter.schemes && adapter.schemes.length > 0) {
ShellState.setColorSchemesList({
schemes: adapter.schemes,
timestamp: adapter.timestamp || 0
});
Logger.i("Settings", "Migrated color-schemes-list.json to ShellState");
}
migrationView.destroy();
}
onLoadFailed: {
migrationView.destroy();
}
}
`, root, "schemesMigrationView");
}
function migrateWallpaperPaths() {
// Check if ShellState already has wallpaper paths
const cached = ShellState.getWallpapers();
if (cached && Object.keys(cached).length > 0) {
return; // Already migrated
}
// Migrate from Settings wallpaper.monitors
var monitors = adapter.wallpaper.monitors || [];
if (monitors.length > 0) {
var wallpapers = {};
for (var i = 0; i < monitors.length; i++) {
if (monitors[i].name && monitors[i].wallpaper) {
wallpapers[monitors[i].name] = monitors[i].wallpaper;
}
}
if (Object.keys(wallpapers).length > 0) {
ShellState.setWallpapers(wallpapers);
Logger.i("Settings", "Migrated wallpaper paths from Settings to ShellState");
}
}
}
// -----------------------------------------------------
// Function to clean up deprecated user/custom bar widgets settings
function upgradeWidget(widget) {
// Backup the widget definition before altering
const widgetBefore = JSON.stringify(widget);
@@ -1047,4 +702,79 @@ Singleton {
const widgetAfter = JSON.stringify(widget);
return (widgetAfter !== widgetBefore);
}
// -----------------------------------------------------
// If the settings structure has changed, ensure
// backward compatibility by upgrading the settings
function upgradeSettingsData() {
// Wait for BarWidgetRegistry to be ready
if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) {
Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade");
Qt.callLater(upgradeSettingsData);
return;
}
const sections = ["left", "center", "right"];
// -----------------
// 1. remove any non existing widget type
var removedWidget = false;
for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s];
const widgets = adapter.bar.widgets[sectionName];
// Iterate backward through the widgets array, so it does not break when removing a widget
for (var i = widgets.length - 1; i >= 0; i--) {
var widget = widgets[i];
if (!BarWidgetRegistry.hasWidget(widget.id)) {
Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`);
widgets.splice(i, 1);
removedWidget = true;
}
}
}
// -----------------
// 2. upgrade user widget settings
for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s];
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
var widget = adapter.bar.widgets[sectionName][i];
// Check if widget registry supports user settings, if it does not, then there is nothing to do
const reg = BarWidgetRegistry.widgetMetadata[widget.id];
if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) {
continue;
}
if (upgradeWidget(widget)) {
Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget));
}
}
}
// -----------------
// 3. safety check
// if a widget was deleted, ensure we still have a control center
if (removedWidget) {
var gotControlCenter = false;
for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s];
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
var widget = adapter.bar.widgets[sectionName][i];
if (widget.id === "ControlCenter") {
gotControlCenter = true;
break;
}
}
}
if (!gotControlCenter) {
//const obj = JSON.parse('{"id": "ControlCenter"}');
adapter.bar.widgets["right"].push(({
"id": "ControlCenter"
}));
Logger.w("Settings", "Added a ControlCenter widget to the right section");
}
}
}
}

View File

@@ -3,6 +3,10 @@ pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import "../Helpers/QtObj2JS.js" as QtObj2JS
import qs.Services.Power
import qs.Services.System
import qs.Services.UI
// Centralized shell state management for small cache files
Singleton {
@@ -57,9 +61,6 @@ Singleton {
schemes: [],
timestamp: 0
})
// WallpaperService: current wallpapers per screen
property var wallpapers: ({})
}
onLoaded: {
@@ -82,7 +83,7 @@ Singleton {
// Debounced save timer
Timer {
id: saveTimer
interval: 300
interval: 500
onTriggered: performSave()
}
@@ -170,13 +171,29 @@ Singleton {
};
}
// Wallpapers (WallpaperService)
function setWallpapers(wallpapersData) {
adapter.wallpapers = wallpapersData;
save();
}
// -----------------------------------------------------
function buildStateSnapshot() {
try {
const settingsData = QtObj2JS.qtObjectToPlainObject(Settings.data);
const shellStateData = ShellState?.data ? QtObj2JS.qtObjectToPlainObject(ShellState.data) || {} : {};
function getWallpapers() {
return adapter.wallpapers || {};
return {
settings: settingsData,
state: {
doNotDisturb: NotificationService.doNotDisturb,
noctaliaPerformanceMode: PowerProfileService.noctaliaPerformanceMode,
barVisible: BarService.isVisible,
wallpapers: WallpaperService.currentWallpapers || {},
// -------------
display: shellStateData.display || {},
notificationsState: shellStateData.notificationsState || {},
changelogState: shellStateData.changelogState || {},
colorSchemesList: shellStateData.colorSchemesList || {}
}
};
} catch (error) {
Logger.e("Settings", "Failed to build state snapshot:", error);
return null;
}
}
}

View File

@@ -3,6 +3,7 @@ import QtQuick
import Quickshell
import qs.Commons
import qs.Services.System
Singleton {
id: root
@@ -15,6 +16,16 @@ Singleton {
return Math.floor(root.now / 1000);
}
// Timer state (for countdown/stopwatch)
property bool timerRunning: false
property bool timerStopwatchMode: false
property int timerRemainingSeconds: 0
property int timerTotalSeconds: 0
property int timerElapsedSeconds: 0
property bool timerSoundPlaying: false
property int timerStartTimestamp: 0 // Unix timestamp when timer was started
property int timerPausedAt: 0 // Value when paused (for resuming)
Timer {
id: updateTimer
interval: 1000
@@ -25,6 +36,20 @@ Singleton {
var newTime = new Date();
root.now = newTime;
// Update timer if running
if (root.timerRunning && root.timerStartTimestamp > 0) {
const elapsedSinceStart = root.timestamp - root.timerStartTimestamp;
if (root.timerStopwatchMode) {
root.timerElapsedSeconds = root.timerPausedAt + elapsedSinceStart;
} else {
root.timerRemainingSeconds = root.timerTotalSeconds - elapsedSinceStart;
if (root.timerRemainingSeconds <= 0) {
root.timerOnFinished();
}
}
}
// Adjust next interval to sync with the start of the next second
var msIntoSecond = newTime.getMilliseconds();
if (msIntoSecond > 100) {
@@ -119,4 +144,64 @@ Singleton {
"diff": Math.floor(diff / 86400000)
});
}
// Timer functions
function timerStart() {
if (root.timerStopwatchMode) {
root.timerRunning = true;
root.timerStartTimestamp = root.timestamp;
root.timerPausedAt = root.timerElapsedSeconds;
} else {
if (root.timerRemainingSeconds <= 0) {
return;
}
root.timerRunning = true;
root.timerTotalSeconds = root.timerRemainingSeconds;
root.timerStartTimestamp = root.timestamp;
root.timerPausedAt = 0;
}
}
function timerPause() {
if (root.timerRunning) {
// Save current state
if (root.timerStopwatchMode) {
root.timerPausedAt = root.timerElapsedSeconds;
} else {
root.timerPausedAt = root.timerRemainingSeconds;
}
}
root.timerRunning = false;
root.timerStartTimestamp = 0;
// Stop any repeating notification sound when pausing
SoundService.stopSound("alarm-beep.wav");
root.timerSoundPlaying = false;
}
function timerReset() {
root.timerRunning = false;
root.timerStartTimestamp = 0;
if (root.timerStopwatchMode) {
root.timerElapsedSeconds = 0;
root.timerPausedAt = 0;
} else {
root.timerRemainingSeconds = 0;
root.timerTotalSeconds = 0;
root.timerPausedAt = 0;
}
// Stop any repeating notification sound
SoundService.stopSound("alarm-beep.wav");
root.timerSoundPlaying = false;
}
function timerOnFinished() {
root.timerRunning = false;
root.timerRemainingSeconds = 0;
// Play notification sound with repeat at lower volume
root.timerSoundPlaying = true;
SoundService.playSound("alarm-beep.wav", {
repeat: true,
volume: 0.3
});
}
}

View File

@@ -20,8 +20,8 @@ Item {
property bool oppositeDirection: false
property bool hovered: false
property bool rotateText: false
property color customBackgroundColor: Qt.rgba(0, 0, 0, 0)
property color customTextIconColor: Qt.rgba(0, 0, 0, 0)
property color customBackgroundColor: Color.transparent
property color customTextIconColor: Color.transparent
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"

View File

@@ -20,8 +20,10 @@ Item {
property bool forceClose: false
property bool oppositeDirection: false
property bool hovered: false
property color customBackgroundColor: Qt.rgba(0, 0, 0, 0)
property color customTextIconColor: Qt.rgba(0, 0, 0, 0)
property color customBackgroundColor: Color.transparent
property color customTextIconColor: Color.transparent
readonly property bool collapseToIcon: forceClose && !forceOpen
// Effective shown state (true if hovered/animated open or forced)
readonly property bool revealed: !forceClose && (forceOpen || showPill)
@@ -44,6 +46,10 @@ Item {
readonly property int pillOverlap: Math.round(Style.capsuleHeight * 0.5)
readonly property int pillMaxWidth: Math.max(1, Math.round(textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap))
// Always prioritize hover color, then the custom one and finally the fallback color
readonly property color bgColor: hovered ? Color.mHover : (customBackgroundColor.a > 0) ? customBackgroundColor : Style.capsuleColor
readonly property color fgColor: hovered ? Color.mOnHover : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface
readonly property real iconSize: {
switch (root.density) {
case "compact":
@@ -62,7 +68,7 @@ Item {
}
}
width: pillHeight + Math.max(0, pill.width - pillOverlap)
width: collapseToIcon ? pillHeight : pillHeight + Math.max(0, pill.width - pillOverlap)
height: pillHeight
Connections {
@@ -77,17 +83,17 @@ Item {
// Unified background for the entire pill area to avoid overlapping opacity
Rectangle {
id: pillBackground
width: root.width
width: collapseToIcon ? pillHeight : root.width
height: pillHeight
radius: halfPillHeight
color: hovered ? (customBackgroundColor.a > 0 ? Qt.lighter(customBackgroundColor, 1.1) : Color.mHover) : (customBackgroundColor.a > 0 ? customBackgroundColor : Style.capsuleColor)
color: root.bgColor
anchors.verticalCenter: parent.verticalCenter
readonly property int halfPillHeight: Math.round(pillHeight * 0.5)
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
@@ -131,7 +137,7 @@ Item {
pointSize: textSize
applyUiScale: false
font.weight: Style.fontWeightBold
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : (forceOpen ? Color.mOnSurface : Color.mPrimary))
color: root.fgColor
visible: revealed
}
@@ -145,7 +151,7 @@ Item {
Behavior on opacity {
enabled: showAnim.running || hideAnim.running
NumberAnimation {
duration: Style.animationNormal
duration: Style.animationFast
easing.type: Easing.OutCubic
}
}
@@ -165,7 +171,7 @@ Item {
icon: root.icon
pointSize: iconSize
applyUiScale: false
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnSurface)
color: root.fgColor
// Center horizontally
x: (iconCircle.width - width) / 2
// Center vertically accounting for font metrics
@@ -189,7 +195,7 @@ Item {
property: "opacity"
from: 0
to: 1
duration: Style.animationNormal
duration: Style.animationFast
easing.type: Easing.OutCubic
}
onStarted: {
@@ -230,7 +236,7 @@ Item {
property: "opacity"
from: 1
to: 0
duration: Style.animationNormal
duration: Style.animationFast
easing.type: Easing.InCubic
}
onStopped: {
@@ -257,7 +263,7 @@ Item {
onEntered: {
hovered = true;
root.entered();
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong);
if (forceClose) {
return;
}
@@ -286,6 +292,8 @@ Item {
}
function show() {
if (collapseToIcon)
return;
if (!showPill) {
shouldAnimateHide = autoHide;
showAnim.start();
@@ -296,6 +304,8 @@ Item {
}
function hide() {
if (collapseToIcon)
return;
if (forceOpen) {
return;
}
@@ -306,6 +316,8 @@ Item {
}
function showDelayed() {
if (collapseToIcon)
return;
if (!showPill) {
shouldAnimateHide = autoHide;
showTimer.start();

View File

@@ -21,19 +21,10 @@ Item {
property bool oppositeDirection: false
property bool hovered: false
property bool rotateText: false
property color customBackgroundColor: Qt.rgba(0, 0, 0, 0)
property color customTextIconColor: Qt.rgba(0, 0, 0, 0)
property color customBackgroundColor: Color.transparent
property color customTextIconColor: Color.transparent
// Bar position detection for pill direction
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
// Determine pill direction based on section position
readonly property bool openDownward: oppositeDirection
readonly property bool openUpward: !oppositeDirection
// Effective shown state (true if animated open or forced, but not if force closed)
readonly property bool revealed: !forceClose && (forceOpen || showPill)
readonly property bool collapseToIcon: forceClose && !forceOpen
signal shown
signal hidden
@@ -50,11 +41,23 @@ Item {
// Sizing logic for vertical bars
readonly property int buttonSize: Style.capsuleHeight
readonly property int halfButtonSize: Math.round(buttonSize * 0.5)
readonly property int pillHeight: buttonSize
readonly property int pillOverlap: Math.round(buttonSize * 0.5)
readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + Style.marginM * 2)) : buttonSize
readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + Style.marginM * 2 + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + Style.marginM * 2))
// Determine pill direction based on section position
readonly property bool openDownward: oppositeDirection
readonly property bool openUpward: !oppositeDirection
// Effective shown state (true if animated open or forced, but not if force closed)
readonly property bool revealed: !forceClose && (forceOpen || showPill)
// Always prioritize hover color, then the custom one and finally the fallback color
readonly property color bgColor: hovered ? Color.mHover : (customBackgroundColor.a > 0) ? customBackgroundColor : Style.capsuleColor
readonly property color fgColor: hovered ? Color.mOnHover : (customTextIconColor.a > 0) ? customTextIconColor : Color.mOnSurface
readonly property real iconSize: {
switch (root.density) {
case "compact":
@@ -75,7 +78,7 @@ Item {
// For vertical bars: width is just icon size, height includes pill space
width: buttonSize
height: revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize
height: collapseToIcon ? buttonSize : (revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize)
Connections {
target: root
@@ -90,15 +93,13 @@ Item {
Rectangle {
id: pillBackground
width: buttonSize
height: revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize
height: collapseToIcon ? buttonSize : (revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize)
radius: halfButtonSize
color: hovered ? (customBackgroundColor.a > 0 ? Qt.lighter(customBackgroundColor, 1.1) : Color.mHover) : (customBackgroundColor.a > 0 ? customBackgroundColor : Style.capsuleColor)
readonly property int halfButtonSize: Math.round(buttonSize * 0.5)
color: root.bgColor
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
@@ -117,8 +118,6 @@ Item {
opacity: revealed ? Style.opacityFull : Style.opacityNone
color: Color.transparent // Make pill background transparent to avoid double opacity
readonly property int halfButtonSize: Math.round(buttonSize * 0.5)
// Radius logic for vertical expansion - rounded on the side that connects to icon
topLeftRadius: openUpward ? halfButtonSize : 0
bottomLeftRadius: openDownward ? halfButtonSize : 0
@@ -140,7 +139,7 @@ Item {
font.weight: Style.fontWeightMedium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : (forceOpen ? Color.mOnSurface : Color.mPrimary))
color: root.fgColor
visible: revealed
function getVerticalCenterOffset() {
@@ -165,7 +164,7 @@ Item {
Behavior on opacity {
enabled: showAnim.running || hideAnim.running
NumberAnimation {
duration: Style.animationNormal
duration: Style.animationFast
easing.type: Easing.OutCubic
}
}
@@ -187,7 +186,7 @@ Item {
icon: root.icon
pointSize: iconSize
applyUiScale: false
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnSurface)
color: root.fgColor
// Center horizontally
x: (iconCircle.width - width) / 2
// Center vertically accounting for font metrics
@@ -219,7 +218,7 @@ Item {
property: "opacity"
from: 0
to: 1
duration: Style.animationNormal
duration: Style.animationFast
easing.type: Easing.OutCubic
}
onStarted: {
@@ -268,7 +267,7 @@ Item {
property: "opacity"
from: 1
to: 0
duration: Style.animationNormal
duration: Style.animationFast
easing.type: Easing.InCubic
}
onStopped: {
@@ -295,7 +294,7 @@ Item {
onEntered: {
hovered = true;
root.entered();
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong);
if (forceClose) {
return;
}
@@ -324,6 +323,8 @@ Item {
}
function show() {
if (collapseToIcon)
return;
if (!showPill) {
shouldAnimateHide = autoHide;
showAnim.start();
@@ -334,6 +335,8 @@ Item {
}
function hide() {
if (collapseToIcon)
return;
if (forceOpen) {
return;
}
@@ -344,6 +347,8 @@ Item {
}
function showDelayed() {
if (collapseToIcon)
return;
if (!showPill) {
shouldAnimateHide = autoHide;
showTimer.start();

View File

@@ -447,9 +447,9 @@ Item {
if (mouse.button === Qt.RightButton) {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}

View File

@@ -150,9 +150,9 @@ Item {
if (mouse.button === Qt.RightButton) {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
} else {
const types = ["linear", "mirrored", "wave"];

View File

@@ -6,6 +6,7 @@ import Quickshell.Services.UPower
import qs.Commons
import qs.Modules.Bar.Extras
import qs.Services.Hardware
import qs.Services.Networking
import qs.Services.UI
import qs.Widgets
@@ -34,55 +35,128 @@ Item {
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode
readonly property real warningThreshold: widgetSettings.warningThreshold !== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
readonly property bool isLowBattery: !charging && percent <= warningThreshold
// Only show low battery warning if device is ready (prevents false positive during initialization)
readonly property bool isLowBattery: isReady && !charging && percent <= warningThreshold
// Test mode
readonly property bool testMode: false
readonly property int testPercent: 15
readonly property int testPercent: 35
readonly property bool testCharging: false
// Main properties
readonly property var battery: UPower.displayDevice
readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
readonly property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
readonly property string deviceNativePath: widgetSettings.deviceNativePath || ""
function findBatteryDevice(nativePath) {
if (!nativePath || !UPower.devices) {
return UPower.displayDevice;
}
var devices = UPower.devices.values || [];
for (var i = 0; i < devices.length; i++) {
var device = devices[i];
if (device && device.nativePath === nativePath && device.type !== UPowerDeviceType.LinePower && device.percentage !== undefined) {
return device;
}
}
return UPower.displayDevice;
}
function findBluetoothDevice(nativePath) {
if (!nativePath || !BluetoothService.devices) {
return null;
}
var macMatch = nativePath.match(/([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})/);
if (!macMatch) {
return null;
}
var macAddress = macMatch[1].toUpperCase();
var devices = BluetoothService.devices.values || [];
for (var i = 0; i < devices.length; i++) {
var device = devices[i];
if (device && device.address && device.address.toUpperCase() === macAddress) {
return device;
}
}
return null;
}
readonly property var battery: findBatteryDevice(deviceNativePath)
readonly property var bluetoothDevice: deviceNativePath ? findBluetoothDevice(deviceNativePath) : null
readonly property bool hasBluetoothBattery: bluetoothDevice && bluetoothDevice.batteryAvailable && bluetoothDevice.battery !== undefined
readonly property bool isBluetoothConnected: bluetoothDevice && bluetoothDevice.connected === true
property bool initializationComplete: false
Timer {
interval: 500
running: true
onTriggered: root.initializationComplete = true
}
readonly property bool isDevicePresent: {
if (testMode)
return true;
if (deviceNativePath) {
if (bluetoothDevice) {
return isBluetoothConnected;
}
if (battery && battery.nativePath === deviceNativePath) {
if (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) {
return battery.isPresent;
}
return battery.ready && battery.percentage !== undefined && (battery.percentage > 0 || battery.state === UPowerDeviceState.Charging);
}
return false;
}
if (battery) {
return (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) ? battery.isPresent : (battery.ready && battery.percentage !== undefined);
}
return false;
}
readonly property bool isReady: testMode ? true : (initializationComplete && battery && battery.ready && isDevicePresent && (battery.percentage !== undefined || hasBluetoothBattery))
readonly property real percent: testMode ? testPercent : (isReady ? (hasBluetoothBattery ? (bluetoothDevice.battery * 100) : (battery.percentage * 100)) : 0)
readonly property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
property bool hasNotifiedLowBattery: false
implicitWidth: pill.width
implicitHeight: pill.height
// Helper to evaluate and possibly notify
function maybeNotify(percent, charging) {
// Only notify once we are a below threshold
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
root.hasNotifiedLowBattery = true;
function maybeNotify(currentPercent, isCharging) {
if (!isCharging && !hasNotifiedLowBattery && currentPercent <= warningThreshold) {
hasNotifiedLowBattery = true;
ToastService.showWarning(I18n.tr("toast.battery.low"), I18n.tr("toast.battery.low-desc", {
"percent": Math.round(percent)
"percent": Math.round(currentPercent)
}));
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
// Reset when charging starts or when battery recovers 5% above threshold
root.hasNotifiedLowBattery = false;
} else if (hasNotifiedLowBattery && (isCharging || currentPercent > warningThreshold + 5)) {
hasNotifiedLowBattery = false;
}
}
// Watch for battery changes
Connections {
target: UPower.displayDevice
function onPercentageChanged() {
var currentPercent = UPower.displayDevice.percentage * 100;
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
root.maybeNotify(currentPercent, isCharging);
}
function getCurrentPercent() {
return hasBluetoothBattery ? (bluetoothDevice.battery * 100) : (battery ? battery.percentage * 100 : 0);
}
function onStateChanged() {
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
// Reset notification flag when charging starts
if (isCharging) {
root.hasNotifiedLowBattery = false;
Connections {
target: battery
function onPercentageChanged() {
if (battery) {
maybeNotify(getCurrentPercent(), battery.state === UPowerDeviceState.Charging);
}
}
function onStateChanged() {
if (battery) {
if (battery.state === UPowerDeviceState.Charging) {
hasNotifiedLowBattery = false;
}
maybeNotify(getCurrentPercent(), battery.state === UPowerDeviceState.Charging);
}
}
}
Connections {
target: bluetoothDevice
function onBatteryChanged() {
if (bluetoothDevice && hasBluetoothBattery) {
maybeNotify(bluetoothDevice.battery * 100, battery ? battery.state === UPowerDeviceState.Charging : false);
}
// Also re-evaluate maybeNotify, as state might have changed
var currentPercent = UPower.displayDevice.percentage * 100;
root.maybeNotify(currentPercent, isCharging);
}
}
@@ -119,10 +193,10 @@ Item {
text: (isReady || testMode) ? Math.round(percent) : "-"
suffix: "%"
autoHide: false
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery)
customBackgroundColor: isLowBattery ? Color.mError : Qt.rgba(0, 0, 0, 0)
customTextIconColor: isLowBattery ? Color.mOnError : charging ? Color.mPrimary : Qt.rgba(0, 0, 0, 0)
forceOpen: isReady && displayMode === "alwaysShow"
forceClose: displayMode === "alwaysHide" || (initializationComplete && !isReady)
customBackgroundColor: !initializationComplete ? Color.transparent : (charging ? Color.mPrimary : (isLowBattery ? Color.mError : Color.transparent))
customTextIconColor: !initializationComplete ? Color.transparent : (charging ? Color.mOnPrimary : (isLowBattery ? Color.mOnError : Color.transparent))
tooltipText: {
let lines = [];
@@ -130,7 +204,7 @@ Item {
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`);
return lines.join("\n");
}
if (!isReady || !battery.isLaptopBattery) {
if (!isReady || !isDevicePresent) {
return I18n.tr("battery.no-battery-detected");
}
if (battery.timeToEmpty > 0) {
@@ -173,9 +247,9 @@ Item {
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(pill, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}

View File

@@ -87,14 +87,14 @@ Item {
}
autoHide: false
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
forceClose: isBarVertical || root.displayMode === "alwaysHide" || BluetoothService.connectedDevices.length === 0
forceClose: isBarVertical || root.displayMode === "alwaysHide" || text === ""
onClicked: PanelService.getPanel("bluetoothPanel", screen)?.toggle(this)
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(pill, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
tooltipText: {

View File

@@ -47,6 +47,8 @@ Item {
function getIcon() {
var monitor = getMonitor();
var brightness = monitor ? monitor.brightness : 0;
if (brightness <= 0.001)
return "sun-off";
return brightness <= 0.5 ? "brightness-low" : "brightness-high";
}
@@ -148,9 +150,9 @@ Item {
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(pill, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}

View File

@@ -140,7 +140,7 @@ Rectangle {
}
if (action === "open-calendar") {
PanelService.getPanel("calendarPanel", screen)?.toggle(root);
PanelService.getPanel("clockPanel", screen)?.toggle(root);
} else if (action === "widget-settings") {
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
}
@@ -154,7 +154,7 @@ Rectangle {
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onEntered: {
if (!PanelService.getPanel("calendarPanel", screen)?.active) {
if (!PanelService.getPanel("clockPanel", screen)?.active) {
TooltipService.show(root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection());
}
}
@@ -166,12 +166,12 @@ Rectangle {
if (mouse.button === Qt.RightButton) {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
} else {
PanelService.getPanel("calendarPanel", screen)?.toggle(this);
PanelService.getPanel("clockPanel", screen)?.toggle(this);
}
}
}

View File

@@ -34,13 +34,51 @@ NIconButton {
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo !== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
readonly property string customIconPath: widgetSettings.customIconPath || ""
readonly property bool colorizeDistroLogo: {
if (widgetSettings.colorizeDistroLogo !== undefined)
return widgetSettings.colorizeDistroLogo;
return widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false;
readonly property bool enableColorization: widgetSettings.enableColorization || false
readonly property string colorizeSystemIcon: {
if (widgetSettings.colorizeSystemIcon !== undefined)
return widgetSettings.colorizeSystemIcon;
return widgetMetadata.colorizeSystemIcon !== undefined ? widgetMetadata.colorizeSystemIcon : "none";
}
// If we have a custom path or distro logo, don't use the theme icon.
readonly property bool isColorizing: enableColorization && colorizeSystemIcon !== "none"
readonly property color iconColor: {
if (!isColorizing)
return Color.mOnSurface;
switch (colorizeSystemIcon) {
case "primary":
return Color.mPrimary;
case "secondary":
return Color.mSecondary;
case "tertiary":
return Color.mTertiary;
case "error":
return Color.mError;
default:
return Color.mOnSurface;
}
}
readonly property color iconHoverColor: {
if (!isColorizing)
return Color.mOnHover;
switch (colorizeSystemIcon) {
case "primary":
return Qt.darker(Color.mPrimary, 1.2);
case "secondary":
return Qt.darker(Color.mSecondary, 1.2);
case "tertiary":
return Qt.darker(Color.mTertiary, 1.2);
case "error":
return Qt.darker(Color.mError, 1.2);
default:
return Color.mOnHover;
}
}
// 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")
tooltipDirection: BarService.getTooltipDirection()
@@ -48,8 +86,9 @@ NIconButton {
applyUiScale: false
density: Settings.data.bar.density
colorBg: Style.capsuleColor
colorFg: Color.mOnSurface
colorFg: iconColor
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mHover
colorFgHover: iconHoverColor
colorBorder: Color.transparent
colorBorderHover: useDistroLogo ? Color.mHover : Color.transparent
@@ -102,9 +141,9 @@ NIconButton {
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
onMiddleClicked: PanelService.getPanel("launcherPanel", screen)?.toggle()
@@ -115,18 +154,18 @@ NIconButton {
width: root.width * 0.8
height: width
source: {
if (customIconPath !== "")
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath;
if (useDistroLogo)
return HostService.osLogo;
if (customIconPath !== "")
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath;
return "";
}
visible: source !== ""
smooth: true
asynchronous: true
layer.enabled: useDistroLogo && colorizeDistroLogo
layer.enabled: isColorizing && (useDistroLogo || customIconPath !== "")
layer.effect: ShaderEffect {
property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant
property color targetColor: isColorizing ? iconColor : (Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant)
property real colorizeMode: 2.0
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")

View File

@@ -15,7 +15,7 @@ NIconButton {
baseSize: Style.capsuleHeight
applyUiScale: false
colorBg: Style.capsuleColor
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
onClicked: Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode

View File

@@ -82,9 +82,9 @@ Item {
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(pill, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}

View File

@@ -80,9 +80,9 @@ Rectangle {
if (mouse.button === Qt.RightButton) {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}

View File

@@ -44,6 +44,10 @@ Item {
readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode
readonly property bool showProgressRing: (widgetSettings.showProgressRing !== undefined) ? widgetSettings.showProgressRing : widgetMetadata.showProgressRing
// Private constants for element sizes
readonly property int _iconOnlySize: Math.round(18 * scaling)
readonly property int _artAndProgressSize: Math.round(21 * scaling)
// Maximum widget width with user settings support
readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0)
readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : widgetMetadata.useFixedWidth
@@ -68,7 +72,7 @@ Item {
// Hide conditions
readonly property bool shouldHideIdle: ((hideMode === "idle") || hideWhenIdle) && !MediaService.isPlaying
readonly property bool isEmptyForHideMode: (!hasActivePlayer) && (hideMode === "hidden" || hideMode === "transparent")
readonly property bool isEmptyForHideMode: (!hasActivePlayer) && (hideMode === "hidden")
implicitHeight: visible ? (isVerticalBar ? ((shouldHideIdle || isEmptyForHideMode) ? 0 : calculatedVerticalDimension()) : Style.capsuleHeight) : 0
implicitWidth: visible ? (isVerticalBar ? ((shouldHideIdle || isEmptyForHideMode) ? 0 : calculatedVerticalDimension()) : ((shouldHideIdle || isEmptyForHideMode) ? 0 : dynamicWidth)) : 0
@@ -111,15 +115,17 @@ Item {
function calculateContentWidth() {
// Calculate the actual content width based on visible elements
var contentWidth = 0;
var margins = Style.marginS * scaling * 2; // Left and right margins
// Icon or album art width
if (!hasActivePlayer || !showAlbumArt) {
// Icon width
contentWidth += Math.round(18 * scaling);
// Icon, progress ring, or album art width
if (!hasActivePlayer || (!showAlbumArt && !showProgressRing)) {
// Icon width only
contentWidth += _iconOnlySize;
} else if (showProgressRing && hasActivePlayer) {
// Progress ring width (same as album art width to maintain consistent sizing)
contentWidth += _artAndProgressSize;
} else if (showAlbumArt && hasActivePlayer) {
// Album art width
contentWidth += 21 * scaling;
contentWidth += _artAndProgressSize;
}
// Spacing between icon/art and text; only if there is text
@@ -133,25 +139,26 @@ Item {
contentWidth += Style.marginXXS * 2;
}
// Add container margins
contentWidth += margins;
return Math.ceil(contentWidth);
}
// Dynamic width: adapt to content but respect maximum width setting
readonly property real dynamicWidth: {
var contentWidth = calculateContentWidth();
// For vertical bars, there are no horizontal margins to add
var margins = isVerticalBar ? 0 : (Style.marginS * scaling * 2);
var totalWidth = contentWidth + margins;
// If using fixed width mode, always use maxWidth
if (useFixedWidth) {
return maxWidth;
}
// Otherwise, adapt to content
// If there's no active player, the widget should be compact
if (!hasActivePlayer) {
// Keep compact when no active player
return calculateContentWidth();
return totalWidth;
}
// Use content width but don't exceed user-set maximum width
return Math.min(calculateContentWidth(), maxWidth);
// Adapt to content but don't exceed user-set maximum width
return Math.min(totalWidth, maxWidth);
}
// A hidden text element to safely measure the full title width
@@ -309,33 +316,36 @@ Item {
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
pointSize: Style.fontSizeL * scaling
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignVCenter
visible: !hasActivePlayer || (!showAlbumArt && !trackArt.visible)
Layout.preferredWidth: _iconOnlySize
Layout.preferredHeight: _iconOnlySize
visible: !hasActivePlayer || (!showAlbumArt && !showProgressRing)
}
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
visible: showAlbumArt && hasActivePlayer
spacing: 0
// Progress circle (independent of album art)
Item {
Layout.preferredWidth: Math.round(21 * scaling)
Layout.preferredHeight: Math.round(21 * scaling)
Layout.preferredWidth: (hasActivePlayer && (showProgressRing || showAlbumArt)) ? _artAndProgressSize : 0
Layout.preferredHeight: (hasActivePlayer && (showProgressRing || showAlbumArt)) ? _artAndProgressSize : 0
Layout.minimumWidth: (hasActivePlayer && showProgressRing) ? _artAndProgressSize : 0
Layout.minimumHeight: (hasActivePlayer && showProgressRing) ? _artAndProgressSize : 0
Layout.maximumWidth: (hasActivePlayer && (showProgressRing || showAlbumArt)) ? _artAndProgressSize : 0
Layout.maximumHeight: (hasActivePlayer && (showProgressRing || showAlbumArt)) ? _artAndProgressSize : 0
Layout.fillWidth: false
Layout.fillHeight: false
visible: hasActivePlayer && (showProgressRing || showAlbumArt) // Show container when there's active player and either feature is enabled
// Background for progress circle
Rectangle {
anchors.fill: parent
radius: width / 2
color: Color.transparent
}
// Progress circle
// Progress circle - always available when showProgressRing is true
Canvas {
id: progressCanvas
anchors.fill: parent
anchors.margins: 0 // Align exactly with parent to avoid clipping
visible: showProgressRing // Control visibility with setting
z: 0 // Behind the album art
visible: hasActivePlayer && showProgressRing // Only show when progress ring is enabled
z: 0 // Behind the album art or icon
// Calculate progress ratio: 0 to 1
property real progressRatio: {
@@ -351,23 +361,28 @@ Item {
onPaint: {
var ctx = getContext("2d");
// Check if width/height are valid before calculating radius
if (width <= 0 || height <= 0) {
return; // Skip drawing if dimensions are invalid
}
var centerX = width / 2;
var centerY = height / 2;
var radius = Math.min(width, height) / 2 - (1.25 * scaling); // Larger radius, accounting for line width to approach edge
var radius = Math.max(0, Math.min(width, height) / 2 - (1.25 * scaling)); // Larger radius, accounting for line width to approach edge
ctx.reset();
// Background circle (full track, not played yet)
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.lineWidth = 3 * scaling; // Thicker line width based on scaling property
ctx.lineWidth = 2.5 * scaling; // Thicker line width based on scaling property
ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.4); // More opaque for better visibility
ctx.stroke();
// Progress arc (played portion)
ctx.beginPath();
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI);
ctx.lineWidth = 3 * scaling; // Thicker line width based on scaling property
ctx.lineWidth = 2.5 * scaling; // Thicker line width based on scaling property
ctx.strokeStyle = Color.mPrimary; // Use primary color for progress
ctx.lineCap = "round";
ctx.stroke();
@@ -385,16 +400,45 @@ Item {
}
}
NImageCircled {
id: trackArt
// Property to track mPrimary color changes and trigger repaint
Item {
id: colorTrackerHorizontal
property color currentColor: Color.mPrimary
onCurrentColorChanged: progressCanvas.requestPaint()
}
// Album art or icon - only show album art when enabled and player is active
Item {
anchors.fill: parent
anchors.margins: showProgressRing ? (2.5 * scaling) : 0.5 // Make album art smaller only when progress ring is visible, scaled with widget
imagePath: MediaService.trackArtUrl
fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play"
fallbackIconSize: 10
borderWidth: 0
border.color: Color.transparent
z: 1 // In front of the progress circle
anchors.margins: showProgressRing ? (3 * scaling) : 0.5 // Adjusted to align with progress circle better
NImageRounded {
id: trackArt
anchors.fill: parent
anchors.margins: showProgressRing ? 0 : -1 * scaling // Add negative margin to make album art larger when no progress ring
radius: width * 0.5
visible: showAlbumArt && hasActivePlayer
imagePath: MediaService.trackArtUrl
fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play"
fallbackIconSize: showProgressRing ? 10 : 12 // Larger fallback icon when no progress ring
borderWidth: 0
borderColor: Color.transparent
z: 1 // In front of the progress circle
}
// Fallback icon when no album art or album art not shown
NIcon {
anchors.centerIn: parent
width: parent.width
height: parent.height
icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc"
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
pointSize: (showAlbumArt || showProgressRing) ? 8 * scaling : 12 * scaling // Smaller when inside album art circle or progress ring, larger when alone
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
visible: (!showAlbumArt && hasActivePlayer) && showProgressRing
z: 1 // In front of the progress circle
}
}
}
}
@@ -403,10 +447,10 @@ Item {
id: titleContainer
Layout.preferredWidth: {
// Calculate available width based on other elements in the row
var iconWidth = (windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0);
var albumArtWidth = (hasActivePlayer && showAlbumArt ? (21 * scaling + Style.marginS * scaling) : 0);
var iconWidth = (windowIcon.visible ? (_iconOnlySize + Style.marginS * scaling) : 0);
var artWidth = (hasActivePlayer && (showAlbumArt || showProgressRing) ? (_artAndProgressSize + Style.marginS * scaling) : 0);
var totalMargins = Style.marginXXS * 2;
var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins;
var availableWidth = mainContainer.width - iconWidth - artWidth - totalMargins;
return Math.max(20, availableWidth);
}
Layout.maximumWidth: Layout.preferredWidth
@@ -572,55 +616,67 @@ Item {
onPaint: {
var ctx = getContext("2d");
// Check if width/height are valid before calculating radius
if (width <= 0 || height <= 0) {
return; // Skip drawing if dimensions are invalid
}
var centerX = width / 2;
var centerY = height / 2;
// Align with mediaMini radius which is circular in vertical mode
var radius = Math.min(width, height) / 2 - 4; // Position ring near the outer edge of background
var radius = Math.max(0, Math.min(width, height) / 2 - 4); // Position ring near the outer edge of background
ctx.reset();
// Background circle (full track, not played yet)
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.lineWidth = 1.5 * scaling; // Line width based on scaling property, thinner for vertical layout
ctx.lineWidth = 2.5 * scaling; // Line width based on scaling property, thinner for vertical layout
ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.4); // More opaque for better visibility
ctx.stroke();
// Progress arc (played portion)
ctx.beginPath();
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI);
ctx.lineWidth = 1.5 * scaling; // Line width based on scaling property, thinner for vertical layout
ctx.lineWidth = 2.5 * scaling; // Line width based on scaling property, thinner for vertical layout
ctx.strokeStyle = Color.mPrimary; // Use primary color for progress
ctx.lineCap = "round";
ctx.stroke();
}
}
// Vertical layout for left/right bars - icon only
// Vertical layout for left/right bars - icon or album art
Item {
id: verticalLayout
anchors.centerIn: parent
width: parent.width - Style.marginM * 2
height: parent.height - Style.marginM * 2
width: showProgressRing ? (Style.baseWidgetSize * 0.5 * scaling) : (calculatedVerticalDimension() - 4 * scaling)
height: width
visible: isVerticalBar
z: 1 // Above the visualizer
z: 1 // Above the visualizer and progress ring
// Media icon
Item {
width: Style.baseWidgetSize * 0.5
height: width
// Album Art
NImageRounded {
anchors.fill: parent
visible: showAlbumArt && hasActivePlayer
radius: width * 0.5
imagePath: MediaService.trackArtUrl
fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play"
fallbackIconSize: 12
borderWidth: 0
}
// Media icon (fallback)
NIcon {
id: mediaIconVertical
anchors.centerIn: parent
NIcon {
id: mediaIconVertical
anchors.fill: parent
icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc"
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
pointSize: Style.fontSizeL * scaling
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
z: 1 // In front of the progress circle
}
width: parent.width
height: parent.height
visible: !showAlbumArt || !hasActivePlayer
icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc"
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
pointSize: Style.fontSizeM * scaling
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
@@ -635,6 +691,13 @@ Item {
}
}
// Property to track mPrimary color changes and trigger repaint for vertical canvas
Item {
id: colorTrackerVertical
property color currentColor: Color.mPrimary
onCurrentColorChanged: progressCanvasVertical.requestPaint()
}
// Mouse area for hover detection
MouseArea {
id: mouseArea
@@ -652,9 +715,9 @@ Item {
TooltipService.hide();
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(mediaMini, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(mediaMini, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
} else if (mouse.button === Qt.MiddleButton) {
if (hasActivePlayer && MediaService.canGoPrevious) {

View File

@@ -169,9 +169,9 @@ Item {
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(pill, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
onMiddleClicked: root.openExternalMixer()

View File

@@ -9,7 +9,7 @@ import qs.Services.System
import qs.Services.UI
import qs.Widgets
NIconButton {
Item {
id: root
property ShellScreen screen
@@ -33,6 +33,9 @@ NIconButton {
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
implicitWidth: pill.width
implicitHeight: pill.height
function computeUnreadCount() {
var since = NotificationService.lastSeenTs;
var count = 0;
@@ -46,17 +49,6 @@ NIconButton {
return count;
}
baseSize: Style.capsuleHeight
applyUiScale: false
density: Settings.data.bar.density
icon: NotificationService.doNotDisturb ? "bell-off" : "bell"
tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.open-notification-history-disable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd")
tooltipDirection: BarService.getTooltipDirection()
colorBg: Style.capsuleColor
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
NPopupContextMenu {
id: contextMenu
@@ -94,37 +86,79 @@ NIconButton {
}
}
onClicked: {
var panel = PanelService.getPanel("notificationHistoryPanel", screen);
panel?.toggle(this);
}
BarPill {
id: pill
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
property string currentNotif
Connections {
target: NotificationService.activeList
function onCountChanged() {
// keep current text a bit longer for the animation
if (NotificationService.activeList.count > 0) {
var notif = NotificationService.activeList.get(0)
var summary = notif.summary.trim()
var body = notif.body.trim()
pill.currentNotif = `${summary}: ${body}`.replace(/\n/g, " ")
}
}
}
}
Loader {
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 2
anchors.topMargin: 1
z: 2
active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0)
sourceComponent: Rectangle {
id: badge
readonly property int count: computeUnreadCount()
height: 8
width: height
radius: height / 2
color: Color.mError
border.color: Color.mSurface
border.width: Style.borderS
visible: count > 0 || !hideWhenZero
Component.onCompleted: {
function dismiss(notificationId) {
if (Settings.data.notifications?.location == "bar") {
NotificationService.dismissActiveNotification(notificationId)
}
}
NotificationService.animateAndRemove.connect(dismiss);
}
screen: root.screen
density: Settings.data.bar.density
oppositeDirection: BarService.getPillDirection(root)
icon: NotificationService.doNotDisturb ? "bell-off" : "bell"
tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.open-notification-history-disable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd")
text: currentNotif
forceOpen: Settings.data.notifications?.location == "bar" && NotificationService.activeList.count > 0
// prevent open via mouse over
forceClose: NotificationService.activeList.count == 0
opacity: NotificationService.doNotDisturb || computeUnreadCount() > 0 ? 100 : 0
onClicked: {
var panel = PanelService.getPanel("notificationHistoryPanel", screen);
panel?.toggle(this);
}
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
}
}
Loader {
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 2
anchors.topMargin: 1
z: 2
active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0)
sourceComponent: Rectangle {
id: badge
readonly property int count: computeUnreadCount()
height: 8
width: height
radius: height / 2
color: Color.mError
border.color: Color.mSurface
border.width: Style.borderS
visible: count > 0 || !hideWhenZero
}
}
}
}

View File

@@ -85,9 +85,9 @@ NIconButton {
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}

View File

@@ -143,9 +143,9 @@ Rectangle {
if (mouse.button === Qt.RightButton) {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}

View File

@@ -20,7 +20,8 @@ Rectangle {
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
readonly property string density: Settings.data.bar.density
readonly property real itemSize: (density === "compact") ? Style.capsuleHeight * 0.9 : Style.capsuleHeight * 0.8
@@ -43,22 +44,26 @@ Rectangle {
// Context menu state
property var selectedWindow: null
property string selectedAppName: ""
property int modelUpdateTrigger: 0 // Dummy property to force model re-evaluation
NPopupContextMenu {
id: contextMenu
model: {
// Reference modelUpdateTrigger to make binding reactive
const _ = root.modelUpdateTrigger;
var items = [];
if (selectedWindow) {
if (root.selectedWindow) {
items.push({
"label": I18n.tr("context-menu.activate-app", {
"app": selectedAppName
"app": root.selectedAppName
}),
"action": "activate",
"icon": "focus"
});
items.push({
"label": I18n.tr("context-menu.close-app", {
"app": selectedAppName
"app": root.selectedAppName
}),
"action": "close",
"icon": "x"
@@ -160,7 +165,7 @@ Rectangle {
required property var modelData
property ShellScreen screen: root.screen
visible: (!onlySameOutput || modelData.output == screen.name) && (!onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(function (ws) {
visible: (!onlySameOutput || modelData.output === screen?.name) && (!onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(function (ws) {
return ws.id;
}).includes(modelData.workspaceId))
@@ -203,6 +208,7 @@ Rectangle {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
preventStealing: true
onPressed: function (mouse) {
if (!taskbarItem.modelData)
@@ -213,16 +219,25 @@ Rectangle {
} catch (error) {
Logger.e("Taskbar", "Failed to activate toplevel: " + error);
}
} else if (mouse.button === Qt.RightButton) {
}
}
onReleased: function (mouse) {
if (!taskbarItem.modelData)
return;
if (mouse.button === Qt.RightButton) {
mouse.accepted = true;
TooltipService.hide();
root.selectedWindow = taskbarItem.modelData;
root.selectedAppName = CompositorService.getCleanAppName(taskbarItem.modelData.appId, taskbarItem.modelData.title);
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
const pos = BarService.getContextMenuPosition(taskbarItem, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(taskbarItem, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
// Store position and size for timer callback
const globalPos = taskbarItem.mapToItem(root, 0, 0);
contextMenuOpenTimer.globalX = globalPos.x;
contextMenuOpenTimer.globalY = globalPos.y;
contextMenuOpenTimer.itemWidth = taskbarItem.width;
contextMenuOpenTimer.itemHeight = taskbarItem.height;
contextMenuOpenTimer.restart();
}
}
onEntered: TooltipService.show(taskbarItem, taskbarItem.modelData.title || taskbarItem.modelData.appId || "Unknown app.", BarService.getTooltipDirection())
@@ -231,4 +246,69 @@ Rectangle {
}
}
}
Timer {
id: contextMenuOpenTimer
interval: 10
repeat: false
property real globalX: 0
property real globalY: 0
property real itemWidth: 0
property real itemHeight: 0
onTriggered: {
// Directly build and set model as a new array (bypass binding issues)
var items = [];
if (root.selectedWindow) {
items.push({
"label": I18n.tr("context-menu.activate-app", {
"app": root.selectedAppName
}),
"action": "activate",
"icon": "focus"
});
items.push({
"label": I18n.tr("context-menu.close-app", {
"app": root.selectedAppName
}),
"action": "close",
"icon": "x"
});
}
items.push({
"label": I18n.tr("context-menu.widget-settings"),
"action": "widget-settings",
"icon": "settings"
});
// Set the model directly
contextMenu.model = items;
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.open();
// Calculate menu position
let menuX, menuY;
if (root.barPosition === "top") {
menuX = globalX + (itemWidth / 2) - (contextMenu.implicitWidth / 2);
menuY = Style.barHeight + Style.marginS;
} else if (root.barPosition === "bottom") {
const menuHeight = 12 + contextMenu.model.length * contextMenu.itemHeight;
menuX = globalX + (itemWidth / 2) - (contextMenu.implicitWidth / 2);
menuY = -menuHeight - Style.marginS;
} else if (root.barPosition === "left") {
menuX = Style.barHeight + Style.marginS;
menuY = globalY + (itemHeight / 2) - (contextMenu.implicitHeight / 2);
} else {
// right
menuX = -contextMenu.implicitWidth - Style.marginS;
menuY = globalY + (itemHeight / 2) - (contextMenu.implicitHeight / 2);
}
contextMenu.openAtItem(root, menuX, menuY);
popupMenuWindow.contentItem = contextMenu;
}
}
}
}

View File

@@ -21,7 +21,8 @@ Item {
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
readonly property string density: Settings.data.bar.density
readonly property real itemSize: (density === "compact") ? Style.capsuleHeight * 0.9 : Style.capsuleHeight * 0.8
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
@@ -53,6 +54,7 @@ Item {
// Context menu state
property var selectedWindow: null
property string selectedAppName: ""
property int modelUpdateTrigger: 0 // Dummy property to force model re-evaluation
function refreshWorkspaces() {
localWorkspaces.clear();
@@ -74,6 +76,7 @@ Item {
localWorkspaces.append(workspaceData);
}
updateWorkspaceFocus();
}
@@ -167,18 +170,21 @@ Item {
id: contextMenu
model: {
// Reference modelUpdateTrigger to make binding reactive
const _ = root.modelUpdateTrigger;
var items = [];
if (selectedWindow) {
if (root.selectedWindow) {
items.push({
"label": I18n.tr("context-menu.activate-app", {
"app": selectedAppName
"app": root.selectedAppName
}),
"action": "activate",
"icon": "focus"
});
items.push({
"label": I18n.tr("context-menu.close-app", {
"app": selectedAppName
"app": root.selectedAppName
}),
"action": "close",
"icon": "x"
@@ -273,21 +279,28 @@ Item {
enabled: !hasWindows
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
preventStealing: true
onPressed: mouse => {
if (mouse.button === Qt.LeftButton) {
CompositorService.switchToWorkspace(workspaceModel);
} else if (mouse.button === Qt.RightButton) {
TooltipService.hide();
root.selectedWindow = "";
root.selectedAppName = "";
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
const pos = BarService.getContextMenuPosition(container, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(container, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}
onReleased: mouse => {
if (mouse.button === Qt.RightButton) {
mouse.accepted = true;
TooltipService.hide();
root.selectedWindow = "";
root.selectedAppName = "";
// Store position and size for timer callback
const globalPos = container.mapToItem(root, 0, 0);
contextMenuOpenTimer1.globalX = globalPos.x;
contextMenuOpenTimer1.globalY = globalPos.y;
contextMenuOpenTimer1.itemWidth = container.width;
contextMenuOpenTimer1.itemHeight = container.height;
contextMenuOpenTimer1.restart();
}
}
}
Flow {
@@ -359,6 +372,7 @@ Item {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
preventStealing: true
onPressed: mouse => {
if (!model) {
@@ -367,18 +381,29 @@ Item {
if (mouse.button === Qt.LeftButton) {
CompositorService.focusWindow(model);
} else if (mouse.button === Qt.RightButton) {
TooltipService.hide();
root.selectedWindow = model;
root.selectedAppName = CompositorService.getCleanAppName(model.appId, model.title);
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
const pos = BarService.getContextMenuPosition(taskbarItem, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(taskbarItem, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}
onReleased: mouse => {
if (!model) {
return;
}
if (mouse.button === Qt.RightButton) {
mouse.accepted = true;
TooltipService.hide();
root.selectedWindow = model;
root.selectedAppName = CompositorService.getCleanAppName(model.appId, model.title);
// Store position and size for timer callback
const globalPos = taskbarItem.mapToItem(root, 0, 0);
contextMenuOpenTimer2.globalX = globalPos.x;
contextMenuOpenTimer2.globalY = globalPos.y;
contextMenuOpenTimer2.itemWidth = taskbarItem.width;
contextMenuOpenTimer2.itemHeight = taskbarItem.height;
contextMenuOpenTimer2.restart();
}
}
onEntered: {
taskbarItem.itemHovered = true;
TooltipService.show(taskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection());
@@ -533,4 +558,82 @@ Item {
delegate: workspaceRepeaterDelegate
}
}
Timer {
id: contextMenuOpenTimer1
interval: 10
repeat: false
property real globalX: 0
property real globalY: 0
property real itemWidth: 0
property real itemHeight: 0
onTriggered: openContextMenu(globalX, globalY, itemWidth, itemHeight)
}
Timer {
id: contextMenuOpenTimer2
interval: 10
repeat: false
property real globalX: 0
property real globalY: 0
property real itemWidth: 0
property real itemHeight: 0
onTriggered: openContextMenu(globalX, globalY, itemWidth, itemHeight)
}
// --------------------------------------------------
function openContextMenu(globalX, globalY, itemWidth, itemHeight) {
// Directly build and set model as a new array (bypass binding issues)
var items = [];
if (root.selectedWindow) {
items.push({
"label": I18n.tr("context-menu.activate-app", {
"app": root.selectedAppName
}),
"action": "activate",
"icon": "focus"
});
items.push({
"label": I18n.tr("context-menu.close-app", {
"app": root.selectedAppName
}),
"action": "close",
"icon": "x"
});
}
items.push({
"label": I18n.tr("context-menu.widget-settings"),
"action": "widget-settings",
"icon": "settings"
});
// Set the model directly
contextMenu.model = items;
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.open();
// Calculate menu position
let menuX, menuY;
if (root.barPosition === "top") {
menuX = globalX + (itemWidth / 2) - (contextMenu.implicitWidth / 2);
menuY = Style.barHeight + Style.marginS;
} else if (root.barPosition === "bottom") {
const menuHeight = 12 + contextMenu.model.length * contextMenu.itemHeight;
menuX = globalX + (itemWidth / 2) - (contextMenu.implicitWidth / 2);
menuY = -menuHeight - Style.marginS;
} else if (root.barPosition === "left") {
menuX = Style.barHeight + Style.marginS;
menuY = globalY + (itemHeight / 2) - (contextMenu.implicitHeight / 2);
} else {
// right
menuX = -contextMenu.implicitWidth - Style.marginS;
menuY = globalY + (itemHeight / 2) - (contextMenu.implicitHeight / 2);
}
contextMenu.openAtItem(root, menuX, menuY);
popupMenuWindow.contentItem = contextMenu;
}
}
}

View File

@@ -161,10 +161,14 @@ Rectangle {
}
//Logger.d("Tray", "wildCardMatch - Input str:", str, "rule:", rule)
// Escape all special regex characters in the rule
let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Convert '*' to '.*' for wildcard matching
let pattern = escapedRule.replace(/\\\*/g, '.*');
// First, convert '*' to a placeholder to preserve it, then escape other special regex characters
// Use a unique placeholder that won't appear in normal strings
const placeholder = '\uE000'; // Private use character
let processedRule = rule.replace(/\*/g, placeholder);
// Escape all special regex characters (but placeholder won't match this)
let escapedRule = processedRule.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
// Convert placeholder back to '.*' for wildcard matching
let pattern = escapedRule.replace(new RegExp(placeholder, 'g'), '.*');
// Add ^ and $ to match the entire string
pattern = '^' + pattern + '$';

View File

@@ -124,9 +124,9 @@ Item {
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(pill, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
tooltipText: {

View File

@@ -153,12 +153,9 @@ Item {
// Get the shared popup menu window for this screen
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
// Calculate position using centralized helper (with center-based positioning)
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
// Show the context menu inside the popup window
contextMenu.openAtItem(pill, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(pill, pos.x, pos.y);
}
}
onMiddleClicked: root.openExternalMixer()

View File

@@ -57,9 +57,9 @@ NIconButton {
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(root, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(root, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}

View File

@@ -109,14 +109,14 @@ Item {
}
autoHide: false
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
forceClose: isBarVertical || root.displayMode === "alwaysHide" || !pill.text
forceClose: isBarVertical || root.displayMode === "alwaysHide" || text === ""
onClicked: PanelService.getPanel("wifiPanel", screen)?.toggle(this)
onRightClicked: {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(pill, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
tooltipText: {

View File

@@ -272,9 +272,9 @@ Item {
if (mouse.button === Qt.RightButton) {
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
if (popupMenuWindow) {
popupMenuWindow.showContextMenu(contextMenu);
const pos = BarService.getContextMenuPosition(workspaceBackground, contextMenu.implicitWidth, contextMenu.implicitHeight);
contextMenu.openAtItem(workspaceBackground, pos.x, pos.y);
popupMenuWindow.showContextMenu(contextMenu);
}
}
}

View File

@@ -11,28 +11,80 @@ NBox {
property real localOutputVolume: 0
property bool localOutputVolumeChanging: false
property int lastSinkId: -1
property real localInputVolume: 0
property bool localInputVolumeChanging: false
property int lastSourceId: -1
Component.onCompleted: {
var vol = AudioService.volume;
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
var inputVol = AudioService.inputVolume;
localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0;
if (AudioService.sink) {
lastSinkId = AudioService.sink.id;
}
if (AudioService.source) {
lastSourceId = AudioService.source.id;
}
}
// Reset local volume when device changes - use current device's volume
Connections {
target: AudioService
function onSinkChanged() {
if (AudioService.sink) {
const newSinkId = AudioService.sink.id;
if (newSinkId !== lastSinkId) {
lastSinkId = newSinkId;
// Immediately set local volume to current device's volume
var vol = AudioService.volume;
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
}
} else {
lastSinkId = -1;
localOutputVolume = 0;
}
}
}
Connections {
target: AudioService
function onSourceChanged() {
if (AudioService.source) {
const newSourceId = AudioService.source.id;
if (newSourceId !== lastSourceId) {
lastSourceId = newSourceId;
// Immediately set local volume to current device's volume
var vol = AudioService.inputVolume;
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
}
} else {
lastSourceId = -1;
localInputVolume = 0;
}
}
}
// Timer to debounce volume changes
// Only sync if the device hasn't changed (check by comparing IDs)
Timer {
interval: 100
running: true
repeat: true
onTriggered: {
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
AudioService.setVolume(localOutputVolume);
// Only sync if sink hasn't changed
if (AudioService.sink && AudioService.sink.id === lastSinkId) {
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
AudioService.setVolume(localOutputVolume);
}
}
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
AudioService.setInputVolume(localInputVolume);
// Only sync if source hasn't changed
if (AudioService.source && AudioService.source.id === lastSourceId) {
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
AudioService.setInputVolume(localInputVolume);
}
}
}
}
@@ -41,7 +93,7 @@ NBox {
Connections {
target: AudioService
function onVolumeChanged() {
if (!localOutputVolumeChanging) {
if (!localOutputVolumeChanging && AudioService.sink && AudioService.sink.id === lastSinkId) {
var vol = AudioService.volume;
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
}
@@ -51,7 +103,7 @@ NBox {
Connections {
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
function onVolumeChanged() {
if (!localOutputVolumeChanging) {
if (!localOutputVolumeChanging && AudioService.sink && AudioService.sink.id === lastSinkId) {
var vol = AudioService.volume;
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
}
@@ -61,7 +113,7 @@ NBox {
Connections {
target: AudioService
function onInputVolumeChanged() {
if (!localInputVolumeChanging) {
if (!localInputVolumeChanging && AudioService.source && AudioService.source.id === lastSourceId) {
var vol = AudioService.inputVolume;
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
}
@@ -71,7 +123,7 @@ NBox {
Connections {
target: AudioService.source?.audio ? AudioService.source?.audio : null
function onVolumeChanged() {
if (!localInputVolumeChanging) {
if (!localInputVolumeChanging && AudioService.source && AudioService.source.id === lastSourceId) {
var vol = AudioService.inputVolume;
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
}

View File

@@ -0,0 +1,132 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Location
import qs.Widgets
// Calendar header with date, month/year, location, and clock
Rectangle {
id: root
Layout.fillWidth: true
Layout.minimumHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
Layout.preferredHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
implicitHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
radius: Style.radiusL
color: Color.mPrimary
// Internal state
readonly property var now: Time.now
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
// Expose current month/year for potential synchronization with CalendarMonthCard
readonly property int currentMonth: now.getMonth()
readonly property int currentYear: now.getFullYear()
ColumnLayout {
id: capsuleColumn
anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: Style.marginM
anchors.bottomMargin: Style.marginM
anchors.rightMargin: clockLoader.width + (Style.marginXL * 2)
anchors.leftMargin: Style.marginXL
spacing: 0
// Combined layout for date, month year, location and time-zone
RowLayout {
Layout.fillWidth: true
height: 60 * Style.uiScaleRatio
clip: true
spacing: Style.marginS
// Today day number
NText {
Layout.preferredWidth: implicitWidth
elide: Text.ElideNone
clip: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: root.now.getDate()
pointSize: Style.fontSizeXXXL * 1.5
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
// Month, year, location
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.bottomMargin: Style.marginXXS
Layout.topMargin: -Style.marginXXS
spacing: -Style.marginXS
RowLayout {
spacing: Style.marginS
NText {
text: I18n.locale.monthName(root.currentMonth, Locale.LongFormat).toUpperCase()
pointSize: Style.fontSizeXL * 1.1
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
Layout.alignment: Qt.AlignBaseline
elide: Text.ElideRight
}
NText {
text: `${root.currentYear}`
pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
color: Qt.alpha(Color.mOnPrimary, 0.7)
Layout.alignment: Qt.AlignBaseline
}
}
RowLayout {
spacing: 0
NText {
text: {
if (!Settings.data.location.weatherEnabled)
return "";
if (!root.weatherReady)
return I18n.tr("calendar.weather.loading");
const chunks = Settings.data.location.name.split(",");
return chunks[0];
}
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnPrimary
Layout.maximumWidth: 150
elide: Text.ElideRight
}
NText {
text: root.weatherReady ? ` (${LocationService.data.weather.timezone_abbreviation})` : ""
pointSize: Style.fontSizeXS
font.weight: Style.fontWeightMedium
color: Qt.alpha(Color.mOnPrimary, 0.7)
}
}
}
// Spacer
Item {
Layout.fillWidth: true
}
}
}
// Analog/Digital clock
NClock {
id: clockLoader
anchors.right: parent.right
anchors.rightMargin: Style.marginXL
anchors.verticalCenter: parent.verticalCenter
clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital"
progressColor: Color.mOnPrimary
Layout.alignment: Qt.AlignVCenter
now: root.now
}
}

View File

@@ -0,0 +1,396 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Location
import qs.Services.System
import qs.Services.UI
import qs.Widgets
// Calendar month grid with navigation
NBox {
id: root
Layout.fillWidth: true
implicitHeight: calendarContent.implicitHeight + Style.marginM * 2
// Internal state - independent from header
readonly property var now: Time.now
property int calendarMonth: now.getMonth()
property int calendarYear: now.getFullYear()
readonly property int firstDayOfWeek: Settings.data.location.firstDayOfWeek === -1 ? I18n.locale.firstDayOfWeek : Settings.data.location.firstDayOfWeek
// Helper function to calculate ISO week number
function getISOWeekNumber(date) {
const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNr + 3);
const firstThursday = new Date(target.getFullYear(), 0, 4);
const diff = target - firstThursday;
const oneWeek = 1000 * 60 * 60 * 24 * 7;
const weekNumber = 1 + Math.round(diff / oneWeek);
return weekNumber;
}
// Helper function to check if an event is all-day
function isAllDayEvent(event) {
const duration = event.end - event.start;
const startDate = new Date(event.start * 1000);
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
return duration === 86400 && isAtMidnight;
}
ColumnLayout {
id: calendarContent
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginS
// Navigation row
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NDivider {
Layout.fillWidth: true
}
NIconButton {
icon: "chevron-left"
onClicked: {
let newDate = new Date(root.calendarYear, root.calendarMonth - 1, 1);
root.calendarYear = newDate.getFullYear();
root.calendarMonth = newDate.getMonth();
const now = new Date();
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
}
}
NIconButton {
icon: "calendar"
onClicked: {
root.calendarMonth = root.now.getMonth();
root.calendarYear = root.now.getFullYear();
CalendarService.loadEvents();
}
}
NIconButton {
icon: "chevron-right"
onClicked: {
let newDate = new Date(root.calendarYear, root.calendarMonth + 1, 1);
root.calendarYear = newDate.getFullYear();
root.calendarMonth = newDate.getMonth();
const now = new Date();
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
}
}
}
// Day names header
RowLayout {
Layout.fillWidth: true
spacing: 0
Item {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
}
GridLayout {
Layout.fillWidth: true
columns: 7
rows: 1
columnSpacing: 0
rowSpacing: 0
Repeater {
model: 7
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.fontSizeS * 2
NText {
anchors.centerIn: parent
text: {
let dayIndex = (root.firstDayOfWeek + index) % 7;
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat);
return dayName.substring(0, 2).toUpperCase();
}
color: Color.mPrimary
pointSize: Style.fontSizeS
font.weight: Style.fontWeightBold
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
// Calendar grid with week numbers
RowLayout {
Layout.fillWidth: true
spacing: 0
// Helper functions
function hasEventsOnDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0)
return false;
const targetDate = new Date(year, month, day);
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000;
const targetEnd = targetStart + 86400;
return CalendarService.events.some(event => {
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
});
}
function getEventsForDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0)
return [];
const targetDate = new Date(year, month, day);
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000);
const targetEnd = targetStart + 86400;
return CalendarService.events.filter(event => {
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
});
}
function isMultiDayEvent(event) {
if (root.isAllDayEvent(event)) {
return false;
}
const startDate = new Date(event.start * 1000);
const endDate = new Date(event.end * 1000);
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
return startDateOnly.getTime() !== endDateOnly.getTime();
}
function getEventColor(event, isToday) {
if (isMultiDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mTertiary;
} else if (root.isAllDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mSecondary;
} else {
return isToday ? Color.mOnSecondary : Color.mPrimary;
}
}
// Week numbers column
ColumnLayout {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
Layout.alignment: Qt.AlignTop
spacing: Style.marginXXS
property var weekNumbers: {
if (!grid.daysModel || grid.daysModel.length === 0)
return [];
const weeks = [];
const numWeeks = Math.ceil(grid.daysModel.length / 7);
for (var i = 0; i < numWeeks; i++) {
const dayIndex = i * 7;
if (dayIndex < grid.daysModel.length) {
const weekDay = grid.daysModel[dayIndex];
const date = new Date(weekDay.year, weekDay.month, weekDay.day);
let thursday = new Date(date);
if (root.firstDayOfWeek === 0) {
thursday.setDate(date.getDate() + 4);
} else if (root.firstDayOfWeek === 1) {
thursday.setDate(date.getDate() + 3);
} else {
let daysToThursday = (4 - root.firstDayOfWeek + 7) % 7;
thursday.setDate(date.getDate() + daysToThursday);
}
weeks.push(root.getISOWeekNumber(thursday));
}
}
return weeks;
}
Repeater {
model: parent.weekNumbers
Item {
Layout.preferredWidth: Style.baseWidgetSize * 0.7
Layout.preferredHeight: Style.baseWidgetSize * 0.9
NText {
anchors.centerIn: parent
color: Qt.alpha(Color.mPrimary, 0.7)
pointSize: Style.fontSizeXXS
font.weight: Style.fontWeightMedium
text: modelData
}
}
}
}
// Calendar grid
GridLayout {
id: grid
Layout.fillWidth: true
columns: 7
columnSpacing: Style.marginXXS
rowSpacing: Style.marginXXS
property int month: root.calendarMonth
property int year: root.calendarYear
property var daysModel: {
const firstOfMonth = new Date(year, month, 1);
const lastOfMonth = new Date(year, month + 1, 0);
const daysInMonth = lastOfMonth.getDate();
const firstDayOfWeek = root.firstDayOfWeek;
const firstOfMonthDayOfWeek = firstOfMonth.getDay();
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
const lastOfMonthDayOfWeek = lastOfMonth.getDay();
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
const days = [];
const today = new Date();
// Previous month days
const prevMonth = new Date(year, month, 0);
const prevMonthDays = prevMonth.getDate();
for (var i = daysBefore - 1; i >= 0; i--) {
const day = prevMonthDays - i;
days.push({
"day": day,
"month": month - 1,
"year": month === 0 ? year - 1 : year,
"today": false,
"currentMonth": false
});
}
// Current month days
for (var day = 1; day <= daysInMonth; day++) {
const date = new Date(year, month, day);
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
days.push({
"day": day,
"month": month,
"year": year,
"today": isToday,
"currentMonth": true
});
}
// Next month days
for (var i = 1; i <= daysAfter; i++) {
days.push({
"day": i,
"month": month + 1,
"year": month === 11 ? year + 1 : year,
"today": false,
"currentMonth": false
});
}
return days;
}
Repeater {
model: grid.daysModel
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.9
Rectangle {
width: Style.baseWidgetSize * 0.9
height: Style.baseWidgetSize * 0.9
anchors.centerIn: parent
radius: Style.radiusM
color: modelData.today ? Color.mSecondary : Color.transparent
NText {
anchors.centerIn: parent
text: modelData.day
color: {
if (modelData.today)
return Color.mOnSecondary;
if (modelData.currentMonth)
return Color.mOnSurface;
return Color.mOnSurfaceVariant;
}
opacity: modelData.currentMonth ? 1.0 : 0.4
pointSize: Style.fontSizeM
font.weight: modelData.today ? Style.fontWeightBold : Style.fontWeightMedium
}
// Event indicator dots
Row {
visible: Settings.data.location.showCalendarEvents && parent.parent.parent.parent.hasEventsOnDate(modelData.year, modelData.month, modelData.day)
spacing: 2
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.marginXS
Repeater {
model: parent.parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day)
Rectangle {
width: 4
height: width
radius: width / 2
color: parent.parent.parent.parent.parent.getEventColor(modelData, modelData.today)
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: Settings.data.location.showCalendarEvents
onEntered: {
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day);
if (events.length > 0) {
const summaries = events.map(event => {
if (root.isAllDayEvent(event)) {
return event.summary;
} else {
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm";
const start = new Date(event.start * 1000);
const startFormatted = I18n.locale.toString(start, timeFormat);
const end = new Date(event.end * 1000);
const endFormatted = I18n.locale.toString(end, timeFormat);
return `${startFormatted}-${endFormatted} ${event.summary}`;
}
}).join('\n');
TooltipService.show(parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed);
}
}
onClicked: {
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`;
if (ProgramCheckerService.gnomeCalendarAvailable) {
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]);
}
}
onExited: {
TooltipService.hide();
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
}
}
}
}
}

View File

@@ -5,7 +5,6 @@ import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.Commons
import qs.Modules.Panels.ControlCenter.Cards
import qs.Modules.Panels.Settings
import qs.Services.System
import qs.Services.UI
@@ -25,13 +24,14 @@ NBox {
anchors.margins: Style.marginM
spacing: Style.marginM
NImageCircled {
NImageRounded {
Layout.preferredWidth: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio)
Layout.preferredHeight: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio)
radius: width * 0.5
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
fallbackIcon: "person"
borderColor: Color.mPrimary
borderWidth: Style.borderM
borderWidth: Style.borderS * 1.5
}
ColumnLayout {

View File

@@ -4,7 +4,6 @@ import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Modules.Panels.ControlCenter
import qs.Modules.Panels.ControlCenter.Cards
import qs.Widgets
RowLayout {
@@ -14,6 +13,7 @@ RowLayout {
NBox {
Layout.fillWidth: true
Layout.preferredHeight: root.shortcutsHeight
visible: Settings.data.controlCenter.shortcuts.left.length > 0
RowLayout {
id: leftContent
@@ -53,6 +53,7 @@ RowLayout {
NBox {
Layout.fillWidth: true
Layout.preferredHeight: root.shortcutsHeight
visible: Settings.data.controlCenter.shortcuts.right.length > 0
RowLayout {
id: rightContent

449
Modules/Cards/TimerCard.qml Normal file
View File

@@ -0,0 +1,449 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.System
import qs.Widgets
// Timer card for the Calendar panel
NBox {
id: root
implicitHeight: content.implicitHeight + (Style.marginM * 2)
Layout.fillWidth: true
clip: true
ColumnLayout {
id: content
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
clip: true
// Header
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NIcon {
icon: isStopwatchMode ? "clock" : "hourglass"
pointSize: Style.fontSizeL
color: Color.mPrimary
}
NText {
text: I18n.tr("calendar.timer.title")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
}
}
// Timer display (editable when not running)
Item {
id: timerDisplayItem
Layout.fillWidth: true
Layout.preferredHeight: isRunning ? 160 * Style.uiScaleRatio : timerInput.implicitHeight
Layout.alignment: Qt.AlignHCenter
property string inputBuffer: ""
property bool isEditing: false
// Circular progress ring (only for countdown mode when running)
Canvas {
id: progressRing
anchors.fill: parent
anchors.margins: 12
visible: !isStopwatchMode && isRunning && totalSeconds > 0
z: -1
property real progressRatio: {
if (totalSeconds <= 0)
return 0;
// Inverted: show remaining time (starts at 1, goes to 0)
const ratio = remainingSeconds / totalSeconds;
return Math.max(0, Math.min(1, ratio));
}
onProgressRatioChanged: requestPaint()
onPaint: {
var ctx = getContext("2d");
if (width <= 0 || height <= 0) {
return;
}
var centerX = width / 2;
var centerY = height / 2;
var radius = Math.max(0, Math.min(width, height) / 2 - 6);
ctx.reset();
// Background circle (full track)
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.lineWidth = 4;
ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.2);
ctx.stroke();
// Progress arc (elapsed portion)
if (progressRatio > 0) {
ctx.beginPath();
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI);
ctx.lineWidth = 4;
ctx.strokeStyle = Color.mPrimary;
ctx.lineCap = "round";
ctx.stroke();
}
}
}
TextInput {
id: timerInput
anchors.centerIn: parent
width: Math.max(implicitWidth, parent.width)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
selectByMouse: false
cursorVisible: false
cursorDelegate: Item {} // Empty cursor delegate to hide cursor
readOnly: isStopwatchMode || isRunning
enabled: !isRunning
font.family: Settings.data.ui.fontFixed
// Calculate if hours are being shown
readonly property bool showingHours: {
if (isStopwatchMode) {
return elapsedSeconds >= 3600;
}
// In edit mode, always show hours (HH:MM:SS format)
if (timerDisplayItem.isEditing) {
return true;
}
// When not editing, only show hours if >= 1 hour
return remainingSeconds >= 3600;
}
font.pointSize: {
if (!isRunning) {
return Style.fontSizeXXXL;
}
// When running, use smaller font if hours are shown
return showingHours ? Style.fontSizeXXL : (Style.fontSizeXXL * 1.2);
}
font.weight: Style.fontWeightBold
color: {
if (isRunning) {
return Color.mPrimary;
}
if (timerDisplayItem.isEditing) {
return Color.mPrimary;
}
return Color.mOnSurface;
}
// Display formatted time, but show input buffer when editing
text: {
if (isStopwatchMode) {
return formatTime(elapsedSeconds, false); // Stopwatch: only show hours if >= 1 hour
}
if (!timerDisplayItem.isEditing) {
// When not editing and not running, always show hours
// When running, only show hours if >= 1 hour
return formatTime(remainingSeconds, isRunning);
}
if (timerDisplayItem.inputBuffer !== "") {
return formatTimeFromDigits(timerDisplayItem.inputBuffer);
}
return formatTime(0, false);
}
// Only accept digit keys
Keys.onPressed: event => {
if (isRunning || isStopwatchMode) {
event.accepted = true;
return;
}
// Handle backspace
if (event.key === Qt.Key_Backspace) {
if (timerDisplayItem.isEditing && timerDisplayItem.inputBuffer.length > 0) {
timerDisplayItem.inputBuffer = timerDisplayItem.inputBuffer.slice(0, -1);
if (timerDisplayItem.inputBuffer !== "") {
parseDigitsToTime(timerDisplayItem.inputBuffer);
} else {
Time.timerRemainingSeconds = 0;
}
}
event.accepted = true;
return;
}
// Handle delete
if (event.key === Qt.Key_Delete) {
if (timerDisplayItem.isEditing) {
timerDisplayItem.inputBuffer = "";
Time.timerRemainingSeconds = 0;
}
event.accepted = true;
return;
}
// Allow navigation keys (but don't let them modify text)
if (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Home || event.key === Qt.Key_End || (event.modifiers & Qt.ControlModifier) || (event.modifiers & Qt.ShiftModifier)) {
event.accepted = false; // Let default handling work for selection
return;
}
// Handle enter/return
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
applyTimeFromBuffer();
timerDisplayItem.isEditing = false;
focus = false;
event.accepted = true;
return;
}
// Handle escape
if (event.key === Qt.Key_Escape) {
timerDisplayItem.inputBuffer = "";
Time.timerRemainingSeconds = 0;
timerDisplayItem.isEditing = false;
focus = false;
event.accepted = true;
return;
}
// Only allow digits 0-9
if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) {
// Limit to 6 digits max
if (timerDisplayItem.inputBuffer.length >= 6) {
event.accepted = true; // Block if already at max
return;
}
// Add the digit to the buffer
timerDisplayItem.inputBuffer += String.fromCharCode(event.key);
// Update the display and parse
parseDigitsToTime(timerDisplayItem.inputBuffer);
event.accepted = true; // We handled it
} else {
event.accepted = true; // Block all other keys
}
}
Keys.onReturnPressed: {
applyTimeFromBuffer();
timerDisplayItem.isEditing = false;
focus = false;
}
Keys.onEscapePressed: {
timerDisplayItem.inputBuffer = "";
Time.timerRemainingSeconds = 0;
timerDisplayItem.isEditing = false;
focus = false;
}
onActiveFocusChanged: {
if (activeFocus) {
timerDisplayItem.isEditing = true;
timerDisplayItem.inputBuffer = "";
} else {
applyTimeFromBuffer();
timerDisplayItem.isEditing = false;
timerDisplayItem.inputBuffer = "";
}
}
MouseArea {
anchors.fill: parent
enabled: !isRunning && !isStopwatchMode
cursorShape: enabled ? Qt.IBeamCursor : Qt.ArrowCursor
onClicked: {
if (!isRunning && !isStopwatchMode) {
timerInput.forceActiveFocus();
}
}
}
}
}
// Control buttons
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
Rectangle {
Layout.fillWidth: true
Layout.preferredWidth: 0
implicitHeight: startButton.implicitHeight
color: Color.transparent
NButton {
id: startButton
anchors.fill: parent
text: isRunning ? I18n.tr("calendar.timer.pause") : I18n.tr("calendar.timer.start")
icon: isRunning ? "player-pause" : "player-play"
enabled: isStopwatchMode || remainingSeconds > 0
onClicked: {
if (isRunning) {
pauseTimer();
} else {
startTimer();
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredWidth: 0
implicitHeight: resetButton.implicitHeight
color: Color.transparent
NButton {
id: resetButton
anchors.fill: parent
text: I18n.tr("calendar.timer.reset")
icon: "refresh"
enabled: (isStopwatchMode && (elapsedSeconds > 0 || isRunning)) || (!isStopwatchMode && (remainingSeconds > 0 || isRunning || soundPlaying))
onClicked: {
resetTimer();
}
}
}
}
// Mode tabs (Android-style) - below buttons
NTabBar {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
visible: !isRunning
currentIndex: isStopwatchMode ? 1 : 0
onCurrentIndexChanged: {
const newMode = currentIndex === 1;
if (newMode !== isStopwatchMode) {
if (isRunning) {
pauseTimer();
}
// Stop any repeating notification sound when switching modes
SoundService.stopSound("alarm-beep.wav");
Time.timerSoundPlaying = false;
Time.timerStopwatchMode = newMode;
if (newMode) {
// Reset to 0 for stopwatch
Time.timerElapsedSeconds = 0;
} else {
Time.timerRemainingSeconds = 0;
}
}
}
spacing: Style.marginXS
NTabButton {
text: I18n.tr("calendar.timer.countdown")
tabIndex: 0
checked: !isStopwatchMode
}
NTabButton {
text: I18n.tr("calendar.timer.stopwatch")
tabIndex: 1
checked: isStopwatchMode
}
}
}
// Bind to Time for persistent timer state
readonly property bool isRunning: Time.timerRunning
property bool isStopwatchMode: Time.timerStopwatchMode
readonly property int remainingSeconds: Time.timerRemainingSeconds
readonly property int totalSeconds: Time.timerTotalSeconds
readonly property int elapsedSeconds: Time.timerElapsedSeconds
readonly property bool soundPlaying: Time.timerSoundPlaying
function formatTime(seconds, hideHoursWhenZero) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
// If hideHoursWhenZero is true (when running), only show hours if > 0
// Otherwise (when not running or editing), always show hours
if (hideHoursWhenZero && hours === 0) {
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
function formatTimeFromDigits(digits) {
// Parse digits right-to-left: last 2 = seconds, next 2 = minutes, rest = hours
const len = digits.length;
let seconds = 0;
let minutes = 0;
let hours = 0;
if (len > 0) {
seconds = parseInt(digits.substring(Math.max(0, len - 2))) || 0;
}
if (len > 2) {
minutes = parseInt(digits.substring(Math.max(0, len - 4), len - 2)) || 0;
}
if (len > 4) {
hours = parseInt(digits.substring(0, len - 4)) || 0;
}
// Clamp values
seconds = Math.min(59, seconds);
minutes = Math.min(59, minutes);
hours = Math.min(99, hours);
// Always show HH:MM:SS format in edit mode
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
function parseDigitsToTime(digits) {
// Parse digits right-to-left: last 2 = seconds, next 2 = minutes, rest = hours
const len = digits.length;
let seconds = 0;
let minutes = 0;
let hours = 0;
if (len > 0) {
seconds = parseInt(digits.substring(Math.max(0, len - 2))) || 0;
}
if (len > 2) {
minutes = parseInt(digits.substring(Math.max(0, len - 4), len - 2)) || 0;
}
if (len > 4) {
hours = parseInt(digits.substring(0, len - 4)) || 0;
}
// Clamp values
seconds = Math.min(59, seconds);
minutes = Math.min(59, minutes);
hours = Math.min(99, hours);
Time.timerRemainingSeconds = (hours * 3600) + (minutes * 60) + seconds;
}
function applyTimeFromBuffer() {
if (timerDisplayItem.inputBuffer !== "") {
parseDigitsToTime(timerDisplayItem.inputBuffer);
timerDisplayItem.inputBuffer = "";
}
}
function startTimer() {
Time.timerStart();
}
function pauseTimer() {
Time.timerPause();
}
function resetTimer() {
Time.timerReset();
}
}

View File

@@ -20,7 +20,7 @@ NBox {
// Weather condition detection
readonly property int currentWeatherCode: weatherReady ? LocationService.data.weather.current_weather.weathercode : 0
readonly property bool isRaining: testEffects === "rain" || (testEffects === "" && currentWeatherCode >= 51 && currentWeatherCode <= 67)
readonly property bool isRaining: testEffects === "rain" || (testEffects === "" && ((currentWeatherCode >= 51 && currentWeatherCode <= 67) || (currentWeatherCode >= 80 && currentWeatherCode <= 82)))
readonly property bool isSnowing: testEffects === "snow" || (testEffects === "" && ((currentWeatherCode >= 71 && currentWeatherCode <= 77) || (currentWeatherCode >= 85 && currentWeatherCode <= 86)))
visible: Settings.data.location.weatherEnabled

View File

@@ -84,6 +84,7 @@ Loader {
cache: true
smooth: true
mipmap: false
antialiasing: true
}
Rectangle {
@@ -301,10 +302,11 @@ Loader {
}
}
NImageCircled {
NImageRounded {
anchors.centerIn: parent
width: 66
height: 66
radius: width * 0.5
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
fallbackIcon: "person"
@@ -344,14 +346,14 @@ Loader {
var lang = I18n.locale.name.split("_")[0];
var formats = {
"de": "dddd, d. MMMM",
"en": "dddd, MMMM d",
"es": "dddd, d 'de' MMMM",
"fr": "dddd d MMMM",
"nl": "dddd d MMMM",
"pt": "dddd, d 'de' MMMM",
"zh": "yyyy年M月d日 dddd",
"uk": "dddd, d MMMM",
"tr": "dddd, d MMMM"
"zh": "yyyy年M月d日 dddd"
};
return I18n.locale.toString(Time.now, formats[lang] || "dddd, MMMM d");
return I18n.locale.toString(Time.now, formats[lang] || "dddd, d MMMM");
}
pointSize: Style.fontSizeXL
font.weight: Font.Medium
@@ -525,7 +527,7 @@ Loader {
}
Text {
id: hibernateText
text: I18n.tr("session-menu.hibernate")
text: Settings.data.general.showHibernateOnLockScreen ? I18n.tr("session-menu.hibernate") : ""
font.pointSize: buttonRowTextMeasurer.fontSize
font.weight: Font.Medium
}
@@ -631,9 +633,10 @@ Loader {
color: Color.transparent
clip: true
NImageCircled {
NImageRounded {
anchors.fill: parent
anchors.margins: 2
radius: width * 0.5
imagePath: MediaService.trackArtUrl
fallbackIcon: "disc"
fallbackIconSize: Style.fontSizeM
@@ -752,7 +755,7 @@ Loader {
}
}
// 3-day forecast
// Forecast
RowLayout {
visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null
Layout.preferredWidth: 260
@@ -760,7 +763,7 @@ Loader {
spacing: 4
Repeater {
model: 3
model: MediaService.currentPlayer && MediaService.canPlay ? 3 : 4
delegate: ColumnLayout {
Layout.fillWidth: true
spacing: 3
@@ -807,8 +810,6 @@ Loader {
Item {
Layout.fillWidth: true
visible: !(Settings.data.location.weatherEnabled && LocationService.data.weather !== null)
Layout.preferredWidth: visible ? 1 : 0
}
// Battery and Keyboard Layout (full mode only)

View File

@@ -90,9 +90,9 @@ Item {
backgroundColor: panelBackgroundColor
}
// Calendar
// Clock
PanelBackground {
panel: root.windowRoot.calendarPanelPlaceholder
panel: root.windowRoot.clockPanelPlaceholder
shapeContainer: backgroundsShape
backgroundColor: panelBackgroundColor
}

View File

@@ -1,4 +1,5 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
@@ -13,8 +14,8 @@ import qs.Modules.Panels.Audio
import qs.Modules.Panels.Battery
import qs.Modules.Panels.Bluetooth
import qs.Modules.Panels.Brightness
import qs.Modules.Panels.Calendar
import qs.Modules.Panels.Changelog
import qs.Modules.Panels.Clock
import qs.Modules.Panels.ControlCenter
import qs.Modules.Panels.Launcher
import qs.Modules.Panels.NotificationHistory
@@ -24,6 +25,7 @@ import qs.Modules.Panels.SetupWizard
import qs.Modules.Panels.Tray
import qs.Modules.Panels.Wallpaper
import qs.Modules.Panels.WiFi
import qs.Services.Compositor
import qs.Services.UI
/**
@@ -37,7 +39,7 @@ PanelWindow {
readonly property alias batteryPanel: batteryPanel
readonly property alias bluetoothPanel: bluetoothPanel
readonly property alias brightnessPanel: brightnessPanel
readonly property alias calendarPanel: calendarPanel
readonly property alias clockPanel: clockPanel
readonly property alias changelogPanel: changelogPanel
readonly property alias controlCenterPanel: controlCenterPanel
readonly property alias launcherPanel: launcherPanel
@@ -49,22 +51,22 @@ PanelWindow {
readonly property alias wallpaperPanel: wallpaperPanel
readonly property alias wifiPanel: wifiPanel
// Expose panel placeholders for AllBackgrounds
readonly property var audioPanelPlaceholder: audioPanel.panelPlaceholder
readonly property var batteryPanelPlaceholder: batteryPanel.panelPlaceholder
readonly property var bluetoothPanelPlaceholder: bluetoothPanel.panelPlaceholder
readonly property var brightnessPanelPlaceholder: brightnessPanel.panelPlaceholder
readonly property var calendarPanelPlaceholder: calendarPanel.panelPlaceholder
readonly property var changelogPanelPlaceholder: changelogPanel.panelPlaceholder
readonly property var controlCenterPanelPlaceholder: controlCenterPanel.panelPlaceholder
readonly property var launcherPanelPlaceholder: launcherPanel.panelPlaceholder
readonly property var notificationHistoryPanelPlaceholder: notificationHistoryPanel.panelPlaceholder
readonly property var sessionMenuPanelPlaceholder: sessionMenuPanel.panelPlaceholder
readonly property var settingsPanelPlaceholder: settingsPanel.panelPlaceholder
readonly property var setupWizardPanelPlaceholder: setupWizardPanel.panelPlaceholder
readonly property var trayDrawerPanelPlaceholder: trayDrawerPanel.panelPlaceholder
readonly property var wallpaperPanelPlaceholder: wallpaperPanel.panelPlaceholder
readonly property var wifiPanelPlaceholder: wifiPanel.panelPlaceholder
// Expose panel backgrounds for AllBackgrounds
readonly property var audioPanelPlaceholder: audioPanel.panelRegion
readonly property var batteryPanelPlaceholder: batteryPanel.panelRegion
readonly property var bluetoothPanelPlaceholder: bluetoothPanel.panelRegion
readonly property var brightnessPanelPlaceholder: brightnessPanel.panelRegion
readonly property var clockPanelPlaceholder: clockPanel.panelRegion
readonly property var changelogPanelPlaceholder: changelogPanel.panelRegion
readonly property var controlCenterPanelPlaceholder: controlCenterPanel.panelRegion
readonly property var launcherPanelPlaceholder: launcherPanel.panelRegion
readonly property var notificationHistoryPanelPlaceholder: notificationHistoryPanel.panelRegion
readonly property var sessionMenuPanelPlaceholder: sessionMenuPanel.panelRegion
readonly property var settingsPanelPlaceholder: settingsPanel.panelRegion
readonly property var setupWizardPanelPlaceholder: setupWizardPanel.panelRegion
readonly property var trayDrawerPanelPlaceholder: trayDrawerPanel.panelRegion
readonly property var wallpaperPanelPlaceholder: wallpaperPanel.panelRegion
readonly property var wifiPanelPlaceholder: wifiPanel.panelRegion
Component.onCompleted: {
Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y);
@@ -74,7 +76,12 @@ PanelWindow {
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "noctalia-background-" + (screen?.name || "unknown")
WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone handles that
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: {
if (!root.isPanelOpen) {
return WlrKeyboardFocus.None;
}
return PanelService.openedPanel.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand;
}
anchors {
top: true
@@ -128,7 +135,9 @@ PanelWindow {
height: root.height
intersection: Intersection.Xor
regions: [barMaskRegion]
// Only include regions that are actually needed
// panelRegions is handled by PanelService, bar is local to this screen
regions: [barMaskRegion, backgroundMaskRegion]
// Bar region - subtract bar area from mask (only if bar should be shown on this screen)
Region {
@@ -142,6 +151,16 @@ PanelWindow {
height: root.barShouldShow ? barPlaceholder.height : 0
intersection: Intersection.Subtract
}
// Background region for click-to-close - reactive sizing
Region {
id: backgroundMaskRegion
x: 0
y: 0
width: root.isPanelOpen && !isPanelClosing ? root.width : 0
height: root.isPanelOpen && !isPanelClosing ? root.height : 0
intersection: Intersection.Subtract
}
}
// --------------------------------------
@@ -162,6 +181,20 @@ PanelWindow {
z: 0 // Behind all content
}
// Background MouseArea for closing panels when clicking outside
// Active whenever a panel is open - the mask ensures it only receives clicks when panel is open
MouseArea {
anchors.fill: parent
enabled: root.isPanelOpen
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
if (PanelService.openedPanel) {
PanelService.openedPanel.close();
}
}
z: 0 // Behind panels and bar
}
// ---------------------------------------
// All panels always exist
// ---------------------------------------
@@ -169,90 +202,105 @@ PanelWindow {
id: audioPanel
objectName: "audioPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
BatteryPanel {
id: batteryPanel
objectName: "batteryPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
BluetoothPanel {
id: bluetoothPanel
objectName: "bluetoothPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
BrightnessPanel {
id: brightnessPanel
objectName: "brightnessPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
ControlCenterPanel {
id: controlCenterPanel
objectName: "controlCenterPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
ChangelogPanel {
id: changelogPanel
objectName: "changelogPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
CalendarPanel {
id: calendarPanel
objectName: "calendarPanel-" + (root.screen?.name || "unknown")
ClockPanel {
id: clockPanel
objectName: "clockPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
Launcher {
id: launcherPanel
objectName: "launcherPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
NotificationHistoryPanel {
id: notificationHistoryPanel
objectName: "notificationHistoryPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
SessionMenu {
id: sessionMenuPanel
objectName: "sessionMenuPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
SettingsPanel {
id: settingsPanel
objectName: "settingsPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
SetupWizard {
id: setupWizardPanel
objectName: "setupWizardPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
TrayDrawerPanel {
id: trayDrawerPanel
objectName: "trayDrawerPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
WallpaperPanel {
id: wallpaperPanel
objectName: "wallpaperPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
WiFiPanel {
id: wifiPanel
objectName: "wifiPanel-" + (root.screen?.name || "unknown")
screen: root.screen
z: 50
}
// ----------------------------------------------
@@ -358,4 +406,183 @@ PanelWindow {
*/
ScreenCorners {}
}
// ========================================
// Centralized Keyboard Shortcuts
// ========================================
// These shortcuts delegate to the opened panel's handler functions
// Panels can implement: onEscapePressed, onTabPressed, onShiftTabPressed,
// onUpPressed, onDownPressed, onReturnPressed
Shortcut {
sequence: "Escape"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onEscapePressed) {
PanelService.openedPanel.onEscapePressed();
} else if (PanelService.openedPanel) {
PanelService.openedPanel.close();
}
}
}
Shortcut {
sequence: "Tab"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onTabPressed) {
PanelService.openedPanel.onTabPressed();
}
}
}
Shortcut {
sequence: "Shift+Tab"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onShiftTabPressed) {
PanelService.openedPanel.onShiftTabPressed();
}
}
}
Shortcut {
sequence: "Up"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onUpPressed) {
PanelService.openedPanel.onUpPressed();
}
}
}
Shortcut {
sequence: "Down"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onDownPressed) {
PanelService.openedPanel.onDownPressed();
}
}
}
Shortcut {
sequence: "Return"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onReturnPressed) {
PanelService.openedPanel.onReturnPressed();
}
}
}
Shortcut {
sequence: "Left"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onLeftPressed) {
PanelService.openedPanel.onLeftPressed();
}
}
}
Shortcut {
sequence: "Right"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onRightPressed) {
PanelService.openedPanel.onRightPressed();
}
}
}
Shortcut {
sequence: "Home"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onHomePressed) {
PanelService.openedPanel.onHomePressed();
}
}
}
Shortcut {
sequence: "End"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onEndPressed) {
PanelService.openedPanel.onEndPressed();
}
}
}
Shortcut {
sequence: "PgUp"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onPageUpPressed) {
PanelService.openedPanel.onPageUpPressed();
}
}
}
Shortcut {
sequence: "PgDown"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onPageDownPressed) {
PanelService.openedPanel.onPageDownPressed();
}
}
}
Shortcut {
sequence: "Backtab"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onBackTabPressed) {
PanelService.openedPanel.onBackTabPressed();
}
}
}
Shortcut {
sequence: "Ctrl+J"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlJPressed) {
PanelService.openedPanel.onCtrlJPressed();
}
}
}
Shortcut {
sequence: "Ctrl+K"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlKPressed) {
PanelService.openedPanel.onCtrlKPressed();
}
}
}
Shortcut {
sequence: "Ctrl+N"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlNPressed) {
PanelService.openedPanel.onCtrlNPressed();
}
}
}
Shortcut {
sequence: "Ctrl+P"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlPPressed) {
PanelService.openedPanel.onCtrlPPressed();
}
}
}
}

View File

@@ -1,706 +0,0 @@
import QtQuick
import Quickshell
import qs.Commons
import qs.Services.UI
/**
* PanelPlaceholder - Lightweight positioning logic for panel backgrounds
*
* This component stays in MainScreen and provides geometry for PanelBackground rendering.
* It contains only positioning calculations and animations, no visual content.
* The actual panel content lives in a separate SmartPanelWindow.
*/
Item {
id: root
// Required properties
required property ShellScreen screen
required property string panelName
// Unique identifier
// Panel size properties
property real preferredWidth: 700
property real preferredHeight: 900
property real preferredWidthRatio
property real preferredHeightRatio
property var buttonItem: null
property bool forceAttachToBar: false
// Anchoring properties
property bool panelAnchorHorizontalCenter: false
property bool panelAnchorVerticalCenter: false
property bool panelAnchorTop: false
property bool panelAnchorBottom: false
property bool panelAnchorLeft: false
property bool panelAnchorRight: false
// Button position properties
property bool useButtonPosition: false
property point buttonPosition: Qt.point(0, 0)
property int buttonWidth: 0
property int buttonHeight: 0
// Edge snapping distance
property real edgeSnapDistance: 50
// State tracking (controlled by SmartPanelWindow)
property bool isPanelVisible: false
property bool isClosing: false
property bool opacityFadeComplete: false
property bool sizeAnimationComplete: false
// Derived state: track opening transition
readonly property bool isOpening: isPanelVisible && !isClosing && !sizeAnimationComplete
// Content size (set by SmartPanelWindow when content size changes)
property real contentPreferredWidth: 0
property real contentPreferredHeight: 0
// Expose panelBackground as panelItem for AllBackgrounds
readonly property var panelItem: panelBackground
// Bar configuration
readonly property string barPosition: Settings.data.bar.position
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property bool barFloating: Settings.data.bar.floating
readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
// Helper to detect if any anchor is explicitly set
readonly property bool hasExplicitHorizontalAnchor: panelAnchorHorizontalCenter || panelAnchorLeft || panelAnchorRight
readonly property bool hasExplicitVerticalAnchor: panelAnchorVerticalCenter || panelAnchorTop || panelAnchorBottom
// Attachment properties
readonly property bool allowAttach: Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar
readonly property bool allowAttachToBar: {
if (!(Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar) || Settings.data.bar.backgroundOpacity < 1.0) {
return false;
}
// A panel can only be attached to a bar if there is a bar on that screen
var monitors = Settings.data.bar.monitors || [];
var result = monitors.length === 0 || monitors.includes(root.screen?.name || "");
return result;
}
// Effective anchor properties (depend on allowAttach)
readonly property bool effectivePanelAnchorTop: panelAnchorTop || (useButtonPosition && barPosition === "top") || (allowAttach && !hasExplicitVerticalAnchor && barPosition === "top" && !barIsVertical)
readonly property bool effectivePanelAnchorBottom: panelAnchorBottom || (useButtonPosition && barPosition === "bottom") || (allowAttach && !hasExplicitVerticalAnchor && barPosition === "bottom" && !barIsVertical)
readonly property bool effectivePanelAnchorLeft: panelAnchorLeft || (useButtonPosition && barPosition === "left") || (allowAttach && !hasExplicitHorizontalAnchor && barPosition === "left" && barIsVertical)
readonly property bool effectivePanelAnchorRight: panelAnchorRight || (useButtonPosition && barPosition === "right") || (allowAttach && !hasExplicitHorizontalAnchor && barPosition === "right" && barIsVertical)
// Panel dimensions and visibility
visible: isPanelVisible
width: parent ? parent.width : 0
height: parent ? parent.height : 0
// Update position when UI scale changes
Connections {
target: Style
function onUiScaleRatioChanged() {
if (root.isPanelVisible) {
root.setPosition();
}
}
}
// Public function to update content size from SmartPanelWindow
function updateContentSize(w, h) {
contentPreferredWidth = w;
contentPreferredHeight = h;
if (isPanelVisible) {
setPosition();
}
}
// Main positioning calculation function
function setPosition() {
// Don't calculate position if parent dimensions aren't available yet
if (!root.width || !root.height) {
Logger.d("PanelPlaceholder", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height, panelName);
Qt.callLater(setPosition);
return;
}
// Calculate panel dimensions first (needed for positioning)
var w;
// Priority 1: Content-driven size (dynamic)
if (contentPreferredWidth > 0) {
w = contentPreferredWidth;
} // Priority 2: Ratio-based size
else if (root.preferredWidthRatio !== undefined) {
w = Math.round(Math.max(root.width * root.preferredWidthRatio, root.preferredWidth));
} // Priority 3: Static preferred width
else {
w = root.preferredWidth;
}
var panelWidth = Math.min(w, root.width - Style.marginL * 2);
var h;
// Priority 1: Content-driven size (dynamic)
if (contentPreferredHeight > 0) {
h = contentPreferredHeight;
} // Priority 2: Ratio-based size
else if (root.preferredHeightRatio !== undefined) {
h = Math.round(Math.max(root.height * root.preferredHeightRatio, root.preferredHeight));
} // Priority 3: Static preferred height
else {
h = root.preferredHeight;
}
var panelHeight = Math.min(h, root.height - Style.barHeight - Style.marginL * 2);
// Update panelBackground target size (will be animated)
panelBackground.targetWidth = panelWidth;
panelBackground.targetHeight = panelHeight;
// Calculate position
var calculatedX;
var calculatedY;
// ===== X POSITIONING =====
if (root.useButtonPosition && root.width > 0 && panelWidth > 0) {
if (root.barIsVertical) {
// For vertical bars
if (allowAttach) {
// Attached panels: align with bar edge (left or right side)
if (root.barPosition === "left") {
var leftBarEdge = root.barMarginH + Style.barHeight;
calculatedX = leftBarEdge;
} else {
// right
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
calculatedX = rightBarEdge - panelWidth;
}
} else {
// Detached panels: center on button X position
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
var minX = Style.marginL;
var maxX = root.width - panelWidth - Style.marginL;
// Account for vertical bar taking up space
if (root.barPosition === "left") {
minX = root.barMarginH + Style.barHeight + Style.marginL;
} else if (root.barPosition === "right") {
maxX = root.width - root.barMarginH - Style.barHeight - panelWidth - Style.marginL;
}
panelX = Math.max(minX, Math.min(panelX, maxX));
calculatedX = panelX;
}
} else {
// For horizontal bars, center panel on button X position
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
if (allowAttach) {
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
var barLeftEdge = root.barMarginH + cornerInset;
var barRightEdge = root.width - root.barMarginH - cornerInset;
panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth));
} else {
panelX = Math.max(Style.marginL, Math.min(panelX, root.width - panelWidth - Style.marginL));
}
calculatedX = panelX;
}
} else {
// Standard anchor positioning
if (root.panelAnchorHorizontalCenter) {
if (root.barIsVertical) {
if (root.barPosition === "left") {
var availableStart = root.barMarginH + Style.barHeight;
var availableWidth = root.width - availableStart;
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
} else if (root.barPosition === "right") {
var availableWidth = root.width - root.barMarginH - Style.barHeight;
calculatedX = (availableWidth - panelWidth) / 2;
} else {
calculatedX = (root.width - panelWidth) / 2;
}
} else {
calculatedX = (root.width - panelWidth) / 2;
}
} else if (root.effectivePanelAnchorRight) {
if (allowAttach && root.barIsVertical && root.barPosition === "right") {
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
calculatedX = rightBarEdge - panelWidth;
} else if (allowAttach) {
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
var rightCornerInset = Style.radiusL * 2;
calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth;
} else {
calculatedX = root.width - panelWidth;
}
} else {
calculatedX = root.width - panelWidth - Style.marginL;
}
} else if (root.effectivePanelAnchorLeft) {
if (allowAttach && root.barIsVertical && root.barPosition === "left") {
var leftBarEdge = root.barMarginH + Style.barHeight;
calculatedX = leftBarEdge;
} else if (allowAttach) {
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
var leftCornerInset = Style.radiusL * 2;
calculatedX = root.barMarginH + leftCornerInset;
} else {
calculatedX = 0;
}
} else {
calculatedX = Style.marginL;
}
} else {
// No explicit anchor: default to centering on bar
if (root.barIsVertical) {
if (root.barPosition === "left") {
var availableStart = root.barMarginH + Style.barHeight;
var availableWidth = root.width - availableStart - Style.marginL;
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
} else {
var availableWidth = root.width - root.barMarginH - Style.barHeight - Style.marginL;
calculatedX = Style.marginL + (availableWidth - panelWidth) / 2;
}
} else {
if (allowAttach) {
var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0);
var barLeftEdge = root.barMarginH + cornerInset;
var barRightEdge = root.width - root.barMarginH - cornerInset;
var centeredX = (root.width - panelWidth) / 2;
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth));
} else {
calculatedX = (root.width - panelWidth) / 2;
}
}
}
}
// Edge snapping for X
if (allowAttach && !root.barFloating && root.width > 0 && panelWidth > 0) {
var leftEdgePos = root.barMarginH;
if (root.barPosition === "left") {
leftEdgePos = root.barMarginH + Style.barHeight;
}
var rightEdgePos = root.width - root.barMarginH - panelWidth;
if (root.barPosition === "right") {
rightEdgePos = root.width - root.barMarginH - Style.barHeight - panelWidth;
}
// Only snap to left edge if panel is actually meant to be at left
var shouldSnapToLeft = root.effectivePanelAnchorLeft || (!root.hasExplicitHorizontalAnchor && root.barPosition === "left");
// Only snap to right edge if panel is actually meant to be at right
var shouldSnapToRight = root.effectivePanelAnchorRight || (!root.hasExplicitHorizontalAnchor && root.barPosition === "right");
if (shouldSnapToLeft && Math.abs(calculatedX - leftEdgePos) <= root.edgeSnapDistance) {
calculatedX = leftEdgePos;
} else if (shouldSnapToRight && Math.abs(calculatedX - rightEdgePos) <= root.edgeSnapDistance) {
calculatedX = rightEdgePos;
}
}
// ===== Y POSITIONING =====
if (root.useButtonPosition && root.height > 0 && panelHeight > 0) {
if (root.barPosition === "top") {
var topBarEdge = root.barMarginV + Style.barHeight;
if (allowAttach) {
calculatedY = topBarEdge;
} else {
calculatedY = topBarEdge + Style.marginM;
}
} else if (root.barPosition === "bottom") {
var bottomBarEdge = root.height - root.barMarginV - Style.barHeight;
if (allowAttach) {
calculatedY = bottomBarEdge - panelHeight;
} else {
calculatedY = bottomBarEdge - panelHeight - Style.marginM;
}
} else if (root.barIsVertical) {
var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2;
var extraPadding = (allowAttach && root.barFloating) ? Style.radiusL : 0;
if (allowAttach) {
var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0);
var barTopEdge = root.barMarginV + cornerInset;
var barBottomEdge = root.height - root.barMarginV - cornerInset;
panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight));
} else {
panelY = Math.max(Style.marginL + extraPadding, Math.min(panelY, root.height - panelHeight - Style.marginL - extraPadding));
}
calculatedY = panelY;
}
} else {
// Standard anchor positioning
var barOffset = 0;
if (!allowAttach) {
if (root.barPosition === "top") {
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
} else if (root.barPosition === "bottom") {
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
}
} else {
if (root.effectivePanelAnchorTop && root.barPosition === "top") {
calculatedY = root.barMarginV + Style.barHeight;
} else if (root.effectivePanelAnchorBottom && root.barPosition === "bottom") {
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
} else if (!root.hasExplicitVerticalAnchor) {
if (root.barPosition === "top") {
calculatedY = root.barMarginV + Style.barHeight;
} else if (root.barPosition === "bottom") {
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
}
}
}
if (calculatedY === undefined) {
if (root.panelAnchorVerticalCenter) {
if (!root.barIsVertical) {
if (root.barPosition === "top") {
var availableStart = root.barMarginV + Style.barHeight;
var availableHeight = root.height - availableStart;
calculatedY = availableStart + (availableHeight - panelHeight) / 2;
} else if (root.barPosition === "bottom") {
var availableHeight = root.height - root.barMarginV - Style.barHeight;
calculatedY = (availableHeight - panelHeight) / 2;
} else {
calculatedY = (root.height - panelHeight) / 2;
}
} else {
calculatedY = (root.height - panelHeight) / 2;
}
} else if (root.effectivePanelAnchorTop) {
if (allowAttach) {
calculatedY = 0;
} else {
var topBarOffset = (root.barPosition === "top") ? barOffset : 0;
calculatedY = topBarOffset + Style.marginL;
}
} else if (root.effectivePanelAnchorBottom) {
if (allowAttach) {
calculatedY = root.height - panelHeight;
} else {
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0;
calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL;
}
} else {
if (root.barIsVertical) {
if (allowAttach) {
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
var barTopEdge = root.barMarginV + cornerInset;
var barBottomEdge = root.height - root.barMarginV - cornerInset;
var centeredY = (root.height - panelHeight) / 2;
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight));
} else {
calculatedY = (root.height - panelHeight) / 2;
}
} else {
if (allowAttach && !root.barIsVertical) {
if (root.barPosition === "top") {
calculatedY = root.barMarginV + Style.barHeight;
} else if (root.barPosition === "bottom") {
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
}
} else {
if (root.barPosition === "top") {
calculatedY = barOffset + Style.marginL;
} else if (root.barPosition === "bottom") {
calculatedY = Style.marginL;
} else {
calculatedY = Style.marginL;
}
}
}
}
}
}
// Edge snapping for Y
if (allowAttach && !root.barFloating && root.height > 0 && panelHeight > 0) {
var topEdgePos = root.barMarginV;
if (root.barPosition === "top") {
topEdgePos = root.barMarginV + Style.barHeight;
}
var bottomEdgePos = root.height - root.barMarginV - panelHeight;
if (root.barPosition === "bottom") {
bottomEdgePos = root.height - root.barMarginV - Style.barHeight - panelHeight;
}
// Only snap to top edge if panel is actually meant to be at top
var shouldSnapToTop = root.effectivePanelAnchorTop || (!root.hasExplicitVerticalAnchor && root.barPosition === "top");
// Only snap to bottom edge if panel is actually meant to be at bottom
var shouldSnapToBottom = root.effectivePanelAnchorBottom || (!root.hasExplicitVerticalAnchor && root.barPosition === "bottom");
if (shouldSnapToTop && Math.abs(calculatedY - topEdgePos) <= root.edgeSnapDistance) {
calculatedY = topEdgePos;
} else if (shouldSnapToBottom && Math.abs(calculatedY - bottomEdgePos) <= root.edgeSnapDistance) {
calculatedY = bottomEdgePos;
}
}
// Apply calculated positions (set targets for animation)
panelBackground.targetX = calculatedX;
panelBackground.targetY = calculatedY;
Logger.d("PanelPlaceholder", "Position calculated:", calculatedX, calculatedY, panelName);
Logger.d("PanelPlaceholder", " Panel size:", panelWidth, "x", panelHeight);
}
// The panel background geometry item
Item {
id: panelBackground
// Store target dimensions (set by setPosition())
property real targetWidth: root.preferredWidth
property real targetHeight: root.preferredHeight
property real targetX: 0
property real targetY: 0
property var bezierCurve: [0.05, 0, 0.133, 0.06, 0.166, 0.4, 0.208, 0.82, 0.25, 1, 1, 1]
// Edge detection
readonly property bool touchingLeftEdge: allowAttach && panelBackground.x <= 1
readonly property bool touchingRightEdge: allowAttach && (panelBackground.x + panelBackground.width) >= (root.width - 1)
readonly property bool touchingTopEdge: allowAttach && panelBackground.y <= 1
readonly property bool touchingBottomEdge: allowAttach && (panelBackground.y + panelBackground.height) >= (root.height - 1)
// Bar edge detection
readonly property bool touchingTopBar: allowAttachToBar && root.barPosition === "top" && !root.barIsVertical && Math.abs(panelBackground.y - (root.barMarginV + Style.barHeight)) <= 1
readonly property bool touchingBottomBar: allowAttachToBar && root.barPosition === "bottom" && !root.barIsVertical && Math.abs((panelBackground.y + panelBackground.height) - (root.height - root.barMarginV - Style.barHeight)) <= 1
readonly property bool touchingLeftBar: allowAttachToBar && root.barPosition === "left" && root.barIsVertical && Math.abs(panelBackground.x - (root.barMarginH + Style.barHeight)) <= 1
readonly property bool touchingRightBar: allowAttachToBar && root.barPosition === "right" && root.barIsVertical && Math.abs((panelBackground.x + panelBackground.width) - (root.width - root.barMarginH - Style.barHeight)) <= 1
// Animation direction determination (using target position to avoid binding loops)
readonly property bool willTouchTopBar: {
if (!isPanelVisible)
return false;
if (!allowAttachToBar || root.barPosition !== "top" || root.barIsVertical)
return false;
var targetTopBarY = root.barMarginV + Style.barHeight;
return Math.abs(panelBackground.targetY - targetTopBarY) <= 1;
}
readonly property bool willTouchBottomBar: {
if (!isPanelVisible)
return false;
if (!allowAttachToBar || root.barPosition !== "bottom" || root.barIsVertical)
return false;
var targetBottomBarY = root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight;
return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1;
}
readonly property bool willTouchLeftBar: {
if (!isPanelVisible)
return false;
if (!allowAttachToBar || root.barPosition !== "left" || !root.barIsVertical)
return false;
var targetLeftBarX = root.barMarginH + Style.barHeight;
return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1;
}
readonly property bool willTouchRightBar: {
if (!isPanelVisible)
return false;
if (!allowAttachToBar || root.barPosition !== "right" || !root.barIsVertical)
return false;
var targetRightBarX = root.width - root.barMarginH - Style.barHeight - panelBackground.targetWidth;
return Math.abs(panelBackground.targetX - targetRightBarX) <= 1;
}
readonly property bool willTouchTopEdge: isPanelVisible && allowAttach && panelBackground.targetY <= 1
readonly property bool willTouchBottomEdge: isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
readonly property bool willTouchLeftEdge: isPanelVisible && allowAttach && panelBackground.targetX <= 1
readonly property bool willTouchRightEdge: isPanelVisible && allowAttach && (panelBackground.targetX + panelBackground.targetWidth) >= (root.width - 1)
readonly property bool isActuallyAttachedToAnyEdge: {
if (!isPanelVisible)
return false;
return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge;
}
readonly property bool animateFromTop: {
if (!isPanelVisible)
return true;
if (willTouchTopBar)
return true;
if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
return true;
if (!isActuallyAttachedToAnyEdge)
return true;
return false;
}
readonly property bool animateFromBottom: {
if (!isPanelVisible)
return false;
if (willTouchBottomBar)
return true;
if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
return true;
return false;
}
readonly property bool animateFromLeft: {
if (!isPanelVisible)
return false;
if (willTouchTopBar || willTouchBottomBar)
return false;
if (willTouchLeftBar)
return true;
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
if (touchingTopEdge || touchingBottomEdge)
return false;
if (willTouchLeftEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
return true;
return false;
}
readonly property bool animateFromRight: {
if (!isPanelVisible)
return false;
if (willTouchTopBar || willTouchBottomBar)
return false;
if (willTouchRightBar)
return true;
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
if (touchingTopEdge || touchingBottomEdge)
return false;
if (willTouchRightEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
return true;
return false;
}
readonly property bool shouldAnimateWidth: !shouldAnimateHeight && (animateFromLeft || animateFromRight)
readonly property bool shouldAnimateHeight: animateFromTop || animateFromBottom
// Track whether we're in an initial open/close state transition vs normal content resizing
readonly property bool isStateTransition: root.isOpening || root.isClosing
// Current animated width/height
readonly property real currentWidth: {
if (isClosing && opacityFadeComplete && shouldAnimateWidth)
return 0;
if (isClosing || isPanelVisible)
return targetWidth;
return 0;
}
readonly property real currentHeight: {
if (isClosing && opacityFadeComplete && shouldAnimateHeight)
return 0;
if (isClosing || isPanelVisible)
return targetHeight;
return 0;
}
width: currentWidth
height: currentHeight
x: {
if (animateFromRight) {
if (isPanelVisible || isClosing) {
var targetRightEdge = targetX + targetWidth;
return targetRightEdge - width;
}
}
return targetX;
}
y: {
if (animateFromBottom) {
if (isPanelVisible || isClosing) {
var targetBottomEdge = targetY + targetHeight;
return targetBottomEdge - height;
}
}
return targetY;
}
Behavior on width {
NumberAnimation {
// During opening: use 0ms if not animating width, otherwise use normal duration
// During closing: use 0ms if not animating width, otherwise use fast duration
// During normal content resizing: always use normal duration
duration: (root.isOpening && !panelBackground.shouldAnimateWidth) ? 0 : root.isOpening ? Style.animationNormal : (root.isClosing && !panelBackground.shouldAnimateWidth) ? 0 : root.isClosing ? Style.animationFast : Style.animationNormal
easing.type: Easing.BezierSpline
easing.bezierCurve: panelBackground.bezierCurve
}
}
Behavior on height {
NumberAnimation {
// During opening: use 0ms if not animating height, otherwise use normal duration
// During closing: use 0ms if not animating height, otherwise use fast duration
// During normal content resizing: always use normal duration
duration: (root.isOpening && !panelBackground.shouldAnimateHeight) ? 0 : root.isOpening ? Style.animationNormal : (root.isClosing && !panelBackground.shouldAnimateHeight) ? 0 : root.isClosing ? Style.animationFast : Style.animationNormal
easing.type: Easing.BezierSpline
easing.bezierCurve: panelBackground.bezierCurve
}
}
// Corner states for PanelBackground to read
property int topLeftCornerState: {
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
var barTouchInverted = touchingTopBar || touchingLeftBar;
var edgeInverted = allowAttach && (touchingLeftEdge || touchingTopEdge);
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
if (touchingLeftEdge && touchingTopEdge)
return 0;
if (touchingLeftEdge)
return 2;
if (touchingTopEdge)
return 1;
return root.barIsVertical ? 2 : 1;
}
return 0;
}
property int topRightCornerState: {
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
var barTouchInverted = touchingTopBar || touchingRightBar;
var edgeInverted = allowAttach && (touchingRightEdge || touchingTopEdge);
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
if (touchingRightEdge && touchingTopEdge)
return 0;
if (touchingRightEdge)
return 2;
if (touchingTopEdge)
return 1;
return root.barIsVertical ? 2 : 1;
}
return 0;
}
property int bottomLeftCornerState: {
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
var barTouchInverted = touchingBottomBar || touchingLeftBar;
var edgeInverted = allowAttach && (touchingLeftEdge || touchingBottomEdge);
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
if (touchingLeftEdge && touchingBottomEdge)
return 0;
if (touchingLeftEdge)
return 2;
if (touchingBottomEdge)
return 1;
return root.barIsVertical ? 2 : 1;
}
return 0;
}
property int bottomRightCornerState: {
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
var barTouchInverted = touchingBottomBar || touchingRightBar;
var edgeInverted = allowAttach && (touchingRightEdge || touchingBottomEdge);
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
if (touchingRightEdge && touchingBottomEdge)
return 0;
if (touchingRightEdge)
return 2;
if (touchingBottomEdge)
return 1;
return root.barIsVertical ? 2 : 1;
}
return 0;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,454 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services.Compositor
import qs.Services.UI
/**
* SmartPanelWindow - Separate window for panel content
*
* This component runs in its own window, separate from MainScreen.
* It follows the PanelPlaceholder for positioning and contains the actual panel content.
*/
PanelWindow {
id: root
// Required reference to placeholder
required property PanelPlaceholder placeholder
// Panel content component (set by SmartPanel wrapper)
property Component panelContent: null
// Reference to the SmartPanel wrapper (for keyboard handlers)
property var panelWrapper: null
// Keyboard focus
property bool exclusiveKeyboard: true
// Support close with escape
property bool closeWithEscape: true
// Track whether panel is open
property bool isPanelOpen: false
// Track actual visibility (delayed until content is loaded and sized)
property bool isPanelVisible: false
// Track size animation completion for sequential opacity animation
property bool sizeAnimationComplete: false
// Track close animation state
property bool isClosing: false
property bool opacityFadeComplete: false
property bool closeFinalized: false
// Safety: Watchdog timers
property bool closeWatchdogActive: false
property bool openWatchdogActive: false
// Signals
signal panelOpened
signal panelClosed
// Window configuration
color: Color.transparent
mask: null // No mask - content window is rectangular
visible: isPanelOpen
screen: placeholder.screen // Explicitly set screen to match placeholder
// Wayland layer shell configuration - fullscreen window
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "noctalia-panel-content-" + placeholder.panelName + "-" + (placeholder.screen?.name || "unknown")
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: {
if (!root.isPanelOpen) {
return WlrKeyboardFocus.None;
}
if (CompositorService.isHyprland) {
// Exclusive focus on hyprland is too restrictive.
return WlrKeyboardFocus.OnDemand;
} else {
return root.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand;
}
}
// Anchor to all edges to make fullscreen
anchors {
top: true
bottom: true
left: true
right: true
}
// Margins to exclude bar area so bar remains clickable
margins {
top: placeholder.barPosition === "top" ? (placeholder.barMarginV + Style.barHeight) : 0
bottom: placeholder.barPosition === "bottom" ? (placeholder.barMarginV + Style.barHeight) : 0
left: placeholder.barPosition === "left" ? (placeholder.barMarginH + Style.barHeight) : 0
right: placeholder.barPosition === "right" ? (placeholder.barMarginH + Style.barHeight) : 0
}
// Sync state to placeholder
onIsPanelVisibleChanged: {
placeholder.isPanelVisible = isPanelVisible;
}
onIsClosingChanged: {
placeholder.isClosing = isClosing;
}
onOpacityFadeCompleteChanged: {
placeholder.opacityFadeComplete = opacityFadeComplete;
}
onSizeAnimationCompleteChanged: {
placeholder.sizeAnimationComplete = sizeAnimationComplete;
}
// Panel control functions
function toggle(buttonItem, buttonName) {
if (!isPanelOpen) {
open(buttonItem, buttonName);
} else {
close();
}
}
function open(buttonItem, buttonName) {
if (!buttonItem && buttonName) {
buttonItem = BarService.lookupWidget(buttonName, placeholder.screen.name);
}
if (buttonItem) {
placeholder.buttonItem = buttonItem;
// Map button position to screen coordinates
var buttonPos = buttonItem.mapToItem(null, 0, 0);
placeholder.buttonPosition = Qt.point(buttonPos.x, buttonPos.y);
placeholder.buttonWidth = buttonItem.width;
placeholder.buttonHeight = buttonItem.height;
placeholder.useButtonPosition = true;
} else {
// No button provided: reset button position mode
placeholder.buttonItem = null;
placeholder.useButtonPosition = false;
}
// Set isPanelOpen to trigger content loading
isPanelOpen = true;
// Notify PanelService
PanelService.willOpenPanel(root);
}
function close() {
// Start close sequence: fade opacity first
isClosing = true;
sizeAnimationComplete = false;
closeFinalized = false;
// Stop the open animation timer if it's still running
opacityTrigger.stop();
openWatchdogActive = false;
openWatchdogTimer.stop();
// Start close watchdog timer
closeWatchdogActive = true;
closeWatchdogTimer.restart();
// If opacity is already 0, skip directly to size animation
if (contentWrapper.opacity === 0.0) {
opacityFadeComplete = true;
} else {
opacityFadeComplete = false;
}
Logger.d("SmartPanelWindow", "Closing panel", placeholder.panelName);
}
function finalizeClose() {
// Prevent double-finalization
if (root.closeFinalized) {
Logger.w("SmartPanelWindow", "finalizeClose called but already finalized - ignoring", placeholder.panelName);
return;
}
// Complete the close sequence after animations finish
root.closeFinalized = true;
root.closeWatchdogActive = false;
closeWatchdogTimer.stop();
root.isPanelVisible = false;
root.isPanelOpen = false;
root.isClosing = false;
root.opacityFadeComplete = false;
PanelService.closedPanel(root);
panelClosed();
Logger.d("SmartPanelWindow", "Panel close finalized", placeholder.panelName);
}
// Fullscreen container for click-to-close and content
Item {
anchors.fill: parent
focus: true // Enable keyboard event handling
// Handle keyboard events directly via Keys handler
Keys.onPressed: event => {
Logger.d("SmartPanelWindow", "Key pressed:", event.key, "for panel:", placeholder.panelName);
if (event.key === Qt.Key_Escape) {
panelWrapper.onEscapePressed();
if (closeWithEscape) {
root.close();
event.accepted = true;
}
} else if (panelWrapper) {
if (event.key === Qt.Key_Up && panelWrapper.onUpPressed) {
panelWrapper.onUpPressed();
event.accepted = true;
} else if (event.key === Qt.Key_Down && panelWrapper.onDownPressed) {
panelWrapper.onDownPressed();
event.accepted = true;
} else if (event.key === Qt.Key_Left && panelWrapper.onLeftPressed) {
panelWrapper.onLeftPressed();
event.accepted = true;
} else if (event.key === Qt.Key_Right && panelWrapper.onRightPressed) {
panelWrapper.onRightPressed();
event.accepted = true;
} else if (event.key === Qt.Key_Tab && panelWrapper.onTabPressed) {
panelWrapper.onTabPressed();
event.accepted = true;
} else if (event.key === Qt.Key_Backtab && panelWrapper.onBackTabPressed) {
panelWrapper.onBackTabPressed();
event.accepted = true;
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && panelWrapper.onReturnPressed) {
panelWrapper.onReturnPressed();
event.accepted = true;
} else if (event.key === Qt.Key_Home && panelWrapper.onHomePressed) {
panelWrapper.onHomePressed();
event.accepted = true;
} else if (event.key === Qt.Key_End && panelWrapper.onEndPressed) {
panelWrapper.onEndPressed();
event.accepted = true;
} else if (event.key === Qt.Key_PageUp && panelWrapper.onPageUpPressed) {
panelWrapper.onPageUpPressed();
event.accepted = true;
} else if (event.key === Qt.Key_PageDown && panelWrapper.onPageDownPressed) {
panelWrapper.onPageDownPressed();
event.accepted = true;
} else if (event.key === Qt.Key_J && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlJPressed) {
panelWrapper.onCtrlJPressed();
event.accepted = true;
} else if (event.key === Qt.Key_K && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlKPressed) {
panelWrapper.onCtrlKPressed();
event.accepted = true;
} else if (event.key === Qt.Key_N && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlNPressed) {
panelWrapper.onCtrlNPressed();
event.accepted = true;
} else if (event.key === Qt.Key_P && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlPPressed) {
panelWrapper.onCtrlPPressed();
event.accepted = true;
}
}
}
// Background MouseArea for click-to-close (behind content)
MouseArea {
anchors.fill: parent
enabled: root.isPanelOpen && !root.isClosing
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
root.close();
mouse.accepted = true;
}
z: 0
}
// Content wrapper with opacity animation
Item {
id: contentWrapper
// Position at placeholder location, compensating for window margins
x: placeholder.panelItem.x - (placeholder.barPosition === "left" ? (placeholder.barMarginH + Style.barHeight) : 0)
y: placeholder.panelItem.y - (placeholder.barPosition === "top" ? (placeholder.barMarginV + Style.barHeight) : 0)
width: placeholder.panelItem.width
height: placeholder.panelItem.height
z: 1 // Above click-to-close MouseArea
// Opacity animation
opacity: {
if (isClosing)
return 0.0;
if (isPanelVisible && sizeAnimationComplete)
return 1.0;
return 0.0;
}
Behavior on opacity {
NumberAnimation {
id: opacityAnimation
duration: root.isClosing ? Style.animationFaster : Style.animationFast
easing.type: Easing.OutQuad
onRunningChanged: {
// Safety: Zero-duration animation handling
if (!running && duration === 0) {
if (root.isClosing && contentWrapper.opacity === 0.0) {
root.opacityFadeComplete = true;
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
if (shouldFinalizeNow) {
Logger.d("SmartPanelWindow", "Zero-duration opacity + no size animation - finalizing", placeholder.panelName);
Qt.callLater(root.finalizeClose);
}
} else if (root.isPanelVisible && contentWrapper.opacity === 1.0) {
root.openWatchdogActive = false;
openWatchdogTimer.stop();
}
return;
}
// When opacity fade completes during close, trigger size animation
if (!running && root.isClosing && contentWrapper.opacity === 0.0) {
root.opacityFadeComplete = true;
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
if (shouldFinalizeNow) {
Logger.d("SmartPanelWindow", "No animation - finalizing immediately", placeholder.panelName);
Qt.callLater(root.finalizeClose);
} else {
Logger.d("SmartPanelWindow", "Animation will run - waiting for size animation", placeholder.panelName);
}
} // When opacity fade completes during open, stop watchdog
else if (!running && root.isPanelVisible && contentWrapper.opacity === 1.0) {
root.openWatchdogActive = false;
openWatchdogTimer.stop();
}
}
}
}
// Panel content loader
Loader {
id: contentLoader
active: isPanelOpen
anchors.fill: parent
sourceComponent: root.panelContent
// When content finishes loading, trigger positioning and visibility
onLoaded: {
// Capture initial content-driven size if available
if (contentLoader.item) {
var hasWidthProp = contentLoader.item.hasOwnProperty('contentPreferredWidth');
var hasHeightProp = contentLoader.item.hasOwnProperty('contentPreferredHeight');
if (hasWidthProp || hasHeightProp) {
var initialWidth = hasWidthProp ? contentLoader.item.contentPreferredWidth : 0;
var initialHeight = hasHeightProp ? contentLoader.item.contentPreferredHeight : 0;
placeholder.updateContentSize(initialWidth, initialHeight);
Logger.d("SmartPanelWindow", "Initial content size:", initialWidth, "x", initialHeight, placeholder.panelName);
}
}
// Calculate position in placeholder
placeholder.setPosition();
// Make panel visible on the next frame
Qt.callLater(function () {
root.isPanelVisible = true;
opacityTrigger.start();
// Start open watchdog timer
root.openWatchdogActive = true;
openWatchdogTimer.start();
panelOpened();
});
}
}
// MouseArea to prevent clicks on panel content from closing it
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
mouse.accepted = true; // Eat the click to prevent propagation to background
}
z: -1 // Behind content but above background click-to-close
}
// Watch for changes in content-driven sizes
Connections {
target: contentLoader.item
ignoreUnknownSignals: true
function onContentPreferredWidthChanged() {
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
placeholder.updateContentSize(contentLoader.item.contentPreferredWidth, placeholder.contentPreferredHeight);
}
}
function onContentPreferredHeightChanged() {
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
placeholder.updateContentSize(placeholder.contentPreferredWidth, contentLoader.item.contentPreferredHeight);
}
}
}
}
}
// Timer to trigger opacity fade at 50% of size animation
Timer {
id: opacityTrigger
interval: Style.animationNormal * 0.5
repeat: false
onTriggered: {
if (root.isPanelVisible) {
root.sizeAnimationComplete = true;
}
}
}
// Watchdog timer for open sequence
Timer {
id: openWatchdogTimer
interval: Style.animationNormal * 3
repeat: false
onTriggered: {
if (root.openWatchdogActive) {
Logger.w("SmartPanelWindow", "Open watchdog timeout - forcing panel visible state", placeholder.panelName);
root.openWatchdogActive = false;
if (root.isPanelOpen && !root.isPanelVisible) {
root.isPanelVisible = true;
root.sizeAnimationComplete = true;
}
}
}
}
// Watchdog timer for close sequence
Timer {
id: closeWatchdogTimer
interval: Style.animationFast * 3
repeat: false
onTriggered: {
if (root.closeWatchdogActive && !root.closeFinalized) {
Logger.w("SmartPanelWindow", "Close watchdog timeout - forcing panel close", placeholder.panelName);
Qt.callLater(root.finalizeClose);
}
}
}
// Watch for placeholder size animation completion to finalize close
Connections {
target: placeholder.panelItem
function onWidthChanged() {
// When width shrinks to 0 during close and we're animating width, finalize
if (root.isClosing && placeholder.panelItem.width === 0 && placeholder.panelItem.shouldAnimateWidth) {
Qt.callLater(root.finalizeClose);
}
}
function onHeightChanged() {
// When height shrinks to 0 during close and we're animating height, finalize
if (root.isClosing && placeholder.panelItem.height === 0 && placeholder.panelItem.shouldAnimateHeight) {
Qt.callLater(root.finalizeClose);
}
}
}
}

View File

@@ -22,7 +22,7 @@ Variants {
property ListModel notificationModel: NotificationService.activeList
// Always create window (but with 0x0 dimensions when no notifications)
active: true
active: (notificationModel.count > 0 || delayTimer.running) && Settings.data.notifications?.location != "bar"
// Keep loader active briefly after last notification to allow animations to complete
Timer {
@@ -104,8 +104,8 @@ Variants {
margins.left: isLeft ? barOffsetLeft : 0
margins.right: isRight ? barOffsetRight : 0
implicitWidth: (notificationModel.count > 0 || delayTimer.running) ? notifWidth : 0
implicitHeight: (notificationModel.count > 0 || delayTimer.running) ? (notificationStack.implicitHeight + Style.marginL) : 0
implicitWidth: notifWidth
implicitHeight: notificationStack.implicitHeight + Style.marginL
property var animateConnection: null
@@ -398,17 +398,16 @@ Variants {
Layout.topMargin: Style.marginM
Layout.bottomMargin: Style.marginM
ColumnLayout {
NImageCircled {
Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio)
Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio)
Layout.alignment: Qt.AlignVCenter
imagePath: model.originalImage || ""
borderColor: Color.transparent
borderWidth: 0
fallbackIcon: "bell"
fallbackIconSize: 24
}
NImageRounded {
Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio)
Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio)
Layout.alignment: Qt.AlignVCenter
radius: width * 0.5
imagePath: model.originalImage || ""
borderColor: Color.transparent
borderWidth: 0
fallbackIcon: "bell"
fallbackIconSize: 24
}
ColumnLayout {

View File

@@ -5,11 +5,22 @@ import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services.Hardware
import qs.Services.Keyboard
import qs.Services.Media
import qs.Widgets
// Unified OSD component that displays volume, input volume, and brightness changes
Variants {
id: osd
// Do not change the order or it will break settings.
enum Type {
Volume,
InputVolume,
Brightness,
LockKey
}
model: Quickshell.screens.filter(screen => (Settings.data.osd.monitors.includes(screen.name) || Settings.data.osd.monitors.length === 0) && Settings.data.osd.enabled)
delegate: Loader {
@@ -20,31 +31,39 @@ Variants {
active: false
// OSD State
property string currentOSDType: "" // "volume", "inputVolume", "brightness", or ""
property int currentOSDType: -1 // OSD.Type enum value, -1 means none
property bool startupComplete: false
property real currentBrightness: 0
// Lock Key States
property string lastLockKeyChanged: "" // "caps", "num", "scroll", or ""
// Current values (computed properties)
readonly property real currentVolume: AudioService.volume
readonly property bool isMuted: AudioService.muted
readonly property real currentInputVolume: AudioService.inputVolume
readonly property bool isInputMuted: AudioService.inputMuted
readonly property real epsilon: 0.005
// ============================================================================
// Helper Functions
// ============================================================================
function getIcon() {
switch (currentOSDType) {
case "volume":
case OSD.Type.Volume:
if (isMuted)
return "volume-mute";
if (currentVolume <= Number.EPSILON)
return "volume-zero";
// Show volume-x icon when volume is effectively 0% (within rounding threshold)
if (currentVolume < root.epsilon)
return "volume-x";
return currentVolume <= 0.5 ? "volume-low" : "volume-high";
case "inputVolume":
case OSD.Type.InputVolume:
return isInputMuted ? "microphone-off" : "microphone";
case "brightness":
case OSD.Type.Brightness:
// Show sun-off icon when brightness is effectively 0% (within rounding threshold)
if (currentBrightness < root.epsilon)
return "sun-off";
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high";
case OSD.Type.LockKey:
return "keyboard";
default:
return "";
}
@@ -52,28 +71,35 @@ Variants {
function getCurrentValue() {
switch (currentOSDType) {
case "volume":
case OSD.Type.Volume:
return isMuted ? 0 : currentVolume;
case "inputVolume":
case OSD.Type.InputVolume:
return isInputMuted ? 0 : currentInputVolume;
case "brightness":
case OSD.Type.Brightness:
return currentBrightness;
case OSD.Type.LockKey:
return 1.0; // Always show 100% when showing lock key status
default:
return 0;
}
}
function getMaxValue() {
if (currentOSDType === "volume" || currentOSDType === "inputVolume") {
if (currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) {
return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0;
}
return 1.0;
}
function getDisplayPercentage() {
if (currentOSDType === OSD.Type.LockKey) {
// For lock keys, return the pre-determined status text
return lastLockKeyChanged;
}
const value = getCurrentValue();
const max = getMaxValue();
if ((currentOSDType === "volume" || currentOSDType === "inputVolume") && Settings.data.audio.volumeOverdrive) {
if ((currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) && Settings.data.audio.volumeOverdrive) {
const pct = Math.round(value * 100);
return pct + "%";
}
@@ -82,28 +108,51 @@ Variants {
}
function getProgressColor() {
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted);
if (isMutedState) {
return Color.mError;
}
// When volumeOverdrive is enabled, show error color if volume is above 100%
if ((currentOSDType === "volume" || currentOSDType === "inputVolume") && Settings.data.audio.volumeOverdrive) {
if ((currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) && Settings.data.audio.volumeOverdrive) {
const value = getCurrentValue();
if (value > 1.0) {
return Color.mError;
}
}
// For lock keys, use a different color to indicate the lock state
if (currentOSDType === OSD.Type.LockKey) {
// Check the specific lock key that was changed
if (lastLockKeyChanged.startsWith("CAPS")) {
return LockKeysService.capsLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
} else if (lastLockKeyChanged.startsWith("NUM")) {
return LockKeysService.numLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
} else if (lastLockKeyChanged.startsWith("SCROLL")) {
return LockKeysService.scrollLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
}
}
return Color.mPrimary;
}
function getIconColor() {
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
return isMutedState ? Color.mError : Color.mOnSurface;
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted);
if (isMutedState)
return Color.mError;
if (currentOSDType === OSD.Type.LockKey) {
// Check the specific lock key that was changed
if (lastLockKeyChanged.startsWith("CAPS")) {
return LockKeysService.capsLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
} else if (lastLockKeyChanged.startsWith("NUM")) {
return LockKeysService.numLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
} else if (lastLockKeyChanged.startsWith("SCROLL")) {
return LockKeysService.scrollLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
}
}
return Color.mOnSurface;
}
// ============================================================================
// Brightness Handling
// ============================================================================
function connectBrightnessMonitors() {
for (var i = 0; i < BrightnessService.monitors.length; i++) {
const monitor = BrightnessService.monitors[i];
@@ -114,17 +163,28 @@ Variants {
function onBrightnessChanged(newBrightness) {
currentBrightness = newBrightness;
showOSD("brightness");
showOSD(OSD.Type.Brightness);
}
// Check if a specific OSD type is enabled
function isTypeEnabled(type) {
const enabledTypes = Settings.data.osd.enabledTypes || [];
// If enabledTypes is empty, all types are enabled (backwards compatibility)
if (enabledTypes.length === 0)
return true;
return enabledTypes.includes(type);
}
// ============================================================================
// OSD Display Control
// ============================================================================
function showOSD(type) {
// Ignore all OSD requests during startup period
if (!startupComplete)
return;
// Check if this OSD type is enabled
if (!isTypeEnabled(type))
return;
currentOSDType = type;
if (!root.active) {
@@ -149,30 +209,52 @@ Variants {
}
}
// ============================================================================
// Signal Connections
// ============================================================================
// AudioService monitoring
Connections {
target: AudioService
function onVolumeChanged() {
showOSD("volume");
showOSD(OSD.Type.Volume);
}
function onMutedChanged() {
showOSD("volume");
if (AudioService.consumeOutputOSDSuppression())
return;
showOSD(OSD.Type.Volume);
}
function onInputVolumeChanged() {
if (AudioService.hasInput)
showOSD("inputVolume");
showOSD(OSD.Type.InputVolume);
}
function onInputMutedChanged() {
if (AudioService.hasInput)
showOSD("inputVolume");
if (!AudioService.hasInput)
return;
if (AudioService.consumeInputOSDSuppression())
return;
showOSD(OSD.Type.InputVolume);
}
// Refresh OSD when device changes to ensure correct volume is displayed
function onSinkChanged() {
// If volume OSD is currently showing, refresh it to show new device's volume
if (root.currentOSDType === OSD.Type.Volume) {
Qt.callLater(() => {
showOSD(OSD.Type.Volume);
});
}
}
function onSourceChanged() {
// If input volume OSD is currently showing, refresh it to show new device's volume
if (root.currentOSDType === OSD.Type.InputVolume) {
Qt.callLater(() => {
showOSD(OSD.Type.InputVolume);
});
}
}
}
@@ -184,6 +266,26 @@ Variants {
}
}
// LockKeys monitoring with a cleaner approach
Connections {
target: LockKeysService
function onCapsLockChanged(active) {
root.lastLockKeyChanged = active ? "CAPS ON" : "CAPS OFF";
root.showOSD(OSD.Type.LockKey);
}
function onNumLockChanged(active) {
root.lastLockKeyChanged = active ? "NUM ON" : "NUM OFF";
root.showOSD(OSD.Type.LockKey);
}
function onScrollLockChanged(active) {
root.lastLockKeyChanged = active ? "SCROLL ON" : "SCROLL OFF";
root.showOSD(OSD.Type.LockKey);
}
}
// Startup timer - connect brightness monitors and enable OSD after 2 seconds
Timer {
id: startupTimer
@@ -195,9 +297,7 @@ Variants {
}
}
// ============================================================================
// Visual Component
// ============================================================================
sourceComponent: PanelWindow {
id: panel
screen: modelData
@@ -211,10 +311,14 @@ Variants {
readonly property bool verticalMode: location === "left" || location === "right"
// Dimensions
readonly property int hWidth: Math.round(320 * Style.uiScaleRatio)
readonly property int hHeight: Math.round(72 * Style.uiScaleRatio)
readonly property int vWidth: Math.round(80 * Style.uiScaleRatio)
readonly property int vHeight: Math.round(280 * Style.uiScaleRatio)
readonly property bool isShortMode: root.currentOSDType === OSD.Type.LockKey
readonly property int longHWidth: Math.round(320 * Style.uiScaleRatio)
readonly property int longHHeight: Math.round(72 * Style.uiScaleRatio)
readonly property int shortHWidth: Math.round(180 * Style.uiScaleRatio)
readonly property int longVWidth: Math.round(80 * Style.uiScaleRatio)
readonly property int longVHeight: Math.round(280 * Style.uiScaleRatio)
readonly property int shortVHeight: Math.round(180 * Style.uiScaleRatio)
readonly property int barThickness: {
const base = Math.max(8, Math.round(8 * Style.uiScaleRatio));
return base % 2 === 0 ? base : base + 1;
@@ -243,8 +347,8 @@ Variants {
margins.left: calculateMargin(anchors.left, "left")
margins.right: calculateMargin(anchors.right, "right")
implicitWidth: verticalMode ? vWidth : hWidth
implicitHeight: verticalMode ? vHeight : hHeight
implicitWidth: verticalMode ? longVWidth : (isShortMode ? shortHWidth : longHWidth)
implicitHeight: verticalMode ? (isShortMode ? shortVHeight : longVHeight) : longHHeight
color: Color.transparent
WlrLayershell.namespace: "noctalia-osd-" + (screen?.name || "unknown")
@@ -284,7 +388,8 @@ Variants {
interval: Style.animationNormal + 50
onTriggered: {
osdItem.visible = false;
root.currentOSDType = "";
root.currentOSDType = -1;
root.lastLockKeyChanged = ""; // Reset the lock key change indicator
root.active = false;
}
}
@@ -325,7 +430,7 @@ Variants {
spacing: Style.marginM
clip: true
// TextMetrics to measure the maximum possible percentage width (150%)
// TextMetrics to measure the maximum possible percentage width
TextMetrics {
id: percentageMetrics
font.family: Settings.data.ui.fontFixed
@@ -334,6 +439,7 @@ Variants {
text: "150%" // Maximum possible value with volumeOverdrive
}
// Common Icon for all types
NIcon {
icon: root.getIcon()
color: root.getIconColor()
@@ -348,7 +454,22 @@ Variants {
}
}
// Lock Key Status Text (replaces progress bar)
NText {
visible: root.currentOSDType === OSD.Type.LockKey
text: root.getDisplayPercentage()
color: root.getProgressColor()
pointSize: Style.fontSizeM
family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightMedium
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignVCenter
}
// Progress Bar for Volume/Brightness
Rectangle {
visible: root.currentOSDType !== OSD.Type.LockKey
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
height: panel.barThickness
@@ -369,7 +490,6 @@ Variants {
easing.type: Easing.InOutQuad
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
@@ -379,7 +499,9 @@ Variants {
}
}
// Percentage Text for Volume/Brightness
NText {
visible: root.currentOSDType !== OSD.Type.LockKey
text: root.getDisplayPercentage()
color: Color.mOnSurface
pointSize: Style.fontSizeS
@@ -401,21 +523,27 @@ Variants {
anchors.fill: parent
anchors.topMargin: Style.marginL
anchors.bottomMargin: Style.marginL
spacing: Style.marginS
spacing: root.currentOSDType === OSD.Type.LockKey ? Style.marginM : Style.marginS
clip: true
// Unified Text display for Percentage or Lock Status
NText {
text: root.getDisplayPercentage()
color: Color.mOnSurface
pointSize: Style.fontSizeS
color: root.currentOSDType === OSD.Type.LockKey ? root.getProgressColor() : Color.mOnSurface
pointSize: root.currentOSDType === OSD.Type.LockKey ? Style.fontSizeM : Style.fontSizeS
family: Settings.data.ui.fontFixed
font.weight: root.currentOSDType === OSD.Type.LockKey ? Style.fontWeightMedium : Style.fontWeightRegular
Layout.fillWidth: true
Layout.preferredHeight: Math.round(20 * Style.uiScaleRatio)
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
// Only set preferredHeight for the standard case to maintain layout
Layout.preferredHeight: root.currentOSDType === OSD.Type.LockKey ? -1 : Math.round(20 * Style.uiScaleRatio)
}
// Progress Bar for Volume/Brightness
Item {
visible: root.currentOSDType !== OSD.Type.LockKey
Layout.fillWidth: true
Layout.fillHeight: true
@@ -441,7 +569,6 @@ Variants {
easing.type: Easing.InOutQuad
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
@@ -452,11 +579,12 @@ Variants {
}
}
// Unified Icon display
NIcon {
icon: root.getIcon()
color: root.getIconColor()
pointSize: Style.fontSizeL
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
pointSize: root.currentOSDType === OSD.Type.LockKey ? Style.fontSizeXL : Style.fontSizeL
Layout.alignment: root.currentOSDType === OSD.Type.LockKey ? Qt.AlignHCenter : (Qt.AlignHCenter | Qt.AlignBottom)
Behavior on color {
ColorAnimation {
@@ -495,7 +623,7 @@ Variants {
osdItem.opacity = 0;
osdItem.scale = 0.85;
osdItem.visible = false;
root.currentOSDType = "";
root.currentOSDType = -1;
root.active = false;
}
}

View File

@@ -13,43 +13,105 @@ SmartPanel {
property real localOutputVolume: AudioService.volume || 0
property bool localOutputVolumeChanging: false
property int lastSinkId: -1
property real localInputVolume: AudioService.inputVolume || 0
property bool localInputVolumeChanging: false
property int lastSourceId: -1
preferredWidth: Math.round(340 * Style.uiScaleRatio)
preferredHeight: Math.round(420 * Style.uiScaleRatio)
// Reset local volume when device changes - use current device's volume
Connections {
target: AudioService
function onSinkChanged() {
if (AudioService.sink) {
const newSinkId = AudioService.sink.id;
if (newSinkId !== lastSinkId) {
lastSinkId = newSinkId;
// Immediately set local volume to current device's volume
localOutputVolume = AudioService.volume;
}
} else {
lastSinkId = -1;
localOutputVolume = 0;
}
}
}
Connections {
target: AudioService
function onSourceChanged() {
if (AudioService.source) {
const newSourceId = AudioService.source.id;
if (newSourceId !== lastSourceId) {
lastSourceId = newSourceId;
// Immediately set local volume to current device's volume
localInputVolume = AudioService.inputVolume;
}
} else {
lastSourceId = -1;
localInputVolume = 0;
}
}
}
// Connections to update local volumes when AudioService changes
Connections {
target: AudioService
function onVolumeChanged() {
if (!localOutputVolumeChanging && AudioService.sink && AudioService.sink.id === lastSinkId) {
localOutputVolume = AudioService.volume;
}
}
}
Connections {
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
function onVolumeChanged() {
if (!localOutputVolumeChanging) {
if (!localOutputVolumeChanging && AudioService.sink && AudioService.sink.id === lastSinkId) {
localOutputVolume = AudioService.volume;
}
}
}
Connections {
target: AudioService
function onInputVolumeChanged() {
if (!localInputVolumeChanging && AudioService.source && AudioService.source.id === lastSourceId) {
localInputVolume = AudioService.inputVolume;
}
}
}
Connections {
target: AudioService.source?.audio ? AudioService.source?.audio : null
function onVolumeChanged() {
if (!localInputVolumeChanging) {
if (!localInputVolumeChanging && AudioService.source && AudioService.source.id === lastSourceId) {
localInputVolume = AudioService.inputVolume;
}
}
}
// Timer to debounce volume changes
// Only sync if the device hasn't changed (check by comparing IDs)
Timer {
interval: 100
running: true
repeat: true
onTriggered: {
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
AudioService.setVolume(localOutputVolume);
// Only sync if sink hasn't changed
if (AudioService.sink && AudioService.sink.id === lastSinkId) {
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
AudioService.setVolume(localOutputVolume);
}
}
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
AudioService.setInputVolume(localInputVolume);
// Only sync if source hasn't changed
if (AudioService.source && AudioService.source.id === lastSourceId) {
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
AudioService.setInputVolume(localInputVolume);
}
}
}
}
@@ -95,6 +157,7 @@ SmartPanel {
tooltipText: I18n.tr("tooltips.output-muted")
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
AudioService.suppressOutputOSD();
AudioService.setOutputMuted(!AudioService.muted);
}
}
@@ -104,6 +167,7 @@ SmartPanel {
tooltipText: I18n.tr("tooltips.input-muted")
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
AudioService.suppressInputOSD();
AudioService.setInputMuted(!AudioService.inputMuted);
}
}

View File

@@ -6,7 +6,7 @@ import Quickshell.Services.UPower
import qs.Commons
import qs.Modules.MainScreen
import qs.Services.Hardware
import qs.Services.Power
import qs.Services.Networking
import qs.Widgets
SmartPanel {
@@ -15,17 +15,133 @@ SmartPanel {
preferredWidth: Math.round(360 * Style.uiScaleRatio)
preferredHeight: Math.round(460 * Style.uiScaleRatio)
readonly property var battery: UPower.displayDevice
readonly property bool isReady: battery && battery.ready && battery.isLaptopBattery && battery.isPresent
readonly property int percent: isReady ? Math.round(battery.percentage * 100) : -1
// Get device selection from Battery widget settings (check right section first, then any Battery widget)
function getBatteryDevicePath() {
// Check right section first (most common location for Battery widget)
var rightWidgets = Settings.data.bar.widgets.right || [];
for (var i = 0; i < rightWidgets.length; i++) {
if (rightWidgets[i].id === "Battery" && rightWidgets[i].deviceNativePath) {
return rightWidgets[i].deviceNativePath;
}
}
// Check other sections
var sections = ["left", "center"];
for (var s = 0; s < sections.length; s++) {
var widgets = Settings.data.bar.widgets[sections[s]] || [];
for (var j = 0; j < widgets.length; j++) {
if (widgets[j].id === "Battery" && widgets[j].deviceNativePath) {
return widgets[j].deviceNativePath;
}
}
}
return "";
}
// Helper function to find battery device by nativePath
function findBatteryDevice(nativePath) {
if (!nativePath || nativePath === "") {
return UPower.displayDevice;
}
if (!UPower.devices) {
return UPower.displayDevice;
}
var deviceArray = UPower.devices.values || [];
for (var i = 0; i < deviceArray.length; i++) {
var device = deviceArray[i];
if (device && device.nativePath === nativePath) {
if (device.type === UPowerDeviceType.LinePower) {
continue;
}
if (device.percentage !== undefined) {
return device;
}
}
}
return UPower.displayDevice;
}
// Helper function to find Bluetooth device by MAC address from nativePath
function findBluetoothDevice(nativePath) {
if (!nativePath || !BluetoothService.devices) {
return null;
}
var macMatch = nativePath.match(/([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})/);
if (!macMatch) {
return null;
}
var macAddress = macMatch[1].toUpperCase();
var deviceArray = BluetoothService.devices.values || [];
for (var i = 0; i < deviceArray.length; i++) {
var device = deviceArray[i];
if (device && device.address && device.address.toUpperCase() === macAddress) {
return device;
}
}
return null;
}
readonly property string deviceNativePath: getBatteryDevicePath()
readonly property var battery: findBatteryDevice(deviceNativePath)
readonly property var bluetoothDevice: deviceNativePath ? findBluetoothDevice(deviceNativePath) : null
readonly property bool hasBluetoothBattery: bluetoothDevice && bluetoothDevice.batteryAvailable && bluetoothDevice.battery !== undefined
readonly property bool isBluetoothConnected: bluetoothDevice && bluetoothDevice.connected !== undefined ? bluetoothDevice.connected : false
// Check if device is actually present/connected
readonly property bool isDevicePresent: {
if (deviceNativePath && deviceNativePath !== "") {
if (bluetoothDevice) {
return isBluetoothConnected;
}
if (battery && battery.nativePath === deviceNativePath) {
if (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) {
return battery.isPresent;
}
return battery.ready && battery.percentage !== undefined && (battery.percentage > 0 || battery.state === UPowerDeviceState.Charging);
}
return false;
}
if (battery) {
if (battery.type === UPowerDeviceType.Battery && battery.isPresent !== undefined) {
return battery.isPresent;
}
return battery.ready && battery.percentage !== undefined;
}
return false;
}
readonly property bool isReady: battery && battery.ready && isDevicePresent && (battery.percentage !== undefined || hasBluetoothBattery)
readonly property int percent: isReady ? Math.round(hasBluetoothBattery ? (bluetoothDevice.battery * 100) : (battery.percentage * 100)) : -1
readonly property bool charging: isReady ? battery.state === UPowerDeviceState.Charging : false
readonly property bool healthSupported: isReady && battery.healthSupported
readonly property bool healthAvailable: healthSupported
readonly property bool healthAvailable: isReady && battery.healthSupported
readonly property int healthPercent: healthAvailable ? Math.round(battery.healthPercentage) : -1
readonly property bool powerProfileAvailable: PowerProfileService.available
readonly property var powerProfiles: [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
function getDeviceName() {
if (!isReady) {
return "";
}
// Don't show name for laptop batteries
if (battery && battery.isLaptopBattery) {
return "";
}
if (bluetoothDevice && bluetoothDevice.name) {
return bluetoothDevice.name;
}
if (battery && battery.model) {
return battery.model;
}
return "";
}
readonly property string deviceName: getDeviceName()
readonly property string panelTitle: deviceName ? `${I18n.tr("battery.panel-title")} - ${deviceName}` : I18n.tr("battery.panel-title")
readonly property string timeText: {
if (!isReady)
if (!isReady || !isDevicePresent)
return I18n.tr("battery.no-battery-detected");
if (charging && battery.timeToFull > 0) {
return I18n.tr("battery.time-until-full", {
@@ -40,9 +156,6 @@ SmartPanel {
return I18n.tr("battery.idle");
}
readonly property string iconName: BatteryService.getIcon(percent, charging, isReady)
readonly property bool profilesAvailable: PowerProfileService.available
property int profileIndex: profileToIndex(PowerProfileService.profile)
property bool manualInhibitActive: manualInhibitorEnabled()
panelContent: Item {
property real contentPreferredHeight: mainLayout.implicitHeight + Style.marginL * 2
@@ -75,11 +188,12 @@ SmartPanel {
Layout.fillWidth: true
NText {
text: I18n.tr("battery.panel-title")
text: root.panelTitle
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
elide: Text.ElideRight
}
NText {
@@ -104,6 +218,7 @@ SmartPanel {
NBox {
Layout.fillWidth: true
height: chargeLayout.implicitHeight + Style.marginL * 2
visible: isReady
ColumnLayout {
id: chargeLayout
@@ -117,7 +232,7 @@ SmartPanel {
ColumnLayout {
NText {
text: I18n.tr("battery.charge-level")
text: I18n.tr("battery.battery-level")
color: Color.mOnSurface
pointSize: Style.fontSizeS
}
@@ -166,131 +281,6 @@ SmartPanel {
}
}
}
// Power profile and idle inhibit controls
NBox {
Layout.fillWidth: true
height: controlsLayout.implicitHeight + Style.marginM * 2
ColumnLayout {
id: controlsLayout
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
ColumnLayout {
id: ppd
visible: root.powerProfileAvailable
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NIcon {
icon: PowerProfileService.getIcon()
pointSize: Style.fontSizeM
color: Color.mPrimary
}
NText {
text: I18n.tr("battery.power-profile")
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
}
NText {
text: PowerProfileService.getName(profileIndex)
color: Color.mOnSurfaceVariant
}
}
NValueSlider {
Layout.fillWidth: true
from: 0
to: 2
stepSize: 1
snapAlways: true
value: profileIndex
enabled: profilesAvailable
onPressedChanged: (pressed, v) => {
if (!pressed) {
setProfileByIndex(v);
}
}
onMoved: v => {
profileIndex = v;
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NIcon {
icon: manualInhibitActive ? "keep-awake-on" : "keep-awake-off"
pointSize: Style.fontSizeL
color: manualInhibitActive ? Color.mPrimary : Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
NToggle {
Layout.fillWidth: true
checked: manualInhibitActive
label: I18n.tr("battery.inhibit-idle-label")
description: I18n.tr("battery.inhibit-idle-description")
onToggled: function (checked) {
if (checked) {
IdleInhibitorService.addManualInhibitor(null);
} else {
IdleInhibitorService.removeManualInhibitor();
}
manualInhibitActive = checked;
}
}
}
}
}
}
}
function profileToIndex(p) {
return powerProfiles.indexOf(p) ?? 1;
}
function indexToProfile(idx) {
return powerProfiles[idx] ?? PowerProfile.Balanced;
}
function setProfileByIndex(idx) {
var prof = indexToProfile(idx);
profileIndex = idx;
PowerProfileService.setProfile(prof);
}
function manualInhibitorEnabled() {
return IdleInhibitorService.activeInhibitors && IdleInhibitorService.activeInhibitors.indexOf("manual") >= 0;
}
Connections {
target: IdleInhibitorService
function onIsInhibitedChanged() {
manualInhibitActive = manualInhibitorEnabled();
}
}
Timer {
id: inhibitorPoll
interval: 1000
repeat: true
running: true
onTriggered: manualInhibitActive = manualInhibitorEnabled()
}
Connections {
target: PowerProfileService
function onProfileChanged() {
profileIndex = profileToIndex(PowerProfileService.profile);
}
}
}

View File

@@ -29,7 +29,7 @@ NBox {
text: root.label
pointSize: Style.fontSizeS
color: Color.mSecondary
font.weight: Style.fontWeightMedium
font.weight: Style.fontWeightBold
visible: root.model.length > 0
Layout.fillWidth: true
Layout.leftMargin: Style.marginM
@@ -86,7 +86,7 @@ NBox {
NText {
text: modelData.name || modelData.deviceName
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
elide: Text.ElideRight
color: getContentColor(Color.mOnSurface)
Layout.fillWidth: true

View File

@@ -21,8 +21,8 @@ SmartPanel {
// Calculate content height based on header + devices list (or minimum for empty states)
property real headerHeight: headerRow.implicitHeight + Style.marginM * 2
property real devicesHeight: devicesList.implicitHeight
property real calculatedHeight: headerHeight + devicesHeight + Style.marginL * 2 + Style.marginM
property real contentPreferredHeight: (BluetoothService.adapter && BluetoothService.adapter.enabled) ? Math.min(root.preferredHeight, Math.max(280 * Style.uiScaleRatio, calculatedHeight)) : Math.min(root.preferredHeight, 280 * Style.uiScaleRatio)
property real calculatedHeight: (devicesHeight !== 0) ? (headerHeight + devicesHeight + Style.marginL * 2 + Style.marginM) : (280 * Style.uiScaleRatio)
property real contentPreferredHeight: (BluetoothService.adapter && BluetoothService.adapter.enabled) ? Math.min(root.preferredHeight, calculatedHeight) : Math.min(root.preferredHeight, 280 * Style.uiScaleRatio)
ColumnLayout {
id: mainColumn

View File

@@ -1,664 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Modules.MainScreen
import qs.Modules.Panels.ControlCenter.Cards
import qs.Services.Location
import qs.Services.System
import qs.Services.UI
import qs.Widgets
SmartPanel {
id: root
readonly property var now: Time.now
preferredWidth: Math.round(440 * Style.uiScaleRatio)
preferredHeight: Math.round(700 * Style.uiScaleRatio)
// Helper function to calculate ISO week number
function getISOWeekNumber(date) {
const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNr + 3);
const firstThursday = new Date(target.getFullYear(), 0, 4);
const diff = target - firstThursday;
const oneWeek = 1000 * 60 * 60 * 24 * 7;
const weekNumber = 1 + Math.round(diff / oneWeek);
return weekNumber;
}
// Helper function to check if an event is all-day
function isAllDayEvent(event) {
const duration = event.end - event.start;
const startDate = new Date(event.start * 1000);
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
return duration === 86400 && isAtMidnight;
}
panelContent: Item {
anchors.fill: parent
// Dynamic sizing properties that SmartPanel will bind to
property real contentPreferredWidth: (Settings.data.location.showWeekNumberInCalendar ? 400 : 380) * Style.uiScaleRatio
// Use implicitHeight from content + margins to avoid binding loops
property real contentPreferredHeight: content.implicitHeight + Style.marginL * 2
property real calendarGridHeight: {
// Calculate number of weeks in the calendar grid
const numWeeks = grid.daysModel ? Math.ceil(grid.daysModel.length / 7) : 5;
// Calendar grid height (dynamic based on number of weeks)
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
return numWeeks * rowHeight;
}
ColumnLayout {
id: content
anchors.fill: parent
anchors.margins: Style.marginL
width: parent.contentPreferredWidth - Style.marginL * 2
spacing: Style.marginM
readonly property int firstDayOfWeek: Settings.data.location.firstDayOfWeek === -1 ? I18n.locale.firstDayOfWeek : Settings.data.location.firstDayOfWeek
property bool isCurrentMonth: true
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
function checkIsCurrentMonth() {
return (now.getMonth() === grid.month) && (now.getFullYear() === grid.year);
}
Component.onCompleted: {
isCurrentMonth = checkIsCurrentMonth();
}
Connections {
target: Time
function onNowChanged() {
content.isCurrentMonth = content.checkIsCurrentMonth();
}
}
Connections {
target: I18n
function onLanguageChanged() {
// Force update of day names when language changes
grid.month = grid.month;
}
}
// Banner with date/time/clock
Rectangle {
id: banner
Layout.fillWidth: true
Layout.preferredHeight: capsuleColumn.implicitHeight + Style.marginM * 2
radius: Style.radiusL
color: Color.mPrimary
ColumnLayout {
id: capsuleColumn
anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: Style.marginM
anchors.bottomMargin: Style.marginM
anchors.rightMargin: clockLoader.width + (Style.marginXL * 2)
anchors.leftMargin: Style.marginXL
spacing: 0
// Combined layout for date, month year, locatio and time-zone
RowLayout {
Layout.fillWidth: true
height: 60 * Style.uiScaleRatio
clip: true
spacing: Style.marginS
// Today day number - with simple, stable animation
NText {
opacity: content.isCurrentMonth ? 1.0 : 0.0
Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0
elide: Text.ElideNone
clip: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: now.getDate()
pointSize: Style.fontSizeXXXL * 1.5
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
Behavior on Layout.preferredWidth {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
// Month, year, location
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.bottomMargin: Style.marginXXS
Layout.topMargin: -Style.marginXXS
spacing: -Style.marginXS
RowLayout {
spacing: Style.marginS
NText {
text: I18n.locale.monthName(grid.month, Locale.LongFormat).toUpperCase()
pointSize: Style.fontSizeXL * 1.1
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
Layout.alignment: Qt.AlignBaseline
elide: Text.ElideRight
}
NText {
text: `${grid.year}`
pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
color: Qt.alpha(Color.mOnPrimary, 0.7)
Layout.alignment: Qt.AlignBaseline
}
}
RowLayout {
spacing: 0
NText {
text: {
if (!Settings.data.location.weatherEnabled)
return "";
if (!content.weatherReady)
return I18n.tr("calendar.weather.loading");
const chunks = Settings.data.location.name.split(",");
return chunks[0];
}
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnPrimary
Layout.maximumWidth: 150
elide: Text.ElideRight
}
NText {
text: content.weatherReady ? ` (${LocationService.data.weather.timezone_abbreviation})` : ""
pointSize: Style.fontSizeXS
font.weight: Style.fontWeightMedium
color: Qt.alpha(Color.mOnPrimary, 0.7)
}
}
}
// Spacer to push content left
Item {
Layout.fillWidth: true
}
}
}
// Analog clock
NClock {
id: clockLoader
anchors.right: parent.right
anchors.rightMargin: Style.marginXL
anchors.verticalCenter: parent.verticalCenter
clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital"
progressColor: Color.mOnPrimary
Layout.alignment: Qt.AlignVCenter
now: root.now
}
}
// Calendar itself
NBox {
id: calendar
Layout.fillWidth: true
Layout.preferredHeight: {
const navigationHeight = Style.baseWidgetSize; // Navigation buttons row
const dayNamesHeight = Style.baseWidgetSize * 0.6; // Day names header row
const innerMargins = Style.marginM * 2; // Top and bottom margins inside NBox
const innerSpacing = Style.marginS * 2; // Spacing between nav, dayNames, and grid (2 gaps)
return navigationHeight + dayNamesHeight + calendarGridHeight + innerMargins + innerSpacing;
}
Behavior on Layout.preferredWidth {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginS
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NDivider {
Layout.fillWidth: true
}
NIconButton {
icon: "chevron-left"
onClicked: {
let newDate = new Date(grid.year, grid.month - 1, 1);
grid.year = newDate.getFullYear();
grid.month = newDate.getMonth();
content.isCurrentMonth = content.checkIsCurrentMonth();
const now = new Date();
const monthStart = new Date(grid.year, grid.month, 1);
const monthEnd = new Date(grid.year, grid.month + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
}
}
NIconButton {
icon: "calendar"
onClicked: {
grid.month = now.getMonth();
grid.year = now.getFullYear();
content.isCurrentMonth = true;
CalendarService.loadEvents();
}
}
NIconButton {
icon: "chevron-right"
onClicked: {
let newDate = new Date(grid.year, grid.month + 1, 1);
grid.year = newDate.getFullYear();
grid.month = newDate.getMonth();
content.isCurrentMonth = content.checkIsCurrentMonth();
const now = new Date();
const monthStart = new Date(grid.year, grid.month, 1);
const monthEnd = new Date(grid.year, grid.month + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 0
Item {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
}
GridLayout {
Layout.fillWidth: true
columns: 7
rows: 1
columnSpacing: 0
rowSpacing: 0
Repeater {
model: 7
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.fontSizeS * 2
NText {
anchors.centerIn: parent
text: {
let dayIndex = (content.firstDayOfWeek + index) % 7;
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat);
return dayName.substring(0, 2).toUpperCase();
}
color: Color.mPrimary
pointSize: Style.fontSizeS
font.weight: Style.fontWeightBold
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 0
// Helper function to check if a date has events
function hasEventsOnDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0)
return false;
const targetDate = new Date(year, month, day);
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000;
const targetEnd = targetStart + 86400; // +24 hours
return CalendarService.events.some(event => {
// Check if event starts or overlaps with this day
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
});
}
// Helper function to get events for a specific date
function getEventsForDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0)
return [];
const targetDate = new Date(year, month, day);
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000);
const targetEnd = targetStart + 86400; // +24 hours
return CalendarService.events.filter(event => {
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
});
}
// Helper function to check if an event is multi-day
function isMultiDayEvent(event) {
if (isAllDayEvent(event)) {
return false;
}
const startDate = new Date(event.start * 1000);
const endDate = new Date(event.end * 1000);
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
return startDateOnly.getTime() !== endDateOnly.getTime();
}
// Helper function to get color for a specific event
function getEventColor(event, isToday) {
if (isMultiDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mTertiary;
} else if (isAllDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mSecondary;
} else {
return isToday ? Color.mOnSecondary : Color.mPrimary;
}
}
// Column of week numbers
ColumnLayout {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
Layout.preferredHeight: {
const numWeeks = weekNumbers ? weekNumbers.length : 5;
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
return numWeeks * rowHeight;
}
spacing: Style.marginXXS
Behavior on Layout.preferredHeight {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
property var weekNumbers: {
if (!grid.daysModel || grid.daysModel.length === 0)
return [];
const weeks = [];
const numWeeks = Math.ceil(grid.daysModel.length / 7);
for (var i = 0; i < numWeeks; i++) {
const dayIndex = i * 7;
if (dayIndex < grid.daysModel.length) {
const weekDay = grid.daysModel[dayIndex];
const date = new Date(weekDay.year, weekDay.month, weekDay.day);
// Get Thursday of this week for ISO week calculation
const firstDayOfWeek = content.firstDayOfWeek;
let thursday = new Date(date);
if (firstDayOfWeek === 0) {
thursday.setDate(date.getDate() + 4);
} else if (firstDayOfWeek === 1) {
thursday.setDate(date.getDate() + 3);
} else {
let daysToThursday = (4 - firstDayOfWeek + 7) % 7;
thursday.setDate(date.getDate() + daysToThursday);
}
weeks.push(root.getISOWeekNumber(thursday));
}
}
return weeks;
}
Repeater {
model: parent.weekNumbers
Item {
Layout.preferredWidth: Style.baseWidgetSize * 0.7
Layout.preferredHeight: Style.baseWidgetSize * 0.9
NText {
anchors.centerIn: parent
color: Qt.alpha(Color.mPrimary, 0.7)
pointSize: Style.fontSizeXXS
font.weight: Style.fontWeightMedium
text: modelData
}
}
}
}
GridLayout {
id: grid
Layout.fillWidth: true
Layout.preferredHeight: {
const numWeeks = daysModel ? Math.ceil(daysModel.length / 7) : 5;
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
return numWeeks * rowHeight;
}
columns: 7
columnSpacing: Style.marginXXS
rowSpacing: Style.marginXXS
property int month: now.getMonth()
property int year: now.getFullYear()
Behavior on Layout.preferredHeight {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
// Calculate days to display
property var daysModel: {
const firstOfMonth = new Date(year, month, 1);
const lastOfMonth = new Date(year, month + 1, 0);
const daysInMonth = lastOfMonth.getDate();
// Get first day of week (0 = Sunday, 1 = Monday, etc.)
const firstDayOfWeek = content.firstDayOfWeek;
const firstOfMonthDayOfWeek = firstOfMonth.getDay();
// Calculate days before first of month
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
// Calculate days after last of month to complete the week
const lastOfMonthDayOfWeek = lastOfMonth.getDay();
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
// Build array of day objects
const days = [];
const today = new Date();
// Previous month days
const prevMonth = new Date(year, month, 0);
const prevMonthDays = prevMonth.getDate();
for (var i = daysBefore - 1; i >= 0; i--) {
const day = prevMonthDays - i;
const date = new Date(year, month - 1, day);
days.push({
"day": day,
"month": month - 1,
"year": month === 0 ? year - 1 : year,
"today": false,
"currentMonth": false
});
}
// Current month days
for (var day = 1; day <= daysInMonth; day++) {
const date = new Date(year, month, day);
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
days.push({
"day": day,
"month": month,
"year": year,
"today": isToday,
"currentMonth": true
});
}
// Next month days (only if needed to complete the week)
for (var i = 1; i <= daysAfter; i++) {
days.push({
"day": i,
"month": month + 1,
"year": month === 11 ? year + 1 : year,
"today": false,
"currentMonth": false
});
}
return days;
}
Repeater {
model: grid.daysModel
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.9
Rectangle {
width: Style.baseWidgetSize * 0.9
height: Style.baseWidgetSize * 0.9
anchors.centerIn: parent
radius: Style.radiusM
color: modelData.today ? Color.mSecondary : Color.transparent
NText {
anchors.centerIn: parent
text: modelData.day
color: {
if (modelData.today)
return Color.mOnSecondary;
if (modelData.currentMonth)
return Color.mOnSurface;
return Color.mOnSurfaceVariant;
}
opacity: modelData.currentMonth ? 1.0 : 0.4
pointSize: Style.fontSizeM
font.weight: modelData.today ? Style.fontWeightBold : Style.fontWeightMedium
}
// Event indicator dots
Row {
visible: Settings.data.location.showCalendarEvents && parent.parent.parent.parent.hasEventsOnDate(modelData.year, modelData.month, modelData.day)
spacing: 2
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.marginXS
Repeater {
model: parent.parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day)
Rectangle {
width: 4
height: width
radius: width / 2
color: parent.parent.parent.parent.parent.getEventColor(modelData, modelData.today)
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: Settings.data.location.showCalendarEvents
onEntered: {
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day);
if (events.length > 0) {
const summaries = events.map(event => {
if (isAllDayEvent(event)) {
return event.summary;
} else {
// Always format with '0' padding to ensure proper horizontal alignment
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm";
const start = new Date(event.start * 1000);
const startFormatted = I18n.locale.toString(start, timeFormat);
const end = new Date(event.end * 1000);
const endFormatted = I18n.locale.toString(end, timeFormat);
return `${startFormatted}-${endFormatted} ${event.summary}`;
}
}).join('\n');
TooltipService.show(parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed);
}
}
onClicked: {
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`;
if (ProgramCheckerService.gnomeCalendarAvailable) {
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]);
root.close();
}
}
onExited: {
TooltipService.hide();
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
}
}
}
}
}
Loader {
id: weatherLoader
active: Settings.data.location.weatherEnabled && Settings.data.location.showCalendarWeather
visible: active
Layout.fillWidth: true
sourceComponent: WeatherCard {
Layout.fillWidth: true
forecastDays: 5
showLocation: false
}
}
}
}
}

View File

@@ -0,0 +1,87 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Modules.Cards
import qs.Modules.MainScreen
import qs.Services.Location
import qs.Services.UI
import qs.Widgets
SmartPanel {
id: root
// Calculate width based on settings
preferredWidth: Math.round((Settings.data.location.showWeekNumberInCalendar ? 460 : 440) * Style.uiScaleRatio)
panelContent: Item {
anchors.fill: parent
// SmartPanel uses this to calculate panel height dynamically
readonly property real contentPreferredHeight: content.implicitHeight + (Style.marginL * 2)
ColumnLayout {
id: content
x: Style.marginL
y: Style.marginL
width: parent.width - (Style.marginL * 2)
spacing: Style.marginL
// All clock panel cards
Repeater {
model: Settings.data.calendar.cards
Loader {
active: modelData.enabled && (modelData.id !== "weather-card" || Settings.data.location.weatherEnabled)
visible: active
Layout.fillWidth: true
sourceComponent: {
switch (modelData.id) {
case "calendar-header-card":
return calendarHeaderCard;
case "calendar-month-card":
return calendarMonthCard;
case "timer-card":
return timerCard;
case "weather-card":
return weatherCard;
default:
return null;
}
}
}
}
}
}
Component {
id: calendarHeaderCard
CalendarHeaderCard {
Layout.fillWidth: true
}
}
Component {
id: calendarMonthCard
CalendarMonthCard {
Layout.fillWidth: true
}
}
Component {
id: timerCard
TimerCard {
Layout.fillWidth: true
}
}
Component {
id: weatherCard
WeatherCard {
Layout.fillWidth: true
forecastDays: 5
showLocation: false
}
}
}

View File

@@ -3,8 +3,8 @@ import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Modules.Cards
import qs.Modules.MainScreen
import qs.Modules.Panels.ControlCenter.Cards
import qs.Services.Media
import qs.Services.UI
import qs.Widgets

View File

@@ -102,7 +102,7 @@ Item {
anchors.fill: parent
imagePath: imageDataUrl
visible: isImageContent && !loadingFullContent && imageDataUrl !== ""
imageRadius: Style.radiusS
radius: Style.radiusS
imageFillMode: Image.PreserveAspectFit
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,13 +11,30 @@ Item {
property var launcher: null
property bool handleSearch: false
property string selectedCategory: "recent"
property bool isBrowsingMode: false
property var categoryIcons: ({
"recent": "clock",
"people": "user",
"animals": "paw",
"nature": "leaf",
"food": "apple",
"activity": "run",
"travel": "plane",
"objects": "home",
"symbols": "star",
"flags": "flag"
})
property var categories: ["recent", "people", "animals", "nature", "food", "activity", "travel", "objects", "symbols", "flags"]
// Force update results when emoji service loads
Connections {
target: EmojiService
function onLoadedChanged() {
if (EmojiService.loaded && root.launcher) {
// Update launcher results to refresh the UI
root.launcher?.updateResults();
root.launcher.updateResults();
}
}
}
@@ -27,6 +44,18 @@ Item {
Logger.i("EmojiPlugin", "Initialized");
}
function selectCategory(category) {
selectedCategory = category;
if (launcher) {
launcher.updateResults();
}
}
function onOpened() {
// Always reset to "recent" category when opening
selectedCategory = "recent";
}
// Check if this plugin handles the command
function handleCommand(searchText) {
return searchText.startsWith(">emoji");
@@ -65,15 +94,23 @@ Item {
];
}
const query = searchText.slice(6).trim();
const emojis = EmojiService.search(query);
return emojis.map(formatEmojiEntry);
var query = searchText.slice(6).trim();
if (query === "") {
isBrowsingMode = true;
var emojis = EmojiService.getEmojisByCategory(selectedCategory);
return emojis.map(formatEmojiEntry);
} else {
isBrowsingMode = false;
var emojis = EmojiService.search(query);
return emojis.map(formatEmojiEntry);
}
}
// Format an emoji entry for the results list
function formatEmojiEntry(emoji) {
let title = emoji.name;
let description = (emoji.keywords || []).join(", ");
let description = emoji.keywords.join(", ");
if (emoji.category) {
description += " • Category: " + emoji.category;

View File

@@ -13,6 +13,71 @@ import qs.Widgets
SmartPanel {
id: root
// 0 = All, 1 = Today, 2 = Yesterday, 3 = Earlier
property int currentRange: 1 // start on Today by default
property var rangeCounts: [0, 0, 0, 0]
property bool groupByDate: true
function dateOnly(d) {
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
}
function rangeForTimestamp(ts) {
var dt = new Date(ts);
var today = dateOnly(new Date());
var thatDay = dateOnly(dt);
var diffMs = today - thatDay;
var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays === 0)
return 0;
if (diffDays === 1)
return 1;
return 2;
}
function isInCurrentRange(ts) {
if (currentRange === 0)
return true;
return rangeForTimestamp(ts) === (currentRange - 1);
}
function recalcRangeCounts() {
var m = NotificationService.historyList;
if (!m || typeof m.count === "undefined" || m.count <= 0) {
rangeCounts = [0, 0, 0, 0];
return;
}
var counts = [0, 0, 0, 0];
counts[0] = m.count;
for (var i = 0; i < m.count; ++i) {
var item = m.get(i);
if (!item || typeof item.timestamp === "undefined")
continue;
var r = rangeForTimestamp(item.timestamp);
counts[r + 1] = counts[r + 1] + 1;
}
rangeCounts = counts;
}
function countForRange(range) {
return rangeCounts[range] || 0;
}
Connections {
target: NotificationService.historyList
function onCountChanged() {
recalcRangeCounts();
}
}
Component.onCompleted: recalcRangeCounts()
preferredWidth: Math.round(420 * Style.uiScaleRatio)
preferredHeight: Math.round(540 * Style.uiScaleRatio)
@@ -82,6 +147,60 @@ SmartPanel {
}
}
// Time range tabs ([All] / [Today] / [Yesterday] / [Earlier])
NBox {
Layout.fillWidth: true
Layout.topMargin: Style.marginS
implicitHeight: timeTabs.implicitHeight + (Style.marginS * 2)
visible: NotificationService.historyList.count > 0 && root.groupByDate
RowLayout {
id: timeTabs
spacing: Style.marginXS
anchors.fill: parent
anchors.margins: Style.marginS
visible: NotificationService.historyList.count > 0
Repeater {
model: 4
delegate: NButton {
readonly property int rangeId: index
readonly property bool isActive: root.currentRange === rangeId
text: {
if (rangeId === 0)
return I18n.tr("notifications.range.all") + " (" + root.countForRange(rangeId) + ")";
else if (rangeId === 1)
return I18n.tr("notifications.range.today") + " (" + root.countForRange(rangeId) + ")";
else if (rangeId === 2)
return I18n.tr("notifications.range.yesterday") + " (" + root.countForRange(rangeId) + ")";
return I18n.tr("notifications.range.earlier") + " (" + root.countForRange(rangeId) + ")";
}
Layout.fillWidth: true
Layout.preferredWidth: 1
implicitHeight: Style.baseWidgetSize * 0.7
fontSize: Style.fontSizeXS
outlined: false
backgroundColor: isActive ? Color.mPrimary : (hovered ? Color.mHover : Color.transparent)
textColor: isActive ? Color.mOnPrimary : (hovered ? Color.mOnHover : Color.mOnSurface)
hoverColor: backgroundColor
Behavior on backgroundColor {
enabled: !Settings.data.general.animationDisabled
ColorAnimation {
duration: Style.animationFast
}
}
onClicked: root.currentRange = rangeId
}
}
}
}
// Empty state when no notifications
ColumnLayout {
Layout.fillWidth: true
@@ -148,7 +267,8 @@ SmartPanel {
delegate: Item {
id: notificationDelegate
width: parent.width
height: contentColumn.height + (Style.marginM * 2)
visible: root.isInCurrentRange(model.timestamp)
height: visible ? contentColumn.height + (Style.marginM * 2) : 0
property string notificationId: model.id
property bool isExpanded: scrollView.expandedId === notificationId
@@ -197,10 +317,11 @@ SmartPanel {
spacing: Style.marginM
// Icon
NImageCircled {
NImageRounded {
anchors.verticalCenter: parent.verticalCenter
width: Math.round(40 * Style.uiScaleRatio)
height: Math.round(40 * Style.uiScaleRatio)
anchors.verticalCenter: parent.verticalCenter
radius: width * 0.5
imagePath: model.cachedImage || model.originalImage || ""
borderColor: Color.transparent
borderWidth: 0

View File

@@ -153,9 +153,15 @@ Popup {
function loadWidgetSettings() {
const source = BarWidgetRegistry.widgetSettingsMap[widgetId];
if (source) {
// Use setSource to pass properties at creation time
var currentWidgetData = widgetData;
if (sectionId && widgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[sectionId];
if (widgets && widgetIndex < widgets.length) {
currentWidgetData = widgets[widgetIndex];
}
}
settingsLoader.setSource(source, {
"widgetData": widgetData,
"widgetData": currentWidgetData,
"widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId]
});
}

View File

@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Services.UPower
import qs.Commons
import qs.Widgets
@@ -15,14 +16,109 @@ ColumnLayout {
// Local state
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
property int valueWarningThreshold: widgetData.warningThreshold !== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
property string valueDeviceNativePath: widgetData.deviceNativePath !== undefined ? widgetData.deviceNativePath : ""
// Build model of available battery devices
function buildDeviceModel() {
var model = [
{
"key": "",
"name": I18n.tr("bar.widget-settings.battery.device.default")
}
];
if (!UPower.devices) {
return model;
}
var deviceArray = UPower.devices.values || [];
for (var i = 0; i < deviceArray.length; i++) {
var device = deviceArray[i];
if (!device || device.type === UPowerDeviceType.LinePower) {
continue;
}
var displayName = device.model || device.nativePath || "Unknown";
model.push({
"key": device.nativePath || "",
"name": displayName
});
}
return model;
}
readonly property int _deviceCount: (UPower.devices && UPower.devices.values) ? UPower.devices.values.length : 0
property var deviceModel: buildDeviceModel()
on_DeviceCountChanged: {
deviceModel = buildDeviceModel();
}
Connections {
target: UPower.devices
function onValuesChanged() {
deviceModel = buildDeviceModel();
}
}
Timer {
id: refreshTimer
interval: 2000
running: true
repeat: true
onTriggered: {
var currentCount = (UPower.devices && UPower.devices.values) ? UPower.devices.values.length : 0;
if (currentCount !== root._deviceCount) {
deviceModel = buildDeviceModel();
}
}
}
function saveSettings() {
var settings = Object.assign({}, widgetData || {});
if (widgetData && widgetData.id) {
settings.id = widgetData.id;
}
settings.displayMode = valueDisplayMode;
settings.warningThreshold = valueWarningThreshold;
if (valueDeviceNativePath && valueDeviceNativePath !== "") {
settings.deviceNativePath = valueDeviceNativePath;
} else {
delete settings.deviceNativePath;
}
return settings;
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
NComboBox {
id: deviceComboBox
Layout.fillWidth: true
label: I18n.tr("bar.widget-settings.battery.device.label")
description: I18n.tr("bar.widget-settings.battery.device.description")
minimumWidth: 134
model: root.deviceModel
currentKey: root.valueDeviceNativePath
onSelected: key => root.valueDeviceNativePath = key
}
// Update currentKey when model changes to ensure selection is preserved
Connections {
target: root
function onDeviceModelChanged() {
// Force update of currentKey to trigger selection update
deviceComboBox.currentKey = root.valueDeviceNativePath;
}
}
NIconButton {
icon: "refresh"
tooltipText: "Refresh device list"
onClicked: deviceModel = buildDeviceModel()
}
}
NComboBox {
label: I18n.tr("bar.widget-settings.battery.display-mode.label")
description: I18n.tr("bar.widget-settings.battery.display-mode.description")

View File

@@ -17,14 +17,16 @@ ColumnLayout {
property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo
property string valueCustomIconPath: widgetData.customIconPath !== undefined ? widgetData.customIconPath : ""
property bool valueColorizeDistroLogo: widgetData.colorizeDistroLogo !== undefined ? widgetData.colorizeDistroLogo : (widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false)
property bool valueEnableColorization: widgetData.enableColorization || false
property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : (widgetMetadata.colorizeSystemIcon !== undefined ? widgetMetadata.colorizeSystemIcon : "none")
function saveSettings() {
var settings = Object.assign({}, widgetData || {});
settings.icon = valueIcon;
settings.useDistroLogo = valueUseDistroLogo;
settings.customIconPath = valueCustomIconPath;
settings.colorizeDistroLogo = valueColorizeDistroLogo;
settings.enableColorization = valueEnableColorization;
settings.colorizeSystemIcon = valueColorizeSystemIcon;
return settings;
}
@@ -34,20 +36,47 @@ ColumnLayout {
checked: valueUseDistroLogo
onToggled: function (checked) {
valueUseDistroLogo = checked;
if (checked) {
valueCustomIconPath = "";
valueIcon = "";
}
}
}
NToggle {
visible: valueUseDistroLogo
label: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.label")
description: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.description")
checked: valueColorizeDistroLogo
label: I18n.tr("bar.widget-settings.control-center.enable-colorization.label")
description: I18n.tr("bar.widget-settings.control-center.enable-colorization.description")
checked: valueEnableColorization
onToggled: function (checked) {
valueColorizeDistroLogo = checked;
valueEnableColorization = checked;
}
}
NComboBox {
visible: valueEnableColorization
label: I18n.tr("bar.widget-settings.control-center.color-selection.label")
description: I18n.tr("bar.widget-settings.control-center.color-selection.description")
model: [
{
"name": I18n.tr("options.colors.none"),
"key": "none"
},
{
"name": I18n.tr("options.colors.primary"),
"key": "primary"
},
{
"name": I18n.tr("options.colors.secondary"),
"key": "secondary"
},
{
"name": I18n.tr("options.colors.tertiary"),
"key": "tertiary"
},
{
"name": I18n.tr("options.colors.error"),
"key": "error"
}
]
currentKey: valueColorizeSystemIcon
onSelected: function (key) {
valueColorizeSystemIcon = key;
}
}
@@ -59,19 +88,20 @@ ColumnLayout {
description: I18n.tr("bar.widget-settings.control-center.icon.description")
}
NImageCircled {
NImageRounded {
Layout.preferredWidth: Style.fontSizeXL * 2
Layout.preferredHeight: Style.fontSizeXL * 2
Layout.alignment: Qt.AlignVCenter
radius: width * 0.5
imagePath: valueCustomIconPath
visible: valueCustomIconPath !== ""
visible: valueCustomIconPath !== "" && !valueUseDistroLogo
}
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: valueIcon
pointSize: Style.fontSizeXXL * 1.5
visible: valueIcon !== "" && valueCustomIconPath === ""
visible: valueIcon !== "" && valueCustomIconPath === "" && !valueUseDistroLogo
}
}

View File

@@ -6,6 +6,7 @@ import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services.Noctalia
import qs.Services.System
import qs.Widgets
ColumnLayout {
@@ -14,9 +15,136 @@ ColumnLayout {
property string latestVersion: GitHubService.latestVersion
property string currentVersion: UpdateService.currentVersion
property var contributors: GitHubService.contributors
property string commitInfo: ""
readonly property int topContributorsCount: 20
readonly property bool isGitVersion: root.currentVersion.endsWith("-git")
spacing: Style.marginL
Component.onCompleted: {
Logger.d("AboutTab", "Component.onCompleted - Current version:", root.currentVersion);
Logger.d("AboutTab", "Component.onCompleted - Is git version:", root.isGitVersion);
// Only fetch commit info for -git versions
if (root.isGitVersion) {
// On NixOS, extract commit hash from the store path
if (HostService.isNixOS) {
var shellDir = Quickshell.shellDir || "";
Logger.d("AboutTab", "Component.onCompleted - NixOS detected, shellDir:", shellDir);
if (shellDir) {
// Extract commit hash from path like: /nix/store/...-noctalia-shell-2025-11-30_225e6d3/share/noctalia-shell
// Pattern matches: noctalia-shell-YYYY-MM-DD_<commit_hash>
var match = shellDir.match(/noctalia-shell-\d{4}-\d{2}-\d{2}_([0-9a-f]{7,})/i);
if (match && match[1]) {
// Use first 7 characters of the commit hash
root.commitInfo = match[1].substring(0, 7);
Logger.d("AboutTab", "Component.onCompleted - Extracted commit from NixOS path:", root.commitInfo);
return;
} else {
Logger.d("AboutTab", "Component.onCompleted - Could not extract commit from NixOS path, trying fallback");
}
}
}
// Try to get Arch package version first (which includes commit hash)
pacmanProcess.running = true;
// Start fallback timer in case pacman fails to start
gitFallbackTimer.start();
}
}
Timer {
id: gitFallbackTimer
interval: 500
running: false
onTriggered: {
if (!root.commitInfo) {
fetchGitCommit();
}
}
}
Process {
id: pacmanProcess
command: ["pacman", "-Q", "noctalia-shell-git"]
running: false
onStarted: {
gitFallbackTimer.stop();
}
onExited: function (exitCode) {
gitFallbackTimer.stop();
Logger.d("AboutTab", "pacmanProcess - Process exited with code:", exitCode);
if (exitCode === 0) {
var output = stdout.text.trim();
Logger.d("AboutTab", "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("AboutTab", "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("AboutTab", "pacmanProcess - No commit hash in version, trying git");
fetchGitCommit();
}
} else {
// Unexpected output format, try git
Logger.d("AboutTab", "pacmanProcess - Unexpected output format, trying git");
fetchGitCommit();
}
} else {
// If not on Arch, try to get git commit from repository
Logger.d("AboutTab", "pacmanProcess - Package not found, trying git");
fetchGitCommit();
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
function fetchGitCommit() {
var shellDir = Quickshell.shellDir || "";
Logger.d("AboutTab", "fetchGitCommit - shellDir:", shellDir);
if (!shellDir) {
Logger.d("AboutTab", "fetchGitCommit - Cannot determine shell directory, skipping git commit fetch");
return;
}
gitProcess.workingDirectory = shellDir;
gitProcess.running = true;
}
Process {
id: gitProcess
command: ["git", "rev-parse", "--short", "HEAD"]
running: false
onExited: function (exitCode) {
Logger.d("AboutTab", "gitProcess - Process exited with code:", exitCode);
if (exitCode === 0) {
var gitOutput = stdout.text.trim();
Logger.d("AboutTab", "gitProcess - gitOutput:", gitOutput);
if (gitOutput) {
root.commitInfo = gitOutput;
Logger.d("AboutTab", "gitProcess - Set commitInfo to:", root.commitInfo);
}
} else {
Logger.d("AboutTab", "gitProcess - Git command failed. Exit code:", exitCode);
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
NHeader {
label: I18n.tr("settings.about.noctalia.section.label")
description: I18n.tr("settings.about.noctalia.section.description")
@@ -52,6 +180,21 @@ ColumnLayout {
color: Color.mOnSurface
font.weight: Style.fontWeightBold
}
NText {
visible: root.isGitVersion
text: I18n.tr("settings.about.noctalia.git-commit")
color: Color.mOnSurface
}
NText {
visible: root.isGitVersion
text: root.commitInfo || I18n.tr("settings.about.noctalia.git-commit-loading")
color: Color.mOnSurface
font.weight: Style.fontWeightBold
font.family: root.commitInfo ? "monospace" : ""
pointSize: Style.fontSizeXS
}
}
// Update button
@@ -149,92 +292,222 @@ ColumnLayout {
enableDescriptionRichText: true
}
GridView {
id: contributorsGrid
readonly property int columnsCount: 2
// Top 20 contributors with full cards (avoids GridView shader crashes on Qt 6.8)
Flow {
id: topContributorsFlow
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: cellWidth * columnsCount
Layout.preferredHeight: {
if (root.contributors.length === 0)
return 0;
Layout.preferredWidth: Math.round(Style.baseWidgetSize * 14)
spacing: Style.marginM
const rows = Math.ceil(root.contributors.length / columnsCount);
return rows * cellHeight;
}
cellWidth: Math.round(Style.baseWidgetSize * 7)
cellHeight: Math.round(Style.baseWidgetSize * 2.5)
model: root.contributors
Repeater {
model: Math.min(root.contributors.length, root.topContributorsCount)
delegate: Rectangle {
width: contributorsGrid.cellWidth - Style.marginM
height: contributorsGrid.cellHeight - Style.marginM
radius: Style.radiusL
color: contributorArea.containsMouse ? Color.mHover : Color.transparent
delegate: Rectangle {
width: Math.round(Style.baseWidgetSize * 6.8)
height: Math.round(Style.baseWidgetSize * 2.3)
radius: Style.radiusM
color: contributorArea.containsMouse ? Color.mHover : Color.transparent
border.width: 1
border.color: contributorArea.containsMouse ? Color.mPrimary : Color.mOutline
Behavior on color {
ColorAnimation {
duration: Style.animationFast
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
RowLayout {
anchors.centerIn: parent
width: parent.width - (Style.marginS * 2)
spacing: Style.marginM
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
Item {
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: Style.baseWidgetSize * 2 * Style.uiScaleRatio
Layout.preferredHeight: Style.baseWidgetSize * 2 * Style.uiScaleRatio
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
NImageCircled {
imagePath: modelData.avatar_url || ""
anchors.fill: parent
anchors.margins: Style.marginXS
fallbackIcon: "person"
borderColor: contributorArea.containsMouse ? Color.mOnHover : Color.mPrimary
borderWidth: Style.borderM
// Avatar container with rectangular design (modern, no shader issues)
Item {
id: wrapper
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: Style.baseWidgetSize * 1.8
Layout.preferredHeight: Style.baseWidgetSize * 1.8
Behavior on borderColor {
ColorAnimation {
property bool isRounded: false
// Background and image container
Item {
anchors.fill: parent
// Simple circular image (pre-rendered, no shaders)
Image {
anchors.fill: parent
source: {
// Try cached circular version first
var username = root.contributors[index].login;
var cached = GitHubService.cachedCircularAvatars[username];
if (cached) {
wrapper.isRounded = true;
return cached;
}
// Fall back to original avatar URL
return root.contributors[index].avatar_url || "";
}
fillMode: Image.PreserveAspectFit // Fit since image is already circular with transparency
mipmap: true
smooth: true
asynchronous: true
visible: root.contributors[index].avatar_url !== undefined && root.contributors[index].avatar_url !== ""
opacity: status === Image.Ready ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
}
// Fallback icon
NIcon {
anchors.centerIn: parent
visible: !root.contributors[index].avatar_url || root.contributors[index].avatar_url === ""
icon: "person"
pointSize: Style.fontSizeL
color: Color.mPrimary
}
}
Rectangle {
visible: wrapper.isRounded
anchors.fill: parent
color: Color.transparent
radius: width * 0.5
border.width: Style.borderM
border.color: Color.mPrimary
}
}
// Info column
ColumnLayout {
spacing: 2
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
NText {
text: root.contributors[index].login || "Unknown"
font.weight: Style.fontWeightBold
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurface
elide: Text.ElideRight
Layout.fillWidth: true
pointSize: Style.fontSizeS
}
RowLayout {
spacing: Style.marginXS
Layout.fillWidth: true
NIcon {
icon: "git-commit"
pointSize: Style.fontSizeXS
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
}
NText {
text: `${(root.contributors[index].contributions || 0).toString()} commits`
pointSize: Style.fontSizeXS
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
}
}
}
// Hover indicator
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: "arrow-right"
pointSize: Style.fontSizeS
color: Color.mPrimary
opacity: contributorArea.containsMouse ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
}
}
ColumnLayout {
spacing: Style.marginXS
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
NText {
text: modelData.login || "Unknown"
font.weight: Style.fontWeightBold
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurface
elide: Text.ElideRight
Layout.fillWidth: true
}
NText {
text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits")
pointSize: Style.fontSizeXS
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
MouseArea {
id: contributorArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.contributors[index].html_url)
Quickshell.execDetached(["xdg-open", root.contributors[index].html_url]);
}
}
}
}
}
MouseArea {
id: contributorArea
// Remaining contributors (simple text links)
Flow {
id: remainingContributorsFlow
visible: root.contributors.length > root.topContributorsCount
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: Math.round(Style.baseWidgetSize * 14)
Layout.topMargin: Style.marginL
spacing: Style.marginS
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData.html_url)
Quickshell.execDetached(["xdg-open", modelData.html_url]);
Repeater {
model: Math.max(0, root.contributors.length - root.topContributorsCount)
delegate: Rectangle {
width: nameText.implicitWidth + Style.marginM * 2
height: nameText.implicitHeight + Style.marginS * 2
radius: Style.radiusS
color: nameArea.containsMouse ? Color.mHover : Color.transparent
border.width: Style.borderS
border.color: nameArea.containsMouse ? Color.mPrimary : Color.mOutline
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
NText {
id: nameText
anchors.centerIn: parent
text: root.contributors[index + root.topContributorsCount].login || "Unknown"
pointSize: Style.fontSizeXS
color: nameArea.containsMouse ? Color.mOnHover : Color.mOnSurface
font.weight: Style.fontWeightMedium
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
MouseArea {
id: nameArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.contributors[index + root.topContributorsCount].html_url)
Quickshell.execDetached(["xdg-open", root.contributors[index + root.topContributorsCount].html_url]);
}
}
}
}

View File

@@ -12,6 +12,17 @@ ColumnLayout {
property real localVolume: AudioService.volume
Connections {
target: AudioService
function onSinkChanged() {
// Immediately update local volume when device changes to prevent old value from being applied
localVolume = AudioService.volume;
}
function onVolumeChanged() {
localVolume = AudioService.volume;
}
}
Connections {
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
function onVolumeChanged() {
@@ -42,7 +53,8 @@ ColumnLayout {
running: true
repeat: true
onTriggered: {
if (Math.abs(localVolume - AudioService.volume) >= 0.01) {
// Don't set volume if device is switching - wait for new device's volume to be read
if (!AudioService.isSwitchingSink && Math.abs(localVolume - AudioService.volume) >= 0.01) {
AudioService.setVolume(localVolume);
}
}

View File

@@ -49,6 +49,8 @@ ColumnLayout {
schemeName = "Noctalia (legacy)";
} else if (schemeName === "Tokyo-Night") {
schemeName = "Tokyo Night";
} else if (schemeName === "Rosepine") {
schemeName = "Rose Pine";
}
return schemeName;
@@ -332,22 +334,17 @@ ColumnLayout {
Layout.fillWidth: true
visible: !Settings.data.colorSchemes.useWallpaperColors
RowLayout {
NHeader {
label: I18n.tr("settings.color-scheme.predefined.section.label")
description: I18n.tr("settings.color-scheme.predefined.section.description")
Layout.fillWidth: true
}
NHeader {
label: I18n.tr("settings.color-scheme.predefined.section.label")
description: I18n.tr("settings.color-scheme.predefined.section.description")
Layout.fillWidth: true
}
NButton {
text: I18n.tr("settings.color-scheme.download.button")
icon: "download"
onClicked: {
root.openDownloadPopup();
}
}
NButton {
text: I18n.tr("settings.color-scheme.download.button")
icon: "download"
onClicked: root.openDownloadPopup()
Layout.alignment: Qt.AlignRight
}
// Download popup
@@ -579,6 +576,32 @@ ColumnLayout {
}
}
// Compositors
NCollapsible {
Layout.fillWidth: true
label: I18n.tr("settings.color-scheme.templates.compositors.label")
description: I18n.tr("settings.color-scheme.templates.compositors.description")
defaultExpanded: false
NCheckbox {
label: "Niri"
description: ProgramCheckerService.niriAvailable ? I18n.tr("settings.color-scheme.templates.compositors.niri.description", {
"filepath": "~/.config/niri/noctalia.kdl"
}) : I18n.tr("settings.color-scheme.templates.compositors.niri.description-missing", {
"app": "niri"
})
checked: Settings.data.templates.niri
enabled: ProgramCheckerService.niriAvailable
opacity: ProgramCheckerService.niriAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.niriAvailable) {
Settings.data.templates.niri = checked;
AppThemeService.generate();
}
}
}
}
// Terminal Emulators
NCollapsible {
Layout.fillWidth: true
@@ -853,14 +876,14 @@ ColumnLayout {
}
}
}
NCheckbox {
label: "Cava"
description: ProgramCheckerService.cavaAvailable ? I18n.tr("settings.color-scheme.templates.programs.cava.description", {
"filepath": "~/.config/cava/themes/noctalia"
}) : I18n.tr("settings.color-scheme.templates.programs.cava.description-missing", {
"app": "cava"
})
"filepath": "~/.config/cava/themes/noctalia"
}) : I18n.tr("settings.color-scheme.templates.programs.cava.description-missing", {
"app": "cava"
})
checked: Settings.data.templates.cava
enabled: ProgramCheckerService.cavaAvailable
opacity: ProgramCheckerService.cavaAvailable ? 1.0 : 0.6
@@ -871,7 +894,24 @@ ColumnLayout {
}
}
}
NCheckbox {
label: "Emacs"
description: ProgramCheckerService.emacsAvailable ? "Doom: ~/.config/doom/themes/noctalia.el\nStandard: ~/.emacs.d/themes/noctalia.el\n\nApply manually: (load-theme 'noctalia)" : I18n.tr("settings.color-scheme.templates.programs.emacs.description-missing", {
"app": "emacs"
})
checked: Settings.data.templates.emacs
enabled: ProgramCheckerService.emacsAvailable
opacity: ProgramCheckerService.emacsAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.emacsAvailable) {
Settings.data.templates.emacs = checked;
AppThemeService.generate();
}
}
}
}
// Miscellaneous
NCollapsible {
Layout.fillWidth: true

View File

@@ -241,9 +241,14 @@ Popup {
// Rate limit hit - try to use cache if available
downloadError = I18n.tr("settings.color-scheme.download.error.rate-limit");
Logger.w("ColorSchemeDownload", downloadError);
if (schemesCacheAdapter.schemes && schemesCacheAdapter.schemes.length > 0) {
availableSchemes = schemesCacheAdapter.schemes;
Logger.i("ColorSchemeDownload", "Using cached schemes due to rate limit");
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
const cacheData = ShellState.getColorSchemesList();
const cachedSchemes = cacheData.schemes || [];
if (cachedSchemes.length > 0) {
availableSchemes = cachedSchemes;
hasInitialData = true;
Logger.i("ColorSchemeDownload", "Using cached schemes due to rate limit");
}
}
} else {
downloadError = I18n.tr("settings.color-scheme.download.error.api-error", {
@@ -740,7 +745,12 @@ Popup {
enabled: !fetching && !downloading
onClicked: {
// Force refresh by clearing cache timestamp and fetching directly from API
schemesCacheAdapter.timestamp = 0;
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
ShellState.setColorSchemesList({
schemes: [],
timestamp: 0
});
}
// Fetch directly from API to avoid cache check delay
fetchAvailableSchemesFromAPI();
}

View File

@@ -249,7 +249,7 @@ ColumnLayout {
sectionName: I18n.tr("settings.control-center.shortcuts.sectionLeft")
sectionId: "left"
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
maxWidgets: 5
maxWidgets: Settings.data.controlCenter.shortcuts["right"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["right"].length > 0 ? 5 : 10)
widgetRegistry: ControlCenterWidgetRegistry
widgetModel: Settings.data.controlCenter.shortcuts["left"]
availableWidgets: availableWidgets
@@ -266,7 +266,7 @@ ColumnLayout {
sectionName: I18n.tr("settings.control-center.shortcuts.sectionRight")
sectionId: "right"
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
maxWidgets: 5
maxWidgets: Settings.data.controlCenter.shortcuts["left"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["left"].length > 0 ? 5 : 10)
widgetRegistry: ControlCenterWidgetRegistry
widgetModel: Settings.data.controlCenter.shortcuts["right"]
availableWidgets: availableWidgets

View File

@@ -232,63 +232,130 @@ ColumnLayout {
// Temperature
ColumnLayout {
spacing: Style.marginXS
Layout.alignment: Qt.AlignVCenter
visible: Settings.data.nightLight.enabled
spacing: Style.marginM
Layout.fillWidth: true
// Night temperature
NLabel {
label: I18n.tr("settings.display.night-light.temperature.label")
description: I18n.tr("settings.display.night-light.temperature.description")
label: I18n.tr("settings.display.night-light.temperature.night")
description: I18n.tr("settings.display.night-light.temperature.night-description")
Layout.fillWidth: true
}
RowLayout {
visible: Settings.data.nightLight.enabled
Layout.fillWidth: true
spacing: Style.marginM
Layout.fillWidth: false
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
NText {
text: I18n.tr("settings.display.night-light.temperature.night")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
NSlider {
id: nightSlider
Layout.fillWidth: true
NTextInput {
text: Settings.data.nightLight.nightTemp
inputMethodHints: Qt.ImhDigitsOnly
Layout.alignment: Qt.AlignVCenter
onEditingFinished: {
var nightTemp = parseInt(text);
from: 1000
to: 6500
value: Settings.data.nightLight.nightTemp
// Clamp as the thumb moves, but do NOT change Settings here
onValueChanged: {
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
// Clamp value between [1000 .. (dayTemp-500)]
var clampedValue = Math.min(dayTemp - 500, Math.max(1000, nightTemp));
text = Settings.data.nightLight.nightTemp = clampedValue.toString();
var v = Math.round(value);
if (!isNaN(dayTemp)) {
var maxNight = dayTemp - 500;
v = Math.min(maxNight, Math.max(1000, v));
} else {
v = Math.max(1000, v);
}
if (v !== value)
value = v;
}
// Only write back to Settings when the user releases the slider
onPressedChanged: {
if (!pressed) {
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
var v = Math.round(value);
if (!isNaN(dayTemp)) {
var maxNight = dayTemp - 500;
v = Math.min(maxNight, Math.max(1000, v));
} else {
v = Math.max(1000, v);
}
Settings.data.nightLight.nightTemp = v;
}
}
}
NText {
text: I18n.tr("settings.display.night-light.temperature.day")
text: nightSlider.value + "K"
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
NTextInput {
text: Settings.data.nightLight.dayTemp
inputMethodHints: Qt.ImhDigitsOnly
Layout.alignment: Qt.AlignVCenter
onEditingFinished: {
var dayTemp = parseInt(text);
}
// Day temperature
NLabel {
label: I18n.tr("settings.display.night-light.temperature.day")
description: I18n.tr("settings.display.night-light.temperature.day-description")
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
NSlider {
id: daySlider
Layout.fillWidth: true
from: 1000
to: 6500
value: Settings.data.nightLight.dayTemp
// Clamp as the thumb moves, but do NOT change Settings here
onValueChanged: {
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
// Clamp value between [(nightTemp+500) .. 6500]
var clampedValue = Math.max(nightTemp + 500, Math.min(6500, dayTemp));
text = Settings.data.nightLight.dayTemp = clampedValue.toString();
var v = Math.round(value);
if (!isNaN(nightTemp)) {
var minDay = nightTemp + 500;
v = Math.max(minDay, Math.min(6500, v));
} else {
v = Math.min(6500, v);
}
if (v !== value)
value = v;
}
// Only write back to Settings when the user releases the slider
onPressedChanged: {
if (!pressed) {
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
var v = Math.round(value);
if (!isNaN(nightTemp)) {
var minDay = nightTemp + 500;
v = Math.max(minDay, Math.min(6500, v));
} else {
v = Math.min(6500, v);
}
Settings.data.nightLight.dayTemp = v;
}
}
}
NText {
text: daySlider.value + "K"
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
}
}

View File

@@ -21,9 +21,10 @@ ColumnLayout {
spacing: Style.marginL
// Avatar preview
NImageCircled {
NImageRounded {
Layout.preferredWidth: 88 * Style.uiScaleRatio
Layout.preferredHeight: width
radius: width * 0.5
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
fallbackIcon: "person"
borderColor: Color.mPrimary
@@ -39,7 +40,7 @@ ColumnLayout {
text: Settings.data.general.avatarImage
placeholderText: I18n.tr("placeholders.profile-picture-path")
buttonIcon: "photo"
buttonTooltip: "Browse for avatar image"
buttonTooltip: I18n.tr("settings.general.profile.tooltip")
onInputEditingFinished: Settings.data.general.avatarImage = text
onButtonClicked: {
avatarPicker.openFilePicker();

View File

@@ -58,6 +58,13 @@ ColumnLayout {
}
}
NToggle {
label: I18n.tr("settings.launcher.settings.grid-view.label")
description: I18n.tr("settings.launcher.settings.grid-view.description")
checked: Settings.data.appLauncher.viewMode === "grid"
onToggled: checked => Settings.data.appLauncher.viewMode = checked ? "grid" : "list"
}
NToggle {
label: I18n.tr("settings.launcher.settings.clipboard-history.label")
description: I18n.tr("settings.launcher.settings.clipboard-history.description")

View File

@@ -9,6 +9,89 @@ ColumnLayout {
id: root
spacing: Style.marginL
property list<var> cardsModel: []
property list<var> cardsDefault: [
{
"id": "calendar-header-card",
"text": I18n.tr("settings.location.calendar.header.label"),
"enabled": true,
"required": true
},
{
"id": "calendar-month-card",
"text": I18n.tr("settings.location.calendar.month.label"),
"enabled": true,
"required": false
},
{
"id": "timer-card",
"text": I18n.tr("calendar.timer.title"),
"enabled": true,
"required": false
},
{
"id": "weather-card",
"text": I18n.tr("settings.location.weather.section.label"),
"enabled": true,
"required": false
}
]
function saveCards() {
var toSave = [];
for (var i = 0; i < cardsModel.length; i++) {
toSave.push({
"id": cardsModel[i].id,
"enabled": cardsModel[i].enabled
});
}
Settings.data.calendar.cards = toSave;
}
Component.onCompleted: {
// Starts empty
cardsModel = [];
// Add the cards available in settings
for (var i = 0; i < Settings.data.calendar.cards.length; i++) {
const settingCard = Settings.data.calendar.cards[i];
for (var j = 0; j < cardsDefault.length; j++) {
if (settingCard.id === cardsDefault[j].id) {
var card = cardsDefault[j];
card.enabled = settingCard.enabled;
// Auto-disable weather card if weather is disabled
if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) {
card.enabled = false;
}
cardsModel.push(card);
}
}
}
// Add any missing cards from default
for (var i = 0; i < cardsDefault.length; i++) {
var found = false;
for (var j = 0; j < cardsModel.length; j++) {
if (cardsModel[j].id === cardsDefault[i].id) {
found = true;
break;
}
}
if (!found) {
var card = cardsDefault[i];
// Auto-disable weather card if weather is disabled
if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) {
card.enabled = false;
}
cardsModel.push(card);
}
}
saveCards();
}
NHeader {
label: I18n.tr("settings.location.location.section.label")
description: I18n.tr("settings.location.location.section.description")
@@ -85,14 +168,6 @@ ColumnLayout {
enabled: Settings.data.location.weatherEnabled
}
NToggle {
label: I18n.tr("settings.location.weather.show-in-calendar.label")
description: I18n.tr("settings.location.weather.show-in-calendar.description")
checked: Settings.data.location.showCalendarWeather
onToggled: checked => Settings.data.location.showCalendarWeather = checked
enabled: Settings.data.location.weatherEnabled
}
NToggle {
label: I18n.tr("settings.location.weather.show-effects.label")
description: I18n.tr("settings.location.weather.show-effects.description")
@@ -108,6 +183,62 @@ ColumnLayout {
Layout.bottomMargin: Style.marginL
}
// Calendar Cards Management Section
ColumnLayout {
spacing: Style.marginXXS
Layout.fillWidth: true
NHeader {
label: I18n.tr("settings.location.calendar.cards.section.label")
description: I18n.tr("settings.location.calendar.cards.section.description")
}
Connections {
target: Settings.data.location
function onWeatherEnabledChanged() {
// Auto-disable weather card when weather is disabled
var newModel = cardsModel.slice();
for (var i = 0; i < newModel.length; i++) {
if (newModel[i].id === "weather-card") {
newModel[i] = Object.assign({}, newModel[i], {
"enabled": Settings.data.location.weatherEnabled
});
cardsModel = newModel;
saveCards();
break;
}
}
}
}
NReorderCheckboxes {
Layout.fillWidth: true
model: cardsModel
disabledIds: Settings.data.location.weatherEnabled ? [] : ["weather-card"]
onItemToggled: function (index, enabled) {
var newModel = cardsModel.slice();
newModel[index] = Object.assign({}, newModel[index], {
"enabled": enabled
});
cardsModel = newModel;
saveCards();
}
onItemsReordered: function (fromIndex, toIndex) {
var newModel = cardsModel.slice();
var item = newModel.splice(fromIndex, 1)[0];
newModel.splice(toIndex, 0, item);
cardsModel = newModel;
saveCards();
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL
Layout.bottomMargin: Style.marginL
}
// Date & time section
ColumnLayout {
spacing: Style.marginM

View File

@@ -74,6 +74,10 @@ ColumnLayout {
{
"key": "bottom_right",
"name": I18n.tr("options.launcher.position.bottom_right")
},
{
"key": "bar",
"name": I18n.tr("options.launcher.position.bar")
}
]
currentKey: Settings.data.notifications.location || "top_right"

Some files were not shown because too many files have changed in this diff Show More