Compare commits

...

1457 Commits

Author SHA1 Message Date
Ly-sec
b22069c455 Release v2.15.1 2025-10-03 13:15:26 +02:00
Ly-sec
b391d03967 Background: revert to old version which fixed the RAM issue
Overview: only load if niri event-stream emits overview active
2025-10-03 13:13:40 +02:00
ItsLemmy
adb84a9e24 Shell: replacing LazyLoader by Loader in an attempt to fix crash when hot-reloading after update. 2025-10-02 22:29:00 -04:00
Ly-sec
4b84e48e8e Overview: potential fix for fallback wallpaper showing after logout/login 2025-10-02 17:06:30 +02:00
Lysec
20cbc03b22 Merge pull request #409 from acdcbyl/main
Matugen: Add 'org.gnome.desktop.interface' related post_hooks for GTK3/4
2025-10-02 16:20:11 +02:00
Aiser
aa33747686 Matugen: Add 'org.gnome.desktop.interface' related post_hooks for GTK 3/4 2025-10-02 22:17:17 +08:00
ItsLemmy
49a0c8449f Tooltips: fixed a bunch of tooltips which where not following the screen's scaling 2025-10-01 16:50:54 -04:00
ItsLemmy
88871e3fbe ActiveWindow-MediaMini: added a minimum size 2025-10-01 15:47:01 -04:00
ItsLemmy
b3989a13da MediaMini: better behavior on smaller screen where the placeholder text may not fit in the capsule 2025-10-01 15:41:31 -04:00
ItsLemmy
07a94de5e2 Shell: more robust reload 2025-10-01 10:43:19 -04:00
ItsLemmy
994f0ca812 Revert "i18n: grab full locale"
This reverts commit 1c1cb8e026.
2025-10-01 10:37:31 -04:00
Ly-sec
1c1cb8e026 i18n: grab full locale 2025-10-01 16:17:35 +02:00
Ly-sec
74270e9478 Set version to dev 2025-10-01 15:54:54 +02:00
Ly-sec
8c9396f325 Release v2.15.0 2025-10-01 15:51:51 +02:00
ItsLemmy
afccf048e7 Taskbar: inactive icon bumped from 0.5 to 0.6 opacity 2025-10-01 09:40:33 -04:00
ItsLemmy
f37625719d Clock: removed useMonospacedFont to keep things simple, + translations + cleanup 2025-10-01 09:20:14 -04:00
Lemmy
cad8fd671f Merge pull request #398 from DiscoCevapi/add-clock-font-setting
Add clock font setting for customizable clock displays
2025-10-01 09:13:18 -04:00
DiscoNiri
68e76abfc7 Move clock font settings to widget-specific configuration
- Moved clock font selection from general settings to clock widget settings
- Added custom font toggle and selection in ClockSettings.qml
- Updated BarWidgetRegistry.qml with new clock font metadata
- Removed global clockFont setting from Settings.qml and GeneralTab.qml
- Updated Clock.qml to use widget-specific custom font setting
- Added proper translation keys for new font options
- Maintained backward compatibility with existing font hierarchy
2025-10-01 20:26:13 +10:00
Lemmy
45c8fe7782 Merge pull request #358 from lonerOrz/fix/brightness
Fix brightness sync after external command changes
2025-09-30 22:49:41 -04:00
ItsLemmy
5ebf4b5377 i18n: launcher terminal-command 2025-09-30 22:45:00 -04:00
Lemmy
59fbe92fe4 Merge pull request #377 from lonerOrz/fix/launcher
fix: the launcher cannot run pure command-line (CLI) programs
2025-09-30 22:44:09 -04:00
ItsLemmy
b051e19f68 i18n: updated all translations via autotranslate! 2025-09-30 22:32:37 -04:00
ItsLemmy
6b9370ac85 i18n: added basic auto translation 2025-09-30 22:24:25 -04:00
lonerorz
9702a300ca Merge branch 'main' into fix/launcher 2025-10-01 10:11:12 +08:00
ItsLemmy
b043664617 Taskbar: Improved the look of the focused app. Made unfocused app icons semi transparent. 2025-09-30 21:33:06 -04:00
ItsLemmy
368e80daf2 .gitignore cleanup 2025-09-30 20:29:18 -04:00
ItsLemmy
056217bf43 Wallpaper: fix double wallpaper init. 2025-09-30 20:24:23 -04:00
ItsLemmy
c1abb3a7dc Default settings updated with Dock's: only same output. 2025-09-30 19:50:24 -04:00
ItsLemmy
52d2055699 MediaMini: fix another binding loop. 2025-09-30 18:20:28 -04:00
ItsLemmy
e324a33137 NiriService: added safe guards to avoid issue with wrong window indexes. 2025-09-30 18:16:35 -04:00
ItsLemmy
6f4aa1a1a1 MediaMini: fix binding loop + edge case where no icon would appear. Also set Autohide to false by default for ActiveWindow and MediaMini 2025-09-30 17:56:59 -04:00
Lemmy
f49462f999 Merge pull request #402 from luleyleo/output-filtered-dock
Per-monitor dock
2025-09-30 17:36:14 -04:00
Leopold Luley
4fb1e2de1e i18n: Add German translation for new dock settings. 2025-09-30 23:07:24 +02:00
Leopold Luley
6d05a20556 Dock: Reformat code. 2025-09-30 23:03:09 +02:00
Leopold Luley
ec2fbb53dc Dock: Allow showing the dock on outputs without a bar. 2025-09-30 23:02:13 +02:00
Leopold Luley
fdc61acfe4 Dock: Add option to filter by output. 2025-09-30 23:01:46 +02:00
Ly-sec
32712c7052 MediaMini: replace placeholder icon 2025-09-30 19:23:18 +02:00
Ly-sec
a0f6d14334 MediaMini: add no active player placeholder 2025-09-30 18:37:45 +02:00
Lysec
6ae8d8536e Merge pull request #400 from acdcbyl/main
i18n: Optimize Chinese translation
2025-09-30 15:35:15 +02:00
Aiser
650dcb8811 i18n: Optimize Chinese translation 2025-09-30 21:32:03 +08:00
ItsLemmy
970684e304 Niri: temp warning fix 2025-09-30 08:07:18 -04:00
Lemmy
e786946abf Merge pull request #394 from ixxie/feat/temp-settings
[NixOS] feat/temp settings
2025-09-30 07:55:14 -04:00
Lemmy
da046cade6 Merge pull request #396 from luleyleo/mouse-sorted-taskbar
NiriService: Keep windows sorted when moving them with the mouse
2025-09-30 07:51:37 -04:00
ItsLemmy
43dee793de More pointSize cleanup 2025-09-30 07:44:03 -04:00
Lysec
0a893f9c5f Merge pull request #399 from pugaizai/main
i18n: update zh-CN translations
2025-09-30 13:28:06 +02:00
Ly-sec
23887574cf NIcon: fix fontSize 2025-09-30 13:12:49 +02:00
pugaizai
2008ba85bc update sessionmenu translation 2025-09-30 19:07:49 +08:00
Ly-sec
773318191d NIcon: use textSize for font.pointSize 2025-09-30 13:02:56 +02:00
pugaizai
78cf0bc8a2 i18n: update zh-CN translations 2025-09-30 18:42:59 +08:00
DiscoNiri
8b0e0f6e0e Add clock font setting for customizable clock displays
This commit adds a new 'Clock Font' setting that allows users to customize
the font used specifically for clock displays in the bar and widgets,
independent of the default UI font.

Features:
- New clockFont property in Settings.data.ui (defaults to 'Roboto')
- Updated Bar Clock widget to use the custom font with fallback support
- Added searchable font dropdown in General Settings tab
- Backward compatible - uses default font if clockFont is not set
- Real-time updates - changes apply immediately

The font selection uses FontService.availableFonts and includes proper
fallback logic that respects the existing monospaced font setting.
2025-09-30 18:37:47 +10:00
Lysec
8c6b3a793f Merge pull request #397 from msdevpt/apply-theme
chore: refresh ghostty configuration
2025-09-30 09:37:42 +02:00
M.Silva
4c3eca80a4 chore: refresh ghostty configuration 2025-09-30 08:32:01 +01:00
Leopold Luley
f61f9a5809 NiriService: Keep windows sorted when moving them with the mouse. 2025-09-30 09:01:58 +02:00
ItsLemmy
518e90d910 SystemMonitor: apply fontScale to TextMetrics for smarted calculation 2025-09-29 21:46:10 -04:00
ItsLemmy
d2e5d0664a Font: added reset button for scaling 2025-09-29 21:42:47 -04:00
ItsLemmy
602d79c98e TrayMenu: fix icon size 2025-09-29 21:38:51 -04:00
ItsLemmy
4b13e89a64 Font: added per font family scaling. removed billboard font 2025-09-29 21:31:45 -04:00
ItsLemmy
1e8b122911 NiriService: syntax fix 2025-09-29 21:19:08 -04:00
Ly-sec
1f257ce847 ControlCenter: fix custom image 2025-09-30 01:33:09 +02:00
Matan Bendix Shenhav
df35589328 feat(flake): write settings to a fallback path 2025-09-30 00:11:03 +02:00
Matan Bendix Shenhav
c92478d27d feat(flake): restart systemd service on package update 2025-09-30 00:10:32 +02:00
Lemmy
ffe39e0ec9 Merge pull request #393 from luleyleo/sorted-taskbar
Sort windows in Taskbar by their scrolling position on Niri
2025-09-29 18:08:50 -04:00
ItsLemmy
b12cf345dc Background Wallpaper: attempt to free up memory earlier. 2025-09-29 16:53:59 -04:00
ItsLemmy
fc4418be0c Shader: fix "disc" shader (no disc at 0 progress) 2025-09-29 16:53:33 -04:00
Leopold Luley
82bfa346a7 NiriService: Fix stale focus state when opening a new window. 2025-09-29 22:16:46 +02:00
Leopold Luley
26ee5046f6 NiriService: Sort windows by their scrolling position. 2025-09-29 22:16:25 +02:00
ItsLemmy
51ed6ea2b0 Compositor: fix getFocusedWindow() 2025-09-29 15:10:44 -04:00
ItsLemmy
c53dd6fade Compositor: fix getFocusedWindowTitle. Since active workspace has been implemented.
+ autoformatting
2025-09-29 15:04:13 -04:00
Lemmy
bb24b6904d Merge pull request #386 from luleyleo/filtered-taskbar
Taskbar: Filter by screen and workspace
2025-09-29 15:02:31 -04:00
Ly-sec
d5857e3363 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-29 16:32:00 +02:00
Ly-sec
559609be64 Launcher: add pin to dock button if dock is enabled 2025-09-29 16:31:53 +02:00
ItsLemmy
5cea61114b Scaling: fix scaling not properly applied on startup. 2025-09-29 10:20:19 -04:00
ItsLemmy
22794ea922 DateTime: proper locale usage. Fix #390
Replaced all Qt.formatDateTime() by Qt.locale().toString()
2025-09-29 10:07:58 -04:00
ItsLemmy
933ba54612 Init Sequence: minor reordering 2025-09-29 09:58:48 -04:00
ItsLemmy
0d0b9a21f2 Wallpaper Selector: added a shortcut to the wallpaper settings in the top bar. 2025-09-29 09:25:45 -04:00
ItsLemmy
9ed9231070 Init Sequence: removed a bunch of no longer necessary Settings.isLoaded 2025-09-29 09:11:37 -04:00
Ly-sec
b8b54825d5 SessionMenu: move lockAndSuspend to CompositorService 2025-09-29 14:20:15 +02:00
Ly-sec
250822e819 Revert "Matugen: add custom-colors.toml"
This reverts commit ece9789f6b.
2025-09-29 14:13:22 +02:00
Ly-sec
ece9789f6b Matugen: add custom-colors.toml 2025-09-29 13:43:37 +02:00
Ly-sec
f11d27bcf1 Background: "explicitly set currentWallpaper.source to nothing as an
attempt to fix the odd memory usage after a few hours"
2025-09-29 13:18:45 +02:00
Ly-sec
0e69256279 Background: fix short flash of default wallpaper before actual wallpaper shows 2025-09-29 13:13:21 +02:00
Leopold Luley
fa49d4aaa0 Taskbar: Add German translation for Taskbar settings. 2025-09-29 11:08:48 +02:00
Leopold Luley
b1f7ae5d9a Taskbar: Add settings. 2025-09-29 11:01:14 +02:00
Leopold Luley
e6b0be77e7 Taskbar: Filter by same output and active workspaces. 2025-09-29 11:01:14 +02:00
ItsLemmy
49961882dd Shell: changed init sequence so that i18n + Settings are fully loaded before any UI component spawn. 2025-09-28 23:39:34 -04:00
ItsLemmy
c1d2d82fa2 NSpinBox: fixes
- replaced row by rowlayount
- using proper Color.mOnTertiary for hover text/icon
- fixed binding break when entering value manually
2025-09-28 21:19:10 -04:00
ItsLemmy
c35f37c7d7 Use Color.transparent instead of "transparent" 2025-09-28 21:17:10 -04:00
Lemmy
e23cb90c5b Merge pull request #388 from MrDowntempo/Consistent-Hover
Nicer SpinBox with better mTertiary hover
2025-09-28 20:53:24 -04:00
ItsLemmy
b2688e9100 More conversion of Row/Column to Layout 2025-09-28 20:49:57 -04:00
ItsLemmy
7f3842ddbf Log cleanup (avoid super long string with path) 2025-09-28 20:39:28 -04:00
ItsLemmy
68b2c83be1 DockMenu: use RowLayout and ColumnLayout 2025-09-28 20:35:25 -04:00
Corey Woodworth
97fa2fb1b5 Back to Chevrons. +/- were inconsistent sizes. Better alignment 2025-09-28 20:20:02 -04:00
ItsLemmy
0ed8ed7fe5 Tooltips: fix clipping for tooltips with long sentences. 2025-09-28 19:45:37 -04:00
Corey Woodworth
a41be0b5d9 Removed gradient and redesigned buttons 2025-09-28 19:08:33 -04:00
ItsLemmy
072d80e2f3 Bar vs Dock: Dock are loaded only once the bar is fully loaded. This ensure the vertical bar use the full screen height if the dock is exclusive. 2025-09-28 16:39:23 -04:00
loner
1f898171e0 Merge remote-tracking branch 'upstream/main' into fix/launcher
# Conflicts:
#	Assets/Translations/zh-CN.json
2025-09-29 03:22:48 +08:00
loner
ef64395dd4 Resolve conflict 2025-09-29 03:09:30 +08:00
loner
a5c89fadb5 fix(services): emit brightnessUpdated signal in setBrightness 2025-09-29 02:40:01 +08:00
loner
cccf0e6017 fix: Fix brightness synchronization in multi-monitor setups 2025-09-29 02:34:42 +08:00
Ly-sec
5da474007e i18n: add lock-and-suspend to all languages 2025-09-28 19:53:20 +02:00
Ly-sec
ffd2cdaf74 SessionMenu: add lock & suspend option as requested in #301 2025-09-28 19:50:52 +02:00
MrDowntempo
5f3c088f22 Update NSpinBox.qml
I missed a line
2025-09-28 13:16:07 -04:00
MrDowntempo
382116e795 Merge branch 'main' into Consistent-Hover 2025-09-28 13:10:13 -04:00
Ly-sec
c7c49433f7 NotificationService: add flatpak name support 2025-09-28 19:08:04 +02:00
Corey Woodworth
0d2d0f1931 Nicer SpinBox with better mTertiary hover 2025-09-28 12:49:52 -04:00
Ly-sec
2e947edc5a Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-28 18:42:59 +02:00
Ly-sec
cdc32f3eac NSpinBox: add text input support 2025-09-28 18:42:53 +02:00
ItsLemmy
21736b3095 DockMenu: auto hides when not hovering the menu, simplified with a single mouse area. 2025-09-28 12:06:41 -04:00
ItsLemmy
48852a9ca4 Tray: close the menu on re-hovering the tooltip 2025-09-28 11:37:12 -04:00
ItsLemmy
65fab7b367 Tray: Fixing hiding tooltip 2025-09-28 11:17:02 -04:00
ItsLemmy
dc414df9bc NRadioButton: proper elipsis. Fix #385 2025-09-28 11:09:17 -04:00
ItsLemmy
69a6c052db LockScreen: adapted custom tooltips to the new lighter look. 2025-09-28 10:55:48 -04:00
ItsLemmy
c422435d3d Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-28 10:52:09 -04:00
ItsLemmy
fc1742e167 Tooltips: proper tooltip service 2025-09-28 10:51:56 -04:00
ItsLemmy
061e7f32da Tooltips: proper tooltip service 2025-09-28 10:40:15 -04:00
Lemmy
8dda007847 Merge pull request #371 from pugaizai/main
allow zh-CN like language code
2025-09-28 09:53:16 -04:00
pugaizai
1cdff28cca Merge from upstream 2025-09-28 21:43:50 +08:00
铺盖崽
f32a34e320 Rename zh.json to zh-CN.json 2025-09-28 21:34:02 +08:00
铺盖崽
0d0088bd52 allow zh-CN like language code 2025-09-28 21:34:02 +08:00
ItsLemmy
a7a7a96585 Merge branch 'tooltips' 2025-09-28 09:23:42 -04:00
ItsLemmy
026d602770 Tooltips: more robust tooltips after hot-reload 2025-09-28 09:23:28 -04:00
Ly-sec
5b54be633d Aya: rename to ayu (probably a typo) 2025-09-28 13:07:51 +02:00
Lysec
3bb10e9561 Merge pull request #383 from acdcbyl/main
i18n: Optimize Chinese translation
2025-09-28 11:44:04 +02:00
Aiser
b9b233a873 i18n: Optimize Chinese translation 2025-09-28 17:38:43 +08:00
Ly-sec
388824bf37 i18n: add description to all Bar widget settings 2025-09-28 11:16:26 +02:00
Ly-sec
25eb31747a ColorSchemeTab: hide predefined colorschemes when matugen is enabled 2025-09-28 10:43:02 +02:00
Lysec
f7109b0bf9 Merge pull request #382 from acdcbyl/main
i18n: Optimize Chinese translation
2025-09-28 10:08:48 +02:00
Aiser
c41fa1aef7 i18n: Optimize Chinese translation 2025-09-28 16:03:59 +08:00
Aiser
1a0ea3893c i18n: Optimize Chinese translation 2025-09-28 15:52:54 +08:00
ItsLemmy
0593543d7a Tooltip: Refactoring in a single global tooltip. 2025-09-28 00:15:43 -04:00
ItsLemmy
fbf80ab577 v2.14.4-dev 2025-09-27 20:40:48 -04:00
ItsLemmy
7e9f7f40ef v2.14.4 2025-09-27 20:40:15 -04:00
ItsLemmy
92460fc5c3 IPC call to enable/disable/toggle wallpaper random automation. Fix #378 2025-09-27 18:22:57 -04:00
ItsLemmy
c1c91edb6c NButton: no bar position is allowed in Widgets/
- Only exception is NPanel.
2025-09-27 17:51:52 -04:00
ItsLemmy
e73d85de04 Bluetooth: Removed the copy of the adapter's state in Settings, makes code much simpler and robust by always relying on the actual adapter's state. 2025-09-27 17:33:09 -04:00
ItsLemmy
fafd7a518b Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-27 16:12:29 -04:00
ItsLemmy
8b89e95b13 New setting to disable all UI animations 2025-09-27 16:12:28 -04:00
ItsLemmy
2112f675c0 Taskbar: fix warning due to non existing property. 2025-09-27 16:12:11 -04:00
Lemmy
d873c2205b Merge pull request #380 from ixxie/feat/flake-defaults
feat(flake): deep merge settings with defaults
2025-09-27 16:00:42 -04:00
ItsLemmy
348c1e8f9f General: Animation speed max back to 200% 2025-09-27 15:01:40 -04:00
ItsLemmy
8e248f6795 Tooltip: removed auto-positionning relative to the bar. as many tooltips are used in panels
- still a few edge cases to work on
2025-09-27 14:57:11 -04:00
ItsLemmy
4c516200dc SystemMonitor: syntax error 2025-09-27 14:19:26 -04:00
ItsLemmy
b5b8b62cf0 Animation speed: allow 500% speed for quasi instant. 2025-09-27 14:03:54 -04:00
ItsLemmy
a4b4caa2ce Bar SysMonitor: Implemented different sizing strategy to avoid unwanted shifting of items inside and outside the component. 2025-09-27 13:38:56 -04:00
Lemmy
423ea60939 Merge pull request #372 from MrDowntempo/Centered-Circles
Centered circles
2025-09-27 13:24:17 -04:00
MrDowntempo
55dd48ce66 Merge branch 'noctalia-dev:main' into Centered-Circles 2025-09-27 12:37:38 -04:00
Corey Woodworth
7dc8d2cd88 Fix: Works regardless of scaling value 2025-09-27 12:34:58 -04:00
Ly-sec
d4dd3b1734 ColorSchemeTab: hide matugen scheme type when Matugen is disabled 2025-09-27 17:20:47 +02:00
Ly-sec
0f30a10a14 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-27 17:16:47 +02:00
Ly-sec
50e2a95f52 Update settings-default 2025-09-27 17:16:37 +02:00
Ly-sec
35bf30ef5e ColorSchemeTab: add matugen type option 2025-09-27 17:16:00 +02:00
ItsLemmy
afce091473 Bluetooth: simplify the way we handle adapter state vs settings value. 2025-09-27 11:03:52 -04:00
Matan Bendix Shenhav
d802b6a2fa feat(flake): deep merge settings with defaults 2025-09-27 16:28:44 +02:00
ItsLemmy
65cd95c62b Notifications: properly handle large/many action buttons. Fix #379 2025-09-27 09:17:23 -04:00
Ly-sec
fe2654268d NightLight: check if wlsunset exists, else dont enable NightLight
SystemMonitorSettings: If RAM usage is not toggled, don't show % option
Settings: remove NightLight from default bar widgets
2025-09-27 15:14:44 +02:00
ItsLemmy
13e32dc11b Notifications test with a lot of actions 2025-09-27 08:58:48 -04:00
loner
b27728e5bf i18n(zh): add translation for terminal command 2025-09-27 12:12:31 +08:00
loner
2379ad134b i18n(pt): add translation for terminal command 2025-09-27 12:12:21 +08:00
loner
3ab9ffed78 i18n(fr): add translation for terminal command 2025-09-27 12:12:11 +08:00
loner
3182d1969b i18n(es): add translation for terminal command 2025-09-27 12:11:53 +08:00
loner
591d099255 i18n(de): add translation for terminal command 2025-09-27 12:11:43 +08:00
loner
256f9b4a76 feat(launcher): add configurable terminal command
The terminal command for launching applications was previously hardcoded to 'kitty', causing issues for users without it installed.

This change introduces a new setting, 'appLauncher.terminalCommand', allowing users to specify their preferred terminal emulator. The default value is set to 'xterm -e'.

The implementation includes:
- Defining the setting in 'Commons/Settings.qml'.
- Adding a text input in the launcher settings tab.
- Updating the application plugin to use the new setting.
2025-09-27 12:06:54 +08:00
ItsLemmy
dd29a739f3 v2.14.3-dev 2025-09-26 23:48:03 -04:00
ItsLemmy
83d82a825b v2.14.3 2025-09-26 23:46:25 -04:00
ItsLemmy
e2f7012c5b NScrollView: properly disable horizontal scrrol when setting proper horizontalPolicy 2025-09-26 23:35:05 -04:00
loner
ff1509939a test kitty 2025-09-27 11:29:57 +08:00
ItsLemmy
f8ee0bb8df FilePicker: debugging and improvements. 2025-09-26 23:21:56 -04:00
ItsLemmy
96d3051151 Update service 2025-09-26 23:18:35 -04:00
Lysec
e8e96a9f68 Merge pull request #376 from kevindiaz314/main
fix(ci): not in a git directory
2025-09-27 02:02:44 +02:00
Kevin Diaz
b7c99905f3 fix(ci): not in a git directory 2025-09-26 20:00:56 -04:00
Lysec
ab89b0e964 Merge pull request #375 from kevindiaz314/main
CI: add GitHub Actions workflow to automate AUR package updates on release
2025-09-27 01:19:48 +02:00
Kevin Diaz
7b9ecd048d CI: add GitHub Actions workflow to automate AUR package updates on release 2025-09-26 19:18:16 -04:00
Corey Woodworth
9d30eac13a Fix: Correct same issue with Radio Buttons too. 2025-09-26 16:01:25 -04:00
Corey Woodworth
4785e287ba Fix: Small fix. 4* instead of 2*2* 2025-09-26 15:37:02 -04:00
Corey Woodworth
aa1cea8d03 Fix: Fix the vertical alignment of circles 2025-09-26 15:30:16 -04:00
Lemmy
823ab9c6a3 Merge pull request #370 from MrDowntempo/Just-The-Tip
Rounds the ends of NSliders to be more consistent with the look
2025-09-26 14:58:19 -04:00
Corey Woodworth
74a0c9dbf4 Fix: Knob was getting clipped. 2025-09-26 14:22:22 -04:00
Corey Woodworth
d1a89387f9 Fix: Make sure left side doesn't get squished 2025-09-26 13:24:19 -04:00
Corey Woodworth
9da310ade4 Rounds the ends of NSliders to be more consistent with the rest of Noctalia's look 2025-09-26 11:01:38 -04:00
Lemmy
348604e45a Merge pull request #368 from MrDowntempo/Old-Theme
Restored the vintage Noctalia theme as Noctalia (legacy)
2025-09-26 10:24:28 -04:00
Ly-sec
5e44af8e6d Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-26 16:23:08 +02:00
Ly-sec
27eaeee5fd i18n-zh/pt: add missing keys 2025-09-26 16:23:04 +02:00
Corey Woodworth
338f4cde6d Restored the vintage Noctalia theme as Noctalia (legacy) 2025-09-26 10:20:01 -04:00
ItsLemmy
1531275707 Wallpaper: smarter init 2025-09-26 10:09:17 -04:00
Ly-sec
5cfa66f9e8 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-26 15:07:30 +02:00
Ly-sec
695d002d6a OsdTab: move all OSD related settings into their own tab
OSD: add Left/Right Center options (will display vertically)
TablerIcons: add OSD Tab icon
i18n: added translation to all files for OSDTab (generated)
2025-09-26 15:05:53 +02:00
ItsLemmy
7afd0177cb Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-26 08:33:32 -04:00
ItsLemmy
180366073f Toast: less intrusive toast logging 2025-09-26 08:33:30 -04:00
Lysec
7eb19237ba Merge pull request #366 from pugaizai/main
i18n(zh): add zh(simplified chinese) translation
2025-09-26 14:21:01 +02:00
铺盖崽
ed7b4f5552 i18n(zh): add zh(simplified chinese) translation 2025-09-26 20:19:43 +08:00
Lysec
9d927bd7fc Merge pull request #364 from lonerOrz/opt/osd
Increase OSD initTimer interval to 500ms
2025-09-26 09:18:36 +02:00
loner
ac683caa1e Increase OSD initTimer interval to 500ms 2025-09-26 13:50:14 +08:00
ItsLemmy
39883ceb10 WallpaperService: proper i18n support of the list models. 2025-09-25 23:35:18 -04:00
ItsLemmy
c1386c491e v2.14.1-dev 2025-09-25 21:58:33 -04:00
ItsLemmy
e7f8a452b8 v2.14.1 2025-09-25 21:57:39 -04:00
ItsLemmy
012ae28dd9 Bar editor: removing the last ControlCenter triggers a toast warning. 2025-09-25 21:54:51 -04:00
ItsLemmy
95d059007e ClipboardService: fix invalid toast invocation 2025-09-25 21:54:09 -04:00
ItsLemmy
b76a252b94 Screencorners: if bar is not visible have them in actual cornes (similar to floating bar) Fix #362 2025-09-25 21:31:49 -04:00
ItsLemmy
6bd4167638 FilePicker: better icons positioning 2025-09-25 21:13:13 -04:00
ItsLemmy
22b843587c FilePicker: back to our custom file picker. 2025-09-25 20:59:50 -04:00
ItsLemmy
cb3fc1a45c Bar: Right clicking the bar will open the ControlCenter 2025-09-25 17:18:07 -04:00
ItsLemmy
b1df7624cc Settings: bullet proofing the widget upgrade code. 2025-09-25 17:09:00 -04:00
Lemmy
8be64359ef Merge pull request #359 from juvevood/osd-toast-location
The locations of OSD and Toast follow the notifications location
2025-09-25 13:44:42 -04:00
ItsLemmy
8e6badc0d6 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-25 13:22:06 -04:00
ItsLemmy
4ac27be0e8 NPanel: don't dim if panel is masked 2025-09-25 13:21:57 -04:00
Ly-sec
2a496a7831 UpdaterService: set dev version 2025-09-25 17:41:34 +02:00
Ly-sec
619420349c i18n: add keep awake to all languages 2025-09-25 17:34:57 +02:00
Ly-sec
349ef85648 Release v2.14.0
This release introduces new themes, a native file picker, multi-language support, a redesigned clock/calendar widget, unified controls, and major quality-of-life improvements alongside numerous fixes and refinements—delivering a smoother and more polished experience.

- **Brand new themes:** Try the beautiful Noctalia and Aya themes for an upgraded look.
- **New file picker:** Picking files just got easier with a seamless native picker.
- **International:** Noctalia is now available in English, French, German, Spanish and Portuguese, with more languages on the way.
- **Revamped clock/calendar:** Enjoy a sleeker, more compact calendar integrated right into your bar.
- **Unified Volume & Brightness controls:** Our new On-Screen Display (OSD) feature lets you see brightness and volume adjustments in real-time, directly on your screen as you make them.
- **Pin your dock apps:** Pin favorites, group them better, and access everything with a right click.
- **Bar Widget Setting addition:** Now you can easily move widgets from one section to another.

- **ActiveWindow and MediaMini widgets:** Cleaner display, better media controls, and improved logic if nothing’s playing.
- **Notification system:** Choose where notifications appear, see progress bars, and enjoy refined layouts and scaling.
- **Workspace switching:** Switch workspaces just by scrolling - no extra clicks needed.
- **System widgets:** New monitor and side panel for greater control.
- **Bar & dock:** Faster, more reliable dragging, better icons, tooltips, and search for widgets.
- **Icons:** We have incorporated the Hyprland logo into the font as a new glyph.

- Reduced margin/alignment issues and bugs in the lock screen, notifications, and OSD.
- The volume system is now smarter and works seamlessly across sinks and sources.
- Lots of little bug fixes for panels, widgets, and popups, all aimed at a smoother experience.
2025-09-25 16:30:08 +02:00
ItsLemmy
b38cf8ef66 i18n: json check script with more colors 2025-09-25 09:51:00 -04:00
ItsLemmy
23c83a49c3 i18n-es: 100% 2025-09-25 09:42:33 -04:00
ItsLemmy
1926008315 i18n-pt: 100% 2025-09-25 09:37:45 -04:00
ItsLemmy
deb75f5bab i18n: json check script now support an argument to review a single language 2025-09-25 09:31:32 -04:00
ItsLemmy
53baf1c86b Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-25 09:27:59 -04:00
ItsLemmy
8173919692 i18n-fr: 100% 2025-09-25 09:27:56 -04:00
Ly-sec
ece8705e5d i18n: de - remove some keys 2025-09-25 15:23:00 +02:00
ItsLemmy
346d29d94a i18n: en: no audio codecs 2025-09-25 09:19:34 -04:00
ItsLemmy
a3f604efc3 en: no audio codecs translation 2025-09-25 09:14:30 -04:00
ItsLemmy
0e8a920ee2 Do not translate audio codecs name 2025-09-25 09:13:43 -04:00
ItsLemmy
e98e034a68 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-25 09:11:33 -04:00
ItsLemmy
1f3cafb1b9 i18n-json-check: report line numbers and sort by descending for easier editing. 2025-09-25 09:11:31 -04:00
Ly-sec
316cd3114a Translations/de: remove extra keys, add missing keys 2025-09-25 15:07:26 +02:00
ItsLemmy
4c951cf380 i18n-json-check script 2025-09-25 09:00:14 -04:00
ItsLemmy
0f888fd734 MediaMini: autoHide 2025-09-25 08:49:01 -04:00
ItsLemmy
0690ac4996 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-25 08:42:11 -04:00
ItsLemmy
3809f290ed ActiveWindow: better autohide 2025-09-25 08:42:10 -04:00
Ly-sec
b1094bbfa0 NDateTimeTokens: replace ListView with js array 2025-09-25 14:37:43 +02:00
Ly-sec
644e24f409 ScreenRecorder: fix recording with both audio sources 2025-09-25 13:23:48 +02:00
Ly-sec
6f2d7516f0 Revert "MediaMini: hide when no media is playing"
This reverts commit 8dad25f79c.
2025-09-25 13:10:31 +02:00
Ly-sec
8dad25f79c MediaMini: hide when no media is playing 2025-09-25 12:11:49 +02:00
Juve
4a9f37a390 The locations of osd and toast follow the notifications location 2025-09-25 14:03:24 +08:00
ItsLemmy
36489491e4 Bar new IPC: ipc call bar toggle 2025-09-24 22:18:22 -04:00
loner
2c7038c504 Fix brightness sync after external command changes
Fix brightness sync after external command changes, improve brightness
module compatibility
2025-09-25 10:18:09 +08:00
ItsLemmy
846730361d autoformatting 2025-09-24 22:17:26 -04:00
Lemmy
428f3627b6 Merge pull request #356 from lonerOrz/fix/osd
Initialize volume silently
2025-09-24 22:05:08 -04:00
ItsLemmy
68b328c982 Better colors for mediamini 2025-09-24 21:38:45 -04:00
ItsLemmy
4dac2ffe88 Autoformatting + cleanup 2025-09-24 21:33:00 -04:00
ItsLemmy
f3535f22ba ActiveWindow: hyprland fix 2025-09-24 21:22:52 -04:00
loner
deca5e1235 Initialize volume silently 2025-09-25 09:22:42 +08:00
ItsLemmy
8da903bb61 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-24 21:14:46 -04:00
ItsLemmy
b58f6f0a1b ActiveWindow: improve display when no active window 2025-09-24 21:14:44 -04:00
Ly-sec
946996917d Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-25 03:10:21 +02:00
Ly-sec
b03b4b0f13 i18n: fix control-center 2025-09-25 03:10:10 +02:00
Lemmy
73f76e2275 Merge pull request #357 from MrDowntempo/NoctaliaTheme
Added New Noctalia theme
2025-09-24 20:53:42 -04:00
ItsLemmy
80442e2839 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-24 20:48:05 -04:00
ItsLemmy
a8a1b0a422 ActiveWindow: similar behavior to MediaMini 2025-09-24 20:48:03 -04:00
Ly-sec
346e27830a MediaMini: small fixes 2025-09-25 02:46:31 +02:00
Ly-sec
ef616efcca i18n: small fix
autoformat
2025-09-25 02:44:27 +02:00
ItsLemmy
8c1153192d MediaMini: infinite scroll 2025-09-24 20:40:11 -04:00
ItsLemmy
c46a84d794 MediaMini: some more tweaks 2025-09-24 20:37:40 -04:00
ItsLemmy
46d3465b50 MediaMini: clip fix 2025-09-24 20:25:41 -04:00
Corey Woodworth
7bd278d428 Added New Noctalia theme 2025-09-24 20:19:15 -04:00
Ly-sec
2123b55aab MediaMini: small fixes 2025-09-25 01:37:17 +02:00
Ly-sec
4de6489cbf Settings: set scrollingTitle default to false 2025-09-25 01:02:26 +02:00
Ly-sec
96c2817e06 MediaMini: add scrolling support (as requested in #293) 2025-09-25 01:02:01 +02:00
Ly-sec
35a7ed165f BarSectionEditor: add search option (fixes #347) 2025-09-25 00:43:04 +02:00
Ly-sec
1c5b02fab4 Notification add ipc to clear history 2025-09-25 00:07:58 +02:00
Ly-sec
2afec4cc46 NotificationsTab: fix i18n 2025-09-25 00:01:50 +02:00
ItsLemmy
6dd6c6af74 Icons: added hyprland icons 2025-09-24 17:47:48 -04:00
ItsLemmy
d86686704c Bar: slightly more compact calendar 2025-09-24 17:17:09 -04:00
ItsLemmy
22b8edb023 OSD: Single component instance. Multi monitor support (follows notifications settings) 2025-09-24 17:05:57 -04:00
ItsLemmy
b96deaa0c3 Notification: simpler active loader conditions 2025-09-24 17:04:02 -04:00
ItsLemmy
0cb619a787 Workspace: slight adjustment to the inactive ws color. So it works better in every situation (with or without capsule) 2025-09-24 16:11:45 -04:00
ItsLemmy
63951ced9e Added Portuguese translation (automatically generated) 2025-09-24 14:17:28 -04:00
ItsLemmy
84502f4c9f Added Spanish translation (automatically generated) 2025-09-24 14:12:51 -04:00
ItsLemmy
430cc64fdb NHeader: fix label visibility 2025-09-24 14:12:32 -04:00
ItsLemmy
b93c733e7c autoformating 2025-09-24 13:52:44 -04:00
ItsLemmy
fe58e5e92a Merge branch 'i18n' 2025-09-24 13:52:29 -04:00
ItsLemmy
e6ae17cdd5 Audio: Debounce timer should not use Style.animationFast 2025-09-24 13:27:10 -04:00
Lemmy
b445153444 Merge pull request #352 from FUFSoB/audio-fixes
Small fixes for audio and auto-hide widgets
2025-09-24 13:23:42 -04:00
Lysec
6f85747d92 Merge pull request #353 from MrDowntempo/AyaTheme
Added Aya theme
2025-09-24 19:16:08 +02:00
Corey Woodworth
66360c2379 Added Aya theme 2025-09-24 13:14:35 -04:00
Ly-sec
7fe504aa8a Merge branch 'i18n' of https://github.com/noctalia-dev/noctalia-shell into i18n 2025-09-24 17:01:41 +02:00
Ly-sec
aca831e54d i18n: remove debug language 2025-09-24 17:01:31 +02:00
ItsLemmy
7da4b1d63c i18n: no debug 2025-09-24 10:58:31 -04:00
FUFSoB
f21bda0de9 other: change desc of overdrive settings toggle 2025-09-24 19:54:29 +05:00
FUFSoB
24ffedd599 bugfix: always hide display mode wasn't working 2025-09-24 19:50:10 +05:00
Ly-sec
7f9acccce7 i18n: remove some entries, edit some entries 2025-09-24 16:48:43 +02:00
ItsLemmy
084fb39abd NComboBox: simple js function 2025-09-24 10:24:45 -04:00
FUFSoB
06694f2428 bugfix: when changing sink after volume change, changes were applying to other sink 2025-09-24 19:20:44 +05:00
ItsLemmy
9105ec6b0d i18n: no more close side panel as its called control center 2025-09-24 10:17:28 -04:00
Ly-sec
9cfe49dec3 NComboBox: fix other languages display
Translations/de: update accordingly
2025-09-24 16:02:24 +02:00
ItsLemmy
58fb397e79 AudioTab: warning fix 2025-09-24 09:46:59 -04:00
Ly-sec
5de4330199 i18n: even more things appeared 2025-09-24 15:31:11 +02:00
Lemmy
5669debd6b Merge pull request #351 from FUFSoB/audio-changes
Audio changes
2025-09-24 09:29:23 -04:00
Lemmy
e71335f9b6 Update README.md 2025-09-24 09:17:54 -04:00
Ly-sec
24cb5823ee Merge branch 'i18n' of https://github.com/noctalia-dev/noctalia-shell into i18n 2025-09-24 14:53:11 +02:00
Ly-sec
1470a92556 i18n: more cases detected 2025-09-24 14:53:09 +02:00
ItsLemmy
1d98a657b2 i18n: service init asap, avoid spamming the console as some warnings are inevitable due to async loading behavior 2025-09-24 08:50:40 -04:00
ItsLemmy
2e1f6f0323 Font: auto reloading with cache busting. 2025-09-24 08:37:29 -04:00
Ly-sec
04f247905a i18n-check: updated detection
i18n: added some odd ones
2025-09-24 14:30:30 +02:00
Ly-sec
2bfed74851 i18n: even more integration
autoformat
2025-09-24 14:24:21 +02:00
Ly-sec
2a23b6afdd i18n: WAY more i18n conversion 2025-09-24 14:12:12 +02:00
Ly-sec
df70f0c824 Possibly got everything transfered over to i18n 2025-09-24 13:47:59 +02:00
Ly-sec
2285a3fb18 SettingsWindow: add i18n support 2025-09-24 13:20:49 +02:00
FUFSoB
ef5447d2fa bugfix: make volume consistent with wpctl get-volume 2025-09-24 14:11:44 +05:00
FUFSoB
fb64b3ba43 feat: volume overdrive 2025-09-24 14:04:08 +05:00
FUFSoB
1673201916 bugfix: update volume on sink/source changes 2025-09-24 13:03:39 +05:00
Lemmy
72475cd29b Merge pull request #344 from FUFSoB/notifications-refine
Notifications improvements
2025-09-23 23:01:33 -04:00
FUFSoB
41b9eb1897 Merge remote-tracking branch 'upstream/main' into notifications-refine
Resolve conflicts due to project structure changes
2025-09-24 07:40:50 +05:00
ItsLemmy
31db195087 First stab at i18n 2025-09-23 22:39:38 -04:00
ItsLemmy
9a9d68c78d NButton: Simplified by removing the press state which was causing issues with Popups opening hover the button 2025-09-23 15:32:24 -04:00
ItsLemmy
a2b57c5165 Panels: more reliable draggable toggling 2025-09-23 14:42:55 -04:00
ItsLemmy
e9efab0d59 Cava: also enable during lockscreen 2025-09-23 14:23:41 -04:00
FUFSoB
5d58083ee5 feat: progress bar for notifs 2025-09-23 22:57:19 +05:00
Ly-sec
055c7d3c20 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-23 18:42:07 +02:00
Ly-sec
0b5ef30b34 OSD: fix race condition 2025-09-23 18:42:05 +02:00
ItsLemmy
6d4ca4ffc0 OSD: moved settings in the appropriate spot 2025-09-23 12:40:40 -04:00
Ly-sec
4cd53c4083 OSD: unified Volume & Brightness OSD into one file (OSD.qml), move OSD settings to NotificationTab 2025-09-23 18:07:14 +02:00
Ly-sec
c6303cdb6b Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-23 17:53:55 +02:00
Ly-sec
c48e87e012 Settings: update default settings 2025-09-23 17:53:40 +02:00
Ly-sec
1ca84bf052 OSD: Implement Volume & Brightness OSD 2025-09-23 17:53:24 +02:00
ItsLemmy
f86dac2172 DockMenu: minor UI tweaks. 2025-09-23 10:22:59 -04:00
ItsLemmy
59fe0a058e Autoformatting 2025-09-23 09:25:44 -04:00
ItsLemmy
640a4339db Cava: Now only runs when a visualizer is in sight. 2025-09-23 08:37:16 -04:00
FUFSoB
505cf48b6c other: small changes 2025-09-23 12:40:19 +05:00
FUFSoB
6d5574cac0 bugfix: urgency low was treated as normal 2025-09-23 11:46:46 +05:00
FUFSoB
e35264708a bugfix: remove race condition, respect duration settings 2025-09-23 11:34:21 +05:00
FUFSoB
ea0350bcca feat: set if notifs can be above fullscreen apps 2025-09-23 11:01:05 +05:00
FUFSoB
b47ac6dd8a feat: set if respecting custom notif timeout 2025-09-23 10:53:44 +05:00
ItsLemmy
120ed36deb Cava: always active 2025-09-22 22:41:24 -04:00
ItsLemmy
26fe3114a6 Settings: updated comments 2025-09-22 22:39:47 -04:00
ItsLemmy
39e58acade MediaCard: Using the new NContextMenu 2025-09-22 22:34:35 -04:00
ItsLemmy
807e7394fe Cava + Visualizer: Should not depend on mpris. Its by design. 2025-09-22 22:07:29 -04:00
ItsLemmy
d745be9c96 Bar section editor: better icons for move across sections 2025-09-22 21:45:22 -04:00
ItsLemmy
8f8f6c23ea Bar Editor: added ability to move widget to other sections with right clicking context menu. 2025-09-22 21:33:38 -04:00
ItsLemmy
3da0e529c6 Shell: cleanup 2025-09-22 21:09:45 -04:00
Ly-sec
d5a862d904 shell: remove reload popup, except for error 2025-09-23 03:08:32 +02:00
Ly-sec
4de2b7f5a8 LockScreen: fix cursor 2025-09-23 03:02:44 +02:00
ItsLemmy
9f31c61a18 Bar section editor: added missing tooltips: 2025-09-22 21:00:51 -04:00
ItsLemmy
d8539c0814 Removed filepicker icons aliases 2025-09-22 20:56:00 -04:00
ItsLemmy
9b8c0b9cf0 ListView replaced by proper NListView 2025-09-22 20:53:59 -04:00
ItsLemmy
c4764c0e5b ScreenRecorder: disable toast when recording starts 2025-09-22 20:23:00 -04:00
ItsLemmy
aec170d7f8 Fix a few hardcoded margin by proper Style.xxx 2025-09-22 20:16:39 -04:00
ItsLemmy
a395156556 ControlCenterSettings fix 2025-09-22 20:14:42 -04:00
ItsLemmy
50ea3e9a8b More renaming 2025-09-22 20:09:12 -04:00
ItsLemmy
50ef79677e Updating bar widgets ids 2025-09-22 19:51:57 -04:00
ItsLemmy
def778dbf1 Settings: Log before splicing or you will log the wrong widget.id 2025-09-22 19:39:52 -04:00
ItsLemmy
b8f4401878 First pass 2025-09-22 19:11:10 -04:00
Ly-sec
9a7fb4a219 Bar/: add Calendar folder 2025-09-23 00:24:22 +02:00
Ly-sec
39b52eb17e Bar/: remove Panel suffix 2025-09-23 00:21:43 +02:00
Ly-sec
609f1e9655 Bar/: refactor layout 2025-09-23 00:20:06 +02:00
Ly-sec
9bb60d0ae3 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-23 00:01:36 +02:00
Ly-sec
202516aee3 Dock: fix pinned app grouping 2025-09-23 00:01:31 +02:00
Ly-sec
489ce76d2a Notification: layout changes 2025-09-22 23:56:18 +02:00
ItsLemmy
6a8c3c721a TablerIcons at root of Commons/ 2025-09-22 17:49:05 -04:00
ItsLemmy
21d331c232 ActiveWindow: more cleanup 2025-09-22 17:37:34 -04:00
Ly-sec
4c9d40865f NText: add elide (ltr & rtl) 2025-09-22 23:20:59 +02:00
Ly-sec
490200b3b8 ActiveWindow: properly hide when no window is available 2025-09-22 22:50:58 +02:00
Ly-sec
6031c97e1a ScreenRecorder: add toast for record stop/start/error 2025-09-22 22:47:16 +02:00
Ly-sec
4d0777ab93 Let people use scrollwheel to switch between workspaces (fixes #290) 2025-09-22 22:27:20 +02:00
Ly-sec
17308083fe Revert "ActiveWindow: hide ActiveWindow if there is no actual window"
This reverts commit 51fb5b9f4a.
2025-09-22 22:25:01 +02:00
Ly-sec
51fb5b9f4a ActiveWindow: hide ActiveWindow if there is no actual window 2025-09-22 22:23:39 +02:00
Ly-sec
773912320f LockScreen: fix expanding password 2025-09-22 22:19:43 +02:00
ItsLemmy
4a4cd20553 ActiveWindow: Fix #338 2025-09-22 16:01:15 -04:00
ItsLemmy
6fbaf46ed9 AppIcons => ThemeIcons 2025-09-22 14:58:34 -04:00
ItsLemmy
03da290c54 Notifications History: restored original panel width, changed title to: "Notifications" 2025-09-22 13:59:19 -04:00
FUFSoB
2d0d6207a1 WIP: notif progress bar 2025-09-22 22:51:25 +05:00
ItsLemmy
f896b41c6b Dock: removed onCountChanged as it is unecessary and was producing warnings. 2025-09-22 13:49:03 -04:00
ItsLemmy
e0d577cbda Prevent even more dragging. 2025-09-22 13:47:51 -04:00
ItsLemmy
be1c975f4d Prevent even more dragging when popup are open. 2025-09-22 13:46:25 -04:00
ItsLemmy
c20773d60b Prevent NPanel dragging when popup are open. 2025-09-22 13:40:38 -04:00
FUFSoB
45fb881ec2 rename notifications layer 2025-09-22 22:33:45 +05:00
ItsLemmy
64001152ef BarWidgetSettings: fix 2025-09-22 13:32:00 -04:00
ItsLemmy
5aa935b348 FileDialog: also properly hide/restore popups when opening 2025-09-22 12:19:41 -04:00
ItsLemmy
826dba7f53 Merge branch 'main' into file-dialog-builtin 2025-09-22 11:54:44 -04:00
Lemmy
358cfe26e2 Merge pull request #335 from lonerOrz/sidepanel
feat(bar): Allow custom icon for SidePanelToggle
2025-09-22 11:49:00 -04:00
ItsLemmy
8ece805273 File Picker: Using platform's native picker - removed custom picker. 2025-09-22 11:39:04 -04:00
Lysec
8e32816976 Merge pull request #336 from lonerOrz/systemMonitor
fix(bar): Ensure SystemMonitor temperature is fully visible
2025-09-22 16:31:08 +02:00
Ly-sec
64757979e8 Dock: use Style.fontSize, remove most logging 2025-09-22 16:25:44 +02:00
Ly-sec
26a4861a8b Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-22 16:10:46 +02:00
Ly-sec
21c6c5a610 Added pinning to dock & right click menu to dock
Dock: display pinned apps on the left even when not running (lower
opacity)
DockMenu: Let users close, activate and pin/unpin apps
Settings: add pinned list for docks
2025-09-22 16:09:25 +02:00
Lysec
5594257147 Merge pull request #340 from msdevpt/ghostty-template
fix: ghostty template
2025-09-22 15:59:12 +02:00
Ly-sec
879d9ec879 Notification: add location option
Autoformat
2025-09-22 14:09:23 +02:00
Ly-sec
d13793fcbd Notification: add scaling 2025-09-22 13:58:59 +02:00
M.Silva
51138cbf55 fix: ghostty template 2025-09-22 08:38:22 +01:00
loner
355473a946 fix(bar): Ensure SystemMonitor temperature is fully visible
In the vertical bar layout, the temperature text in the SystemMonitor
widget (e.g., "55°C") could be truncated due to the widget's fixed
width.
  This commit resolves the issue by applying a dynamic scale
transformation to the text component.
2025-09-22 11:26:21 +08:00
loner
f25bba7c11 feat(bar): Allow custom icon for SidePanelToggle
Adds a feature allowing users to select a custom image file to be used
as the icon for the SidePanelToggle widget.
  - Introduces a "Browse File" button in the widget's settings dialog,
utilizing the `NFilePicker` component.
  - An `NImageCircled` preview of the selected custom icon is now shown
in the settings.
  - The display logic for the widget is updated to prioritize the custom
icon path over the library icon and distro logo.
2025-09-22 11:05:26 +08:00
LemmyCook
f348eb993c v2.13.0-dev 2025-09-21 21:31:38 -04:00
LemmyCook
3f1675b84a v2.13.0 2025-09-21 21:25:39 -04:00
LemmyCook
3aac552c44 Clock: Minor vertical adjustment tweaks when capsule are off. 2025-09-21 21:25:15 -04:00
LemmyCook
1717fc0992 NTextInput: new approach to avoid all input leakage and dragging NPanel issues. 2025-09-21 21:17:12 -04:00
LemmyCook
a7e3deecd3 NInputButton properly uses NTextInput 2025-09-21 20:49:46 -04:00
LemmyCook
46c3ea5d22 Revert "fix: disable panel dragging during text input and dialog interaction"
This reverts commit 56db321846.
2025-09-21 20:24:51 -04:00
LemmyCook
78f0c1da6a Merge branch 'file-picker' 2025-09-21 20:22:09 -04:00
LemmyCook
4753766b4f Clock / DateTimeTokens: better look and alignment 2025-09-21 20:19:50 -04:00
LemmyCook
0c1ed01319 DisplayTab: slight UI rework 2025-09-21 17:06:15 -04:00
LemmyCook
91dbc6a7f1 Brightness: Fix wrong logger call. 2025-09-21 16:38:33 -04:00
LemmyCook
d4a46e5361 Default settings generation completed! 2025-09-21 16:31:42 -04:00
LemmyCook
177a9743d6 Merge branch 'main' into default-settings 2025-09-21 15:42:16 -04:00
LemmyCook
2b8338938a Default wallpaper with the new logo (wip) 2025-09-21 15:41:58 -04:00
LemmyCook
84702465d7 wip: default settings 2025-09-21 15:40:41 -04:00
Ly-sec
3684c87f8c WallpaperTab: fix width of NInputAction for individual wallpapers
NFilePicker: reverse grid/listview button
2025-09-21 21:32:57 +02:00
Lemmy
85815ba86d Update README.md 2025-09-21 15:20:42 -04:00
LemmyCook
6eb453136d Wallpaper: cached images goes to their own subfolder. 2025-09-21 14:54:33 -04:00
Ly-sec
385f4943ae NFilePicker: cleanup 2025-09-21 20:52:47 +02:00
Ly-sec
4dcc9609d6 Add icons to TablerIcons, edit sizing of icons in FilePicker etc 2025-09-21 20:40:28 +02:00
Ly-sec
3bbf26a18e NFilePicker: renamed NFileManager to NFilePicker, update grid hover 2025-09-21 19:44:04 +02:00
Ly-sec
dfe3aed46e NFilePicker: fix some layout/color issues 2025-09-21 19:39:52 +02:00
LemmyCook
796e080948 Merge branch 'notification-history-improved' 2025-09-21 12:28:55 -04:00
LemmyCook
052bdefaab Notification: finalization before merge 2025-09-21 12:28:42 -04:00
LemmyCook
794853b7bd Notifications: removed hard limit to 100 characters. 2025-09-21 10:56:27 -04:00
LemmyCook
fbd431164b Notifications: minor renaming for clarity 2025-09-21 10:45:50 -04:00
Lysec
2c1c1a513a Merge pull request #332 from acdcbyl/main
MatugenTemplate: Try to fix ghostty template
2025-09-21 16:28:14 +02:00
LemmyCook
0279b5654a Notifications: minor renaming + house keeping. Bring back the close history when clearing all notifications 2025-09-21 10:24:47 -04:00
Aiser
c93e907595 MatugenTemplate: Try to fix ghostty template 2025-09-21 19:35:12 +08:00
Ly-sec
5965004721 NFileManager: fix file path, add image thumbnails 2025-09-21 13:18:52 +02:00
Ly-sec
86d891cfa8 Add NInputButton widget and FileManagerService integration
NInputButton.qml: new input+button widget
FileManagerService.qml: singleton service for file/folder dialogs
NFileManager.qml: create first iteration of filemanager
WallpaperTab.qml: integrate NInputButton
ScreenRecorderTab.qml: integrate NInputButton
GeneralTab.qml: integrate NInputButton
2025-09-21 13:06:57 +02:00
Lysec
1161fca422 Merge pull request #331 from acdcbyl/main
MatugenTemplate: Rewrite ghostty template
2025-09-21 12:51:12 +02:00
Aiser
26575ade7e MatugenTemplate:Rewrite ghostty template 2025-09-21 18:48:28 +08:00
Ly-sec
fac9b8f54c NotificationService: fix width/height warning 2025-09-21 11:12:18 +02:00
Ly-sec
71ce858b32 Notification: fix saving/deleting notifications 2025-09-21 10:59:44 +02:00
Ly-sec
ff34696d28 NotificationService: cleanup, fix duplicate images, resize to 64x64 2025-09-21 10:48:43 +02:00
LemmyCook
2e0214ddb8 Workspaces: Fix scaling #328 2025-09-20 23:51:49 -04:00
LemmyCook
f316effecd Clock: fixed centering and padding + smarted sizing. Fix #325 2025-09-20 23:46:12 -04:00
Lemmy
6aa14120de Merge pull request #327 from msdevpt/adjust-workspace-size
chore: adjust to maintain visual proportion
2025-09-20 23:27:41 -04:00
LemmyCook
1ad6969d9b Notification service: Full refactoring to support image caching for history. 2025-09-20 23:26:05 -04:00
LemmyCook
aed7440c5b Center Fallback icon 2025-09-20 17:23:49 -04:00
LemmyCook
10534b46f9 test-notif: changed debian-logo to steam, as I don't have a debian logo 2025-09-20 16:40:36 -04:00
M.Silva
802d4efdd3 chore: adjust to maintain visual proportion 2025-09-20 19:47:19 +01:00
Lemmy
20949a0298 Merge pull request #322 from ixxie/flake/systemd-service
nix flake: systemd service + home manager settings
2025-09-20 12:19:51 -04:00
Matan Bendix Shenhav
8f596f14b0 feat(flake): enable home-manager colors options 2025-09-20 17:32:28 +02:00
LemmyCook
c85043782f Clock: better settings UI + support for \\n in horizontal bar. 2025-09-20 10:44:50 -04:00
LemmyCook
fe4603f87a Clock Settings: slight layout and wording improvement 2025-09-20 09:47:20 -04:00
Matan Bendix Shenhav
f8313a04fd feat(flake): enable home-manager settings config 2025-09-20 15:12:01 +02:00
Matan Bendix Shenhav
ba5e85ca67 chore(flake): format with nixfmt-rfc-style 2025-09-20 15:12:01 +02:00
Matan Bendix Shenhav
5233547d76 feat(flake): systemd service 2025-09-20 15:12:01 +02:00
Ly-sec
56db321846 fix: disable panel dragging during text input and dialog interaction
NPanel: disable DragHandler when popups open, block drag over text inputs
BarWidgetSettingsDialog: notify panel of open/close state
BarSectionEditor: pass panel reference to dialog
2025-09-20 12:23:43 +02:00
ItsLemmy
8d0ce8dc49 Clock: simpler format management (horiz vs vertical) so one can switch the bar position without editing its clock. 2025-09-20 03:01:06 -04:00
ItsLemmy
a340f8f31f Merge branch 'main' of github.com:Ly-sec/Noctalia 2025-09-20 01:53:00 -04:00
ItsLemmy
3853c099d0 NTextInput: dont propagate events to avoid dragging panel when selecting text with the mouse. 2025-09-20 01:52:57 -04:00
Lemmy
35a928e3d8 Update README.md 2025-09-20 01:31:11 -04:00
ItsLemmy
8d942d0782 CLock settings: less tall UI for 1080p 2025-09-20 01:23:59 -04:00
Lemmy
c70a66b589 Update README.md 2025-09-20 00:54:12 -04:00
Lemmy
a8398916c9 New logo 2025-09-20 00:42:58 -04:00
LemmyCook
ed464b196f Font: added new Noctalia icon + Niri icon. 2025-09-20 00:31:45 -04:00
LemmyCook
f3f8b82fdd Clock: new approach to bar clock display based on tokens. 2025-09-19 23:18:59 -04:00
LemmyCook
2cd73c265d Settings: on load, automatically remove deprecated userSettings. 2025-09-19 22:42:09 -04:00
LemmyCook
737e990117 CustomButtonSettings: Using header for subsection 2025-09-19 22:41:32 -04:00
LemmyCook
8a78ee090a Cleanup: more strings 2025-09-19 17:11:34 -04:00
LemmyCook
761aa62995 Cleanup: more strings cleanup, removing capitalization and minor adjusments. 2025-09-19 17:03:31 -04:00
LemmyCook
dabf281ae8 CustomButton: simplified icon selection (in accordance with sidepanel toggle) 2025-09-19 16:42:19 -04:00
LemmyCook
5cb9935f2f SidePanelToggle: now allows to pick any icon from the font. 2025-09-19 16:37:38 -04:00
LemmyCook
9236b2f00e autoformatting 2025-09-19 15:53:06 -04:00
LemmyCook
29b67f1337 Calendar: week numbers take 2 - Fix #308 2025-09-19 15:52:58 -04:00
LemmyCook
dd2c02af3f Merge branch 'compositor-service' 2025-09-19 14:42:31 -04:00
LemmyCook
b960441321 Revert flake.nix until it's properly investigated. 2025-09-19 14:02:13 -04:00
LemmyCook
babb4ca202 Revert to the old flake.nix until things work as expected. 2025-09-19 14:01:19 -04:00
LemmyCook
4dc1076abc ActiveWindow: adaptation to the new compositor service 2025-09-19 13:45:12 -04:00
LemmyCook
590708da57 Bar: New widget "Wallpaper Selector" to open the selector directly. 2025-09-19 11:24:46 -04:00
LemmyCook
78df416bc7 KeepAwake: fix border onHover 2025-09-19 11:24:04 -04:00
LemmyCook
fcc054c3ae WallpaperSelector: set current tab index to the current screen the UI opened on. 2025-09-19 11:18:55 -04:00
LemmyCook
06b858a77e Autoformatting 2025-09-19 11:05:35 -04:00
LemmyCook
658b583e84 Floating bar: On the perpendicular axis of the bar: only apply the floating margin between the screen and the bar. This will avoid people having to deal with struts and gaps.
- ex: if bar is on top, the vertical margin will only be applied between
the top screen edge and the bar, not extra margin below the bar
2025-09-19 11:05:15 -04:00
LemmyCook
ed557af1c2 Tooltip improvements (only use period for long sentences) 2025-09-19 10:38:10 -04:00
LemmyCook
61203dc5fd Wallpaper Selector: added screen tab for a better UX. 2025-09-19 09:48:43 -04:00
Ly-sec
b7d417ea91 flake: possible fix for installation issue 2025-09-19 12:55:57 +02:00
LemmyCook
978405bd85 2.12.1-dev 2025-09-18 23:42:34 -04:00
LemmyCook
878115db59 ScreenRecorderIndicator: Now always shown and can now start recording. 2025-09-18 23:34:20 -04:00
LemmyCook
50469e5c82 BarService: lookupWidget can now match by index. 2025-09-18 23:33:46 -04:00
LemmyCook
860e721709 Hotfix: do not filter our the screenrecorder indicator, as it messes with widgets index and settings. 2025-09-18 23:12:35 -04:00
LemmyCook
1dbc0cada6 WIP compositor cleanup 2025-09-18 22:58:57 -04:00
LemmyCook
88ece93db2 2.12.0-dev 2025-09-18 22:09:38 -04:00
LemmyCook
2d290bf5f7 Release v2.12.0 2025-09-18 22:06:05 -04:00
LemmyCook
891c8660e3 Properly hide ScreenRecorderIndicator when inactive (no spacing) 2025-09-18 22:05:55 -04:00
LemmyCook
a734235cd0 Autoformating 2025-09-18 22:05:33 -04:00
Lemmy
8fdc6a0f72 Merge pull request #314 from kevindiaz314/main
fix(clock): respect monthBeforeDay setting in vertical clock date dis…
2025-09-18 21:38:31 -04:00
LemmyCook
603f499355 Settings: removed systemic capitalization improved labels and descriptions. 2025-09-18 21:34:30 -04:00
Kevin Diaz
2b8b97ab3b fix(clock): respect monthBeforeDay setting in vertical clock date display 2025-09-18 20:30:22 -04:00
LemmyCook
458ef3c0d5 TrayMenu: not using 'Screen' as we have a proper 'screen' 2025-09-18 18:28:01 -04:00
LemmyCook
c4008e3899 CustomButtonSettings: Don't use Screen with a capital 'S' unless really necessary. 2025-09-18 18:25:15 -04:00
LemmyCook
6c3299ad10 Merge branch 'wallpaper-selector' 2025-09-18 18:22:32 -04:00
LemmyCook
6fe498ce19 Wallpaper Selector: auto-focus search field 2025-09-18 17:47:26 -04:00
LemmyCook
4e67f26576 Wallpaper Selector: fix for multi screens / multi directories setup 2025-09-18 17:35:25 -04:00
LemmyCook
b2d46ab759 Settings: cleanup since we moved the wallpaper selector out. 2025-09-18 17:34:55 -04:00
Lemmy
0d3cc917fa Merge pull request #302 from randibudi/main
NixOS: Add Night Light Dependency and Enable Required Services
2025-09-18 15:51:48 -04:00
Lemmy
ac591da6c5 Update README.md 2025-09-18 15:51:21 -04:00
Lemmy
c7709b5f21 Update README.md 2025-09-18 15:50:19 -04:00
Lemmy
e6370904cd Update README.md 2025-09-18 15:47:37 -04:00
Randi Budi
e412cee52f Merge branch 'main' into main 2025-09-19 01:32:07 +07:00
Ly-sec
c3019230ae WallpaperSelector: even more layout changes 2025-09-18 20:04:03 +02:00
Ly-sec
c7ab350cbd MatugenService: add check for Settings.isLoaded 2025-09-18 19:53:06 +02:00
Ly-sec
b65d82d895 WallpaperSelector: more layout changes 2025-09-18 19:51:45 +02:00
Ly-sec
89eb5ecde6 IPCManager: add wallpaper selector toggle 2025-09-18 19:31:04 +02:00
Ly-sec
b374f167ef WallpaperSelectorPanel: rename to WallpaperSelector 2025-09-18 19:26:35 +02:00
Ly-sec
28026a4c37 NPanel: add bar detection while dragging
WallpaperSelectorPanel: adjust layout
2025-09-18 19:24:00 +02:00
Ly-sec
b8bce3d421 NPanel: add border while dragging 2025-09-18 18:34:48 +02:00
Ly-sec
6fba3457f7 NPanel: add drag support 2025-09-18 18:27:35 +02:00
Ly-sec
07a6a16011 WallpaperSelector: cleanup 2025-09-18 18:11:37 +02:00
Ly-sec
6b61599633 WallpaperSelector: change sizing 2025-09-18 18:06:18 +02:00
Ly-sec
1bd093db7f WallpaperSelector overhaul: initial commit 2025-09-18 17:55:30 +02:00
Ly-sec
3d9295856c Launcher: add sort by most used option 2025-09-18 16:53:38 +02:00
LemmyCook
a1aabd02f5 Toast: reworked the display and logic to make it more robust.
+ some bluetooth logic debouncing to avoid extra toast when adapter
comes back to life after suspend.
2025-09-18 10:10:40 -04:00
Ly-sec
ae2d3eddd6 README: revert Credits & Acknowledgment sections 2025-09-18 11:12:48 +02:00
Ly-sec
b75c358f54 README: full overhaul, linking to docs 2025-09-18 11:10:29 +02:00
Lysec
0972a55aad Merge pull request #312 from nalakawula/lockScreen/adjust-password-prompt
Make password prompt look like a terminal/tty
2025-09-18 11:02:15 +02:00
sumarsono
112f71b633 Make password prompt look like a terminal/tty 2025-09-18 15:52:45 +07:00
LemmyCook
e67d7166de Merge branch 'bar-service' 2025-09-17 22:50:56 -04:00
LemmyCook
6e88118ca9 Calendar: add conditional week number column. New option is in the Location tab of the settings. 2025-09-17 22:32:44 -04:00
LemmyCook
75b7f0fcb0 Bluetooth device: fixed missing busy icon on the call to action. 2025-09-17 21:58:44 -04:00
LemmyCook
47f72d9498 Location/Clock: Moved use12hourformat and reverseDaymonth from the clock widget settings to the main settings, location tab
- Fix #303
2025-09-17 21:10:51 -04:00
LemmyCook
85d7dc2506 Settings/Notification: typo fix 2025-09-17 15:40:10 -04:00
LemmyCook
1305efec24 Settings/Notification: fixed typo 2025-09-17 15:38:25 -04:00
LemmyCook
8af8bf2e2e BarService: to keep tracks of bar widgets and improve IPC behavior. 2025-09-17 10:19:55 -04:00
Lemmy
abd6a66297 Merge pull request #295 from knuesel/colorscheme-kanagawa
Kanagawa colorscheme
2025-09-17 09:34:31 -04:00
LemmyCook
2e9a812513 PowerProfile: Standardization + Factorisation. Fix #307 2025-09-17 09:30:23 -04:00
Jeremie Knuesel
8d845e7cd0 Kanagawa colorscheme 2025-09-17 14:56:13 +02:00
Ly-sec
a1dcef8dec Revert "Brightness: holding down keybind with brightness IPC now keeps changing brightness until release"
This reverts commit 38e0bb8e64.
2025-09-17 12:51:02 +02:00
Ly-sec
38e0bb8e64 Brightness: holding down keybind with brightness IPC now keeps changing brightness until release 2025-09-17 12:50:19 +02:00
Ly-sec
8811cb3d13 Notification: display links as plain text 2025-09-17 12:40:52 +02:00
ItsLemmy
a872682eb8 Brightness: fix #300 2025-09-17 00:28:57 -04:00
LemmyCook
46b8317330 v2.11.0-dev 2025-09-16 23:30:04 -04:00
LemmyCook
8204460112 v2.11.0 2025-09-16 23:29:02 -04:00
LemmyCook
292337dc00 Settings: Put monitor configs below other settings on Bar and Notif. tabs 2025-09-16 23:26:35 -04:00
LemmyCook
0b790c219d Dimming: replaced dimmer by panel dimming, now that we have no margins it works fine. 2025-09-16 23:23:16 -04:00
LemmyCook
7acca17b83 2.10.0-dev 2025-09-16 23:10:12 -04:00
LemmyCook
166da9191e v2.10.0 2025-09-16 22:48:57 -04:00
LemmyCook
de6b7c6470 Dimmer: bulletproffed test on screen 2025-09-16 22:47:43 -04:00
LemmyCook
a92b4b311a Renamed and moved NPill to BarPill.
Pill should not be used outside of the Bar as they rely on bar settings.
2025-09-16 22:26:56 -04:00
LemmyCook
3a6bf8d299 Bar widgets: fixed bg colors when used with showCapsule=false 2025-09-16 22:20:42 -04:00
LemmyCook
cdca7c1d83 NPanel dimensions & Dimmer: Panels have no margin they are full screen and prevent clicking on the bar until dismissed.
Margins are now included in the rectangle X,Y coordinates calculation

Might sound weird at first but it fixes a lot of inconsistencies/issues
we have had for a long time when a panel was open:
- can't close panel when clicking in a dead zone of the bar.
- hovering an icon on the bar used to make it look like you could
interact with it, but the click would just close the panel and not
actuall y do anything with bar .

I recommend turning back on dimming, as it is now way cooler. Changed
the default to true.
2025-09-16 21:53:11 -04:00
LemmyCook
6f1ae43d62 Dimmer: new implementation of the screen diming in a separate component. 2025-09-16 21:35:27 -04:00
LemmyCook
eb26aa10f7 NPanel: Reworked all margins and X,Y computation to make things simpler. Fix #298
- Temporarily removed Dimming as it was a pain to manage on each panel,
this will be reimplemented in a better way soon.
2025-09-16 20:28:07 -04:00
Randi Budi
cdfb110007 fix(nixos): power profile and battery monitoring with module 2025-09-17 04:20:37 +07:00
Randi Budi
b7d8f92414 fix(nixos): add wlsunset dependency for night light 2025-09-17 00:59:13 +07:00
LemmyCook
b625df6484 Icons: slightly smaller noctalia logo to better match the others. 2025-09-16 12:22:51 -04:00
LemmyCook
2c3eb6efda Launcher: AppPlugin, close panel immediately to avoid focusing issues. 2025-09-16 12:09:12 -04:00
Ly-sec
8e034cd912 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-16 15:25:52 +02:00
Ly-sec
03bdfdb340 Notification: replace unread badge with small circle 2025-09-16 15:25:46 +02:00
LemmyCook
95d2dbe3fc Optional capsule bg 2025-09-16 09:23:37 -04:00
LemmyCook
071100459f Better compact mode 2025-09-16 09:06:40 -04:00
LemmyCook
0da59954cd Workspace: less chunky when no numbers 2025-09-16 08:55:36 -04:00
LemmyCook
2e63f93d41 Workspace: less chunky 2025-09-16 08:48:19 -04:00
LemmyCook
c6ee99375d Screen recorder: typo fix 2025-09-16 08:35:43 -04:00
LemmyCook
ed6562475d Monitors configuration: improved description. Fix #292 2025-09-16 08:16:29 -04:00
LemmyCook
a2caebb8e5 Bar Density: improved workspace widget + slight density adjustments 2025-09-16 08:10:10 -04:00
Ly-sec
03698e7bb9 ActiveWindow: use same font height as MediaMini 2025-09-16 13:08:30 +02:00
Ly-sec
d8db086127 NotificationHistoryPanel: remove hover of notifications 2025-09-16 09:05:17 +02:00
LemmyCook
339505abe3 Workspace: better font sizing for active workspace 2025-09-16 00:42:44 -04:00
LemmyCook
b52451fde5 Bar density: leftover files from previous commit 2025-09-16 00:40:02 -04:00
LemmyCook
ac1902c76a Bar: compact mode works pretty well but need some more testing. 2025-09-16 00:39:30 -04:00
LemmyCook
93c674f356 SysMonitor: converted dual layout for vertical/horiz bar to a single grid layout 2025-09-15 23:06:06 -04:00
LemmyCook
5f3add5d99 autoformatting 2025-09-15 22:56:22 -04:00
LemmyCook
937675ebb3 TaskBar: implemented vertical mode 2025-09-15 22:56:05 -04:00
LemmyCook
47ef62beb3 Widgets Sizing: reworked our sizing approach to prepare for different bar densities. 2025-09-15 22:33:09 -04:00
LemmyCook
593a0bfc2c NColorPicker: sizing improvements 2025-09-15 21:36:09 -04:00
LemmyCook
abe51f4928 NSpinBox: use fixed font for number 2025-09-15 21:21:42 -04:00
LemmyCook
33a75d042d IconImage: They have to be asynchronous or the may crash QS on startup. TaskBar was crashing very often during development. 2025-09-15 21:07:42 -04:00
Ly-sec
2a62a13b16 README: remove useless layer-rule 2025-09-15 19:40:00 +02:00
LemmyCook
4edeedd5ad v2.9.2-dev 2025-09-15 13:19:12 -04:00
Ly-sec
8a17c047c9 README: add missing icon info 2025-09-15 19:01:18 +02:00
Ly-sec
899595ec5c Release v2.9.2 2025-09-15 14:52:09 +02:00
Ly-sec
dbf1020636 CustomButton: add script execution/polling support with text display 2025-09-15 14:37:29 +02:00
LemmyCook
7def695c0e AboutTab: Fix hover on contributos 2025-09-15 08:36:18 -04:00
LemmyCook
b51a87a981 NSlider: slightly more discrete bg track 2025-09-15 08:36:08 -04:00
LemmyCook
3da2682111 Settings: fix about tab alignment of download button 2025-09-15 07:58:33 -04:00
Ly-sec
758f2f2e55 SystemMonitor: fix network stats, move text above storage icon (vertical bar) 2025-09-15 13:43:58 +02:00
Lysec
26a27c3393 Merge pull request #282 from Mtendekuyokwa19/everforest
Everforest
2025-09-15 09:39:42 +02:00
Mtende Kuyokwa
9d9bfb54e1 everforest light 2025-09-15 09:30:26 +02:00
Mtende Kuyokwa
ab5b1e4d82 everforest dark complete 2025-09-15 08:40:11 +02:00
Ly-sec
8cb9f04a22 ScreenCorners: add solid black option 2025-09-15 08:28:57 +02:00
Ly-sec
22bc5a3bff Move ScreenCorners to the actualy screen edges when bar is floating,
edit vesktop template
ScreenCorners: move to screen corners on floating bar
BarTab: mention the ScreenCorner changes
vesktop: make read channels have less visible text
2025-09-15 08:08:32 +02:00
Ly-sec
f5982f41a2 Remove noctalia.svg 2025-09-15 07:59:39 +02:00
Ly-sec
9a80d51b10 Vesktop: replace old theme with midnight from refact0r for better looks 2025-09-15 07:59:06 +02:00
Ly-sec
fa838ecdb1 Cleaned up ColorSchemeTab, added program checks, added firefox template
Matugen: added firefox (pywalfox) template
SidePanelToggle: use ProgramCheckerService for gpu-screen-recorder
ColorSchemeTab: use NCollapsible for matugen templates, use
ProgramCheckerService to detect available programs (for matugen
templates)
NCollapsible: create collapsible category
2025-09-15 07:44:31 +02:00
ItsLemmy
c0d6780c3d Volume/Brightness/Microphone: fixed tooltips to new mapping 2025-09-14 23:45:17 -04:00
ItsLemmy
8935f9a0f9 NPill: fix broken mouse-wheel control 2025-09-14 23:42:21 -04:00
LemmyCook
b8b97c46a0 DistroLogo: respect original colors, and avoid changing bg color when hovering to compensate. 2025-09-14 23:07:00 -04:00
Lemmy
f2bbf70f93 Merge pull request #279 from MrDowntempo/better-cats
Updated Catppuccin to use more of the official colors.
2025-09-14 22:54:44 -04:00
Corey Woodworth
ecf468f78f Updated Catppuccin to use more of the official colors. Also changed Green to Teal in Mocha and a few minor tweaks for readability. 2025-09-14 22:50:29 -04:00
Lemmy
0b35fc1d2d Merge pull request #278 from kevindiaz314/main
feat: update tokyo night light color scheme with refined color values
2025-09-14 22:47:03 -04:00
LemmyCook
94c5d73a61 NPill: fix icon bg hover color 2025-09-14 22:46:40 -04:00
LemmyCook
f399a6d9f5 TrayMenu: improve tray opening direction in vertical bar more 2025-09-14 22:44:27 -04:00
Kevin Diaz
44fd859aec feat: update tokyo night light color scheme with refined color values 2025-09-14 22:33:13 -04:00
LemmyCook
53d0c3943d TrayMenu: Fix submenu burger icon color when hovered. 2025-09-14 22:24:39 -04:00
Lemmy
d80f923802 Merge pull request #229 from matejc/main
Fix for fingerprint flow on lock screen
2025-09-14 22:18:14 -04:00
Lemmy
1b861d7b7b Merge pull request #277 from kevindiaz314/main
feat: update tokyo night color scheme with refined color values
2025-09-14 22:07:10 -04:00
LemmyCook
65933208ec NPillVertical: match NHorizontal on margins and color 2025-09-14 22:03:44 -04:00
LemmyCook
5df218a789 Bar Widgets: Removed 3 unecessary anchors 2025-09-14 21:56:55 -04:00
LemmyCook
2f7a834b55 Bar: fix centering (againg) 2025-09-14 21:55:21 -04:00
LemmyCook
eca301553e Bar: better vertical centering on horizontal bar. 2025-09-14 21:51:43 -04:00
LemmyCook
91efa38101 HorizontalPill: different color for forceOpen + better margins 2025-09-14 21:49:29 -04:00
Kevin Diaz
acfe94f736 feat: update tokyo night color scheme with refined color values 2025-09-14 21:24:23 -04:00
LemmyCook
97bfcbb9e8 Clock: height calculation similar to NPill to avoid discrepancies 2025-09-14 21:15:27 -04:00
LemmyCook
9e47d91be2 v2.9.1-dev / git 2025-09-14 21:03:21 -04:00
LemmyCook
8872002225 Release 2.9.1 2025-09-14 21:01:14 -04:00
LemmyCook
519a85b251 AudioTab: fixed spinbox 2025-09-14 20:53:42 -04:00
LemmyCook
5aa7ff7e91 NValueSlider: new component + pimped NSlider with a small gradient and removed rounded corners due to issues. 2025-09-14 20:52:32 -04:00
LemmyCook
5ce5659b38 NPills: keep hover even when force open, as there are actions available on clicks. 2025-09-14 18:21:24 -04:00
LemmyCook
00459606ce Brightness: hotfix 2025-09-14 18:19:02 -04:00
LemmyCook
da1081700a NPill: replaced "Normal" by "On Hover" 2025-09-14 18:15:07 -04:00
LemmyCook
7e965262f5 NPill: Restored the old horizontal NPill 2025-09-14 18:07:43 -04:00
LemmyCook
19312d94c3 Removing test mode on battery 2025-09-14 17:21:38 -04:00
Ly-sec
118323e6b5 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-14 23:14:59 +02:00
Ly-sec
aed81e82b0 Remove "%" from NVerticalPill add force close option to it too
NVerticalPill: add force close option
Any vertical bar widget: remove "%" display to have nice horizontal text
BarTab: add "always hide percentage" option so the pills will never
expand (opposite of always show percentage)
2025-09-14 23:13:11 +02:00
Lemmy
76c167c2c2 Merge pull request #273 from ThatOneCalculator/fix/power-menu-suspend-icon
fix(consistency): use unfilled pause icon for suspend in power menu
2025-09-14 17:08:40 -04:00
Ly-sec
852e2fa4d1 Fix N*Pill force show layout 2025-09-14 22:39:16 +02:00
Ly-sec
17dceffff6 Overview: add autoPaddingEnabled:false to MultiEffect blur 2025-09-14 22:33:44 +02:00
Kainoa Kanter
b589f37e0b use unfilled pause icon for suspend in power menu 2025-09-14 13:27:07 -07:00
Lysec
682c6af231 Merge pull request #267 from ThatOneCalculator/patch-1
docs: mention `gpu-screen-recorder` Flatpak
2025-09-14 22:25:13 +02:00
Ly-sec
d71226c6bd Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-14 22:09:40 +02:00
Ly-sec
1f3725faf8 set version to dev 2025-09-14 22:09:38 +02:00
LemmyCook
efcec1b2f9 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-14 15:58:58 -04:00
LemmyCook
651322aef0 Bar: Removed unecessary qt5compat import 2025-09-14 15:58:56 -04:00
Ly-sec
a0a3a58668 Release v2.9.0
- **Floating Mode**: Added floating option for more flexible bar positioning
- **Vertical Orientation**: New vertical bar layout support

- **Exclusive Mode**: Added exclusive setting to prevent windows from rendering behind the dock
- **Floating Distance Control**: Added control for adjusting floating distance
- **Layout Refinements**: Various layout fixes for better visual consistency

- **Enhanced Navigation**: More panels now support closing with the Escape key
- **Settings Overhaul**: Complete revamp of the settings window tab content

- **Layout Editor**: Added ability to edit keyboard layouts directly

- **GPU Temperature**: Removed GPU temperature monitoring (resolved NVIDIA compatibility issues)

- **Compact Mode**: New compact version for space-constrained layouts

- **Hyprland Stability**: Added numerous null checks for improved Hyprland compatibility
- **Niri Support**: Fixed active window detection for the Niri compositor
- **Workspace Visibility**: Added toggle to hide unoccupied workspaces

- **Monochrome Theme**: Added new monochrome color scheme option

- **Bluetooth Stability**: More stable connections and adapter state management
- **Toast Notifications**: Fixed odd toast notification behavior
- **Font Service**: Improved font service reliability and added fuzzy search for the font selection in General Tab
2025-09-14 21:51:58 +02:00
Ly-sec
b3abe44d65 Bar: remove Qt5Compat import 2025-09-14 21:37:56 +02:00
LemmyCook
5b603472bd Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-14 15:25:59 -04:00
LemmyCook
57b0fe8a21 Wi-Fi: connect and disconnect toast messages 2025-09-14 15:25:57 -04:00
Ly-sec
f5561da3cc Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-14 21:25:14 +02:00
Ly-sec
11f6475b9f Some layout fixes to toggle and slider
NSlider(withLabel): fix some small layout issues
NToggle: fix vertical centering of the thumb
2025-09-14 21:24:11 +02:00
LemmyCook
fb2c5e0470 SysMon: removed unecessary Item {} 2025-09-14 15:14:45 -04:00
LemmyCook
b1764fddc8 SysMon: larger margin 2025-09-14 15:12:08 -04:00
Ly-sec
bb7f957e44 Clock: change to mono font 2025-09-14 21:10:16 +02:00
LemmyCook
0682315c9d Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-14 15:07:03 -04:00
LemmyCook
dd100597ed SysMon: better lookin 2025-09-14 15:05:34 -04:00
Ly-sec
6bc6380ee1 FontService: even more mono font fixes 2025-09-14 21:04:46 +02:00
Ly-sec
966089e471 FontService: more mono font fixes 2025-09-14 20:47:36 +02:00
Ly-sec
3956461254 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-14 20:38:32 +02:00
Ly-sec
02d114a05e FontService: more reliable mono lookup 2025-09-14 20:36:52 +02:00
LemmyCook
3764edafa8 Widgets: improved centering 2025-09-14 14:35:15 -04:00
Ly-sec
1cd0376381 Style: reduce vertical bar to 39 2025-09-14 20:30:26 +02:00
LemmyCook
2b154e2cdb Settings: added missing end divider to tabs 2025-09-14 13:58:00 -04:00
LemmyCook
933dfc402b Wifi+BT: added right click 2025-09-14 13:48:12 -04:00
LemmyCook
34d037d7dc Toast: improved clickability around toast 2025-09-14 13:37:15 -04:00
LemmyCook
76b6626073 Trying to match all buttons left/right click.
- left click: mute/unmute, cycle between functionality
- right click: open settings
- middle click: open external settings
2025-09-14 13:35:24 -04:00
LemmyCook
d348cfc2b0 Toast: refactored service vs UI. 2025-09-14 13:29:20 -04:00
LemmyCook
f9d7de2e3c Volume: Fixed missing externalHideTimer 2025-09-14 12:32:05 -04:00
LemmyCook
2ea00fffa5 NSlider: simplification, no Halo + some rounding 2025-09-14 12:22:26 -04:00
LemmyCook
b163dab241 NCheckbox+NToggle: better look 2025-09-14 12:03:15 -04:00
LemmyCook
af0f4818d8 Autoformatting 2025-09-14 11:51:04 -04:00
LemmyCook
8b6c7632af Tray: fixed with vertical bar 2025-09-14 11:50:18 -04:00
LemmyCook
d6d51d24c9 NPill: fix NPill icon color to match or icons (mOnSurface, even tho the bg is mSurfaceVariant) 2025-09-14 11:33:48 -04:00
LemmyCook
0c6aea7154 Vertical Bar! 2025-09-14 11:26:36 -04:00
LemmyCook
a61b2edd07 Settings: fully cleanup and aligned 2025-09-14 11:23:20 -04:00
LemmyCook
c108e7707a Settings: cleanup, almost there! 2025-09-14 11:14:37 -04:00
LemmyCook
f3123ba5b1 Settings: more cleanup - wip 2025-09-14 10:57:24 -04:00
LemmyCook
c09a93af48 NHeader: use label instead of title (matches NLabel) 2025-09-14 10:24:09 -04:00
LemmyCook
2a262999ce Merge branch 'vertical-bar' of github.com:noctalia-dev/noctalia-shell into vertical-bar 2025-09-14 10:17:30 -04:00
LemmyCook
7d952dc226 Settings: new display tab 2025-09-14 10:17:28 -04:00
Ly-sec
3cb838b455 NCheckbox: edit sizing
NToggle: edit sizing, fix thumb vertical center
2025-09-14 16:01:00 +02:00
Ly-sec
7594651e05 SettingsTabs: use NHeader, move display settings around 2025-09-14 15:50:23 +02:00
Ly-sec
0d611fc891 Merge branch 'vertical-bar' of https://github.com/noctalia-dev/noctalia-shell into vertical-bar 2025-09-14 13:46:05 +02:00
Ly-sec
8982909fae Edit Style.qml so barHeight check for vertical bar
SystemMonitor.qml: edit layout a little bit
2025-09-14 13:45:17 +02:00
ItsLemmy
132b331c7c Dock: fix floating distance when bar is at the bottom 2025-09-14 07:42:10 -04:00
ItsLemmy
85cef214c8 Merge branch 'vertical-bar' of github.com:Ly-sec/Noctalia into vertical-bar 2025-09-14 07:27:16 -04:00
ItsLemmy
80b4dad199 NPill: using monospace font 2025-09-14 07:27:14 -04:00
Ly-sec
aadbc9596d NSearchableComboBox: small layout change 2025-09-14 13:25:02 +02:00
Ly-sec
0949d154c1 Merge branch 'vertical-bar' of https://github.com/noctalia-dev/noctalia-shell into vertical-bar 2025-09-14 13:22:49 +02:00
Ly-sec
a86a0d33c1 NSearchableComboBox: created, uses fuzzy find
GeneralTab: replace NComboBox with NSearchableComboBox
2025-09-14 13:22:17 +02:00
ItsLemmy
e6372a2473 VerticalBar: smaller spacing and margin 2025-09-14 07:21:49 -04:00
ItsLemmy
e3d9ab5679 NPill better naming so files stay closeby 2025-09-14 07:21:32 -04:00
Ly-sec
ccd7458ea3 KeyboardLayout: fix language detection/parsing
Bar: add a tiny bit more spacing between widgets
NHorizontalPill: fix layout
MediaMini: set size to 0 if no media is playing
2025-09-14 10:21:53 +02:00
Ly-sec
d41b59d563 KeyboardLayout: fix ukranian iso code 2025-09-14 09:31:07 +02:00
Ly-sec
290ba4ac03 Fix N*Pill expanded text layout 2025-09-14 09:26:05 +02:00
Ly-sec
1ee14df915 Make things more readable 2025-09-14 09:05:51 +02:00
LemmyCook
76376a9783 Dock: do not show dock if no app/toplevel available 2025-09-13 22:13:02 -04:00
LemmyCook
880ac93662 autoformatting 2025-09-13 22:12:44 -04:00
LemmyCook
1157c8e21d FloatingBar: Wip 2025-09-13 22:04:36 -04:00
LemmyCook
2082cfe7c7 Merge branch 'main' into vertical-bar 2025-09-13 15:27:55 -04:00
LemmyCook
9a9f2886e0 Floating Bar: Fix for #265 (overlapping panels, toasts and notifications) 2025-09-13 15:23:27 -04:00
Ly-sec
0035fbcc4e NPill: act as loder for NVerticalPill and NHorizontalPill
NHorizontalPill: should be used for anything that expands horizontal
NVerticalPill: should be used for anything that expands vertical
2025-09-13 20:52:20 +02:00
Kainoa Kanter
5ca2c2a095 docs: mention gpu-screen-recorder Flatpak 2025-09-13 11:42:41 -07:00
Lysec
46103062d0 Merge pull request #266 from povvke/fix-app2unit-steam-games
Fix steam games not launching with app2unit
2025-09-13 20:02:10 +02:00
povvke
78a41c236c use the exec string itself to launch non terminal apps 2025-09-13 20:48:34 +03:00
Ly-sec
9dfac69e9e More spacing fixes 2025-09-13 19:28:44 +02:00
LemmyCook
de72236fe5 Merge branch 'main' into vertical-bar 2025-09-13 13:06:21 -04:00
LemmyCook
101e3125a9 Vertical bar: simpler management 2025-09-13 13:06:17 -04:00
Ly-sec
b443c9f492 Add compact clock again 2025-09-13 17:46:38 +02:00
Ly-sec
2a1e7832d6 Revert 8c81514 2025-09-13 17:44:31 +02:00
Ly-sec
8c815146e6 More fixes 2025-09-13 17:34:13 +02:00
LemmyCook
acae2b8c21 Dock: border alpha follows bg opacity 2025-09-13 11:09:55 -04:00
Ly-sec
004836fc8f More layout fixes 2025-09-13 17:00:49 +02:00
Ly-sec
b51f2d16cb Change Notification location 2025-09-13 16:47:32 +02:00
Ly-sec
6fba9d9f22 NPanel positioning fixes 2025-09-13 16:45:22 +02:00
LemmyCook
335e38d461 Floating Bar: simplified settings 2025-09-13 10:16:54 -04:00
Ly-sec
ee50d84a53 Fix spacing for vertical bar 2025-09-13 15:51:21 +02:00
Ly-sec
e706dabef3 Add BarService, use signals to check state of bar and update widgets accordingly 2025-09-13 15:31:23 +02:00
Ly-sec
dcedae46e5 Horizontal bar: try to get better spacing 2025-09-13 15:21:36 +02:00
LemmyCook
f27f9d35b0 Merge branch 'hyprland-smarter-detect' 2025-09-13 09:10:50 -04:00
Ly-sec
4f5acb7114 First iteration of vertical bar 2025-09-13 14:26:20 +02:00
Lysec
25ba27cbdd Merge pull request #262 from Mtendekuyokwa19/nixpkg
invalid nixpkg change
2025-09-13 13:14:26 +02:00
Mtende Kuyokwa
74da975ed4 invalid nixpkg change 2025-09-13 13:04:54 +02:00
Ly-sec
6f6a5b364a Bar: proper top/bottom margin check 2025-09-13 10:55:14 +02:00
Ly-sec
f670f88804 NPanel: add margin if bar is floating (except for SettingsPanel) 2025-09-13 10:25:50 +02:00
Ly-sec
814cb774a6 Notification: added margin if bar is floating 2025-09-13 10:13:51 +02:00
Ly-sec
50d8b54adf Bar: add floating setting 2025-09-13 10:11:57 +02:00
LemmyCook
ae931b791f Icons: replaced most left over filled icons by the outlined counterparts.
- only kept a few filled for basic controls (carets, play, pause,
etc...)
2025-09-12 23:20:33 -04:00
LemmyCook
dd4641eedd CompositorService: improved Hyprland detection so there is no warning on Niri. 2025-09-12 22:55:13 -04:00
LemmyCook
b66bb46fc1 We don't use qmlformat, we do use qmlfmt 2025-09-12 21:18:24 -04:00
LemmyCook
5079fc78d3 Removed ArchUpdateService 2025-09-12 21:16:50 -04:00
LemmyCook
7d2eaa46e6 qmlfmt: increase line-length to 360 to avoid hard-wrap.
+ cleaned up power menu/panel
2025-09-12 21:07:11 -04:00
LemmyCook
1043eaa39f Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-12 20:45:08 -04:00
LemmyCook
f16798f6e3 LockScreen: shorter tooltips 2025-09-12 20:45:06 -04:00
Lemmy
96acb1a679 Update README.md 2025-09-12 20:42:37 -04:00
LemmyCook
0f93797ab3 LockScreen: tooltip uniformisation 2025-09-12 18:22:12 -04:00
LemmyCook
3186a84d6b Settings: using "cloud-sun" for weather tab 2025-09-12 18:18:12 -04:00
Lemmy
d3ee66d845 Merge pull request #261 from MrDowntempo/monochrome
Updated Monochrome to not be more grayscale and pretty
2025-09-12 18:05:10 -04:00
Corey Woodworth
a8837283ab Updated Monochrome to not be more grayscale and pretty 2025-09-12 16:59:08 -04:00
LemmyCook
6fe0784c00 Autoformatting 2025-09-12 16:45:35 -04:00
Lemmy
59ce164b40 Merge pull request #257 from mkuritsu/main
Add toggle to hide unoccupied workspaces
2025-09-12 16:42:36 -04:00
mkuritsu
70144eb06f Fixed redundant comparison 2025-09-12 21:33:12 +01:00
Ly-sec
5136af5d95 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-12 22:20:48 +02:00
Ly-sec
ff42244c6d Revert hardcoded font change 2025-09-12 22:20:46 +02:00
mkuritsu
3a2bb40117 Add toggle to hide unoccupied workspaces 2025-09-12 21:16:44 +01:00
LemmyCook
bcd3100849 Merge branch 'dock-better-peeking' 2025-09-12 16:10:45 -04:00
LemmyCook
c8886629ad Dock: Float improvements, can click below dock and on the side. Should fix #237 2025-09-12 16:10:03 -04:00
Ly-sec
be4a69f6e0 Replace hardcoded font with check for default fonts, fall back to
inter/roboto
Settings: use font detection function
GeneralTab: let user know that it uses default fonts and falls back to
inter/roboto
FontService: add proper checks for default fonts (sans & mono)
2025-09-12 22:00:05 +02:00
LemmyCook
62b12d5436 Bar ethernet icon: unfilled 2025-09-12 14:50:09 -04:00
LemmyCook
99e75d51b8 Bar settings icon: unfilled 2025-09-12 14:48:13 -04:00
Lemmy
079c8f0803 Merge pull request #259 from ThatOneCalculator/settings-icons
fix: consistent settings icons
2025-09-12 14:46:26 -04:00
Kainoa Kanter
16f87cbfa3 fix: consistent settings icons 2025-09-12 11:22:41 -07:00
LemmyCook
380f31fbd9 BaBar Widgets: pass a proper section name instead of a longer string. 2025-09-12 12:54:09 -04:00
LemmyCook
28677d6888 Panels: added kb focus to BTPanel, NotifHistory, SidePanel, so they close with ESC. 2025-09-12 11:29:46 -04:00
Matej Cotman
b9ae772987 feat(Modules/LockScreen): divert PAM messages to user (eg: to notify the user about fingerprint reader) 2025-09-12 18:26:15 +03:00
Matej Cotman
4265290a0f fix(fingerprint): better fingerprint integration by removing the check for empty password 2025-09-12 18:26:11 +03:00
Lemmy
07e94b0f0e Update feature_request.md 2025-09-12 10:58:27 -04:00
Lemmy
307318918d Update bug_report.md 2025-09-12 10:58:11 -04:00
Lemmy
3f6662182e Merge pull request #258 from matejc/feat/notifications-close-on-clear
feat(Modules/Notification): auto-close history panel on clear history
2025-09-12 10:52:11 -04:00
Matej Cotman
be532fa146 feat(Modules/Notification): auto-close history panel on clear history 2025-09-12 17:44:14 +03:00
mkuritsu
722a59da80 add qmlformat simple file, add .gitignore with .qmlls.ini 2025-09-12 11:48:56 +01:00
Lemmy
3c97acf00f Merge pull request #255 from MrDowntempo/monochrome
Add Monochrome (Black & White) color scheme
2025-09-12 00:03:47 -04:00
Corey Woodworth
2d4fa59c41 Add Monochrome (Black & White) color scheme 2025-09-11 23:31:52 -04:00
LemmyCook
c5ca758d3e Settings: New Dock tab. 2025-09-11 23:31:40 -04:00
LemmyCook
6f70a98b83 ColorSchemeTab: fixed currently selected scheme to match wallpaper. 2025-09-11 23:18:13 -04:00
LemmyCook
424594a11a Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-11 22:46:21 -04:00
LemmyCook
f5ac42c692 Dock: Slightly more compact 2025-09-11 22:46:19 -04:00
Lemmy
675f96d0e6 Merge pull request #254 from SeraphimRP/patch-1
Fix a missing semicolon in Nix instructions.
2025-09-11 21:48:23 -04:00
Rdr. Seraphim Pardee
9570688294 Fix a missing semicolon in Nix instructions. 2025-09-11 20:45:48 -04:00
LemmyCook
626b745ce3 Settings: Added a ScreenCorners section in the general tab. 2025-09-11 19:22:07 -04:00
Lemmy
4afb98cf4c Merge pull request #244 from juvevood/screen-corners-radius
Screen Corners use general radius ratio of settings
2025-09-11 19:17:42 -04:00
LemmyCook
df2a9a246d Dock: New "exclusive" settings to ensure no windows go below. 2025-09-11 19:14:19 -04:00
Lemmy
d80e9ba3d5 Merge pull request #252 from BinaryQuantumSoul/patch-1
Update nix readme instructions
2025-09-11 19:01:49 -04:00
Lemmy
130c68b3e2 Merge pull request #250 from matejc/fix/bluetooth-switch
fix(bluetooth): rename wifiSwitch to bluetoothSwitch
2025-09-11 18:59:33 -04:00
Lemmy
6eea4a17a4 Merge pull request #253 from SailorSnoW/fix/screen-recorder-aarch
import gpu-screen-recorder only on x86_64 in nix flake
2025-09-11 18:56:29 -04:00
SailorSnoW
40dc8633ec import gpu-screen-recorder only on x86_64 2025-09-12 00:38:38 +02:00
QuantumSoul
12ac91d125 Update README.md 2025-09-12 00:31:41 +02:00
LemmyCook
87d86911d7 BarSectionEdit: fix click in the background closing panel, fix ghost bg color when dragging 2025-09-11 18:06:12 -04:00
LemmyCook
2872a7b5c9 Using NScrollView and NListView where it matters.
Not using them in tiny ListViews (ex: NComboBox, and Media player
dropdown)
2025-09-11 17:58:28 -04:00
LemmyCook
4067896434 New components: NScrollView + NListView
Allow controlling the handle color and stuf...
2025-09-11 17:56:47 -04:00
LemmyCook
78443451e4 Bar Widgets: Hover color switched from mPrimary to mTertiary for consistency 2025-09-11 17:30:52 -04:00
LemmyCook
719f5a20e7 Bar widget editor: better colors + autoformatting 2025-09-11 16:45:33 -04:00
LemmyCook
d8b12e6d6b Rosepine: revamped light theme by following RosePine Dawn 2025-09-11 16:29:11 -04:00
LemmyCook
9a0746d737 PowerToggle: was not receiving scaling which led to a broken bar. 2025-09-11 15:56:10 -04:00
LemmyCook
77f8b3937c RosePine: improve dark theme 2025-09-11 15:13:05 -04:00
Matej Cotman
3f4313635a fix(bluetooth): rename wifiSwitch to bluetoothSwitch to fix the toggle switch 2025-09-11 21:23:22 +03:00
LemmyCook
004d92a85d SidePanelToggle: use Noctalia logo by default 2025-09-11 13:34:04 -04:00
LemmyCook
720c17258b Weather: use the regular "sun" icon (unfilled) for better uniformity 2025-09-11 11:52:33 -04:00
LemmyCook
a8b312f3a7 SidePanel: even more robust with sizing forced everywhere 2025-09-11 11:47:09 -04:00
LemmyCook
4d6361dfe5 Updated font 2025-09-11 11:46:50 -04:00
LemmyCook
1f75819795 Tabler icons: commented out all broken icons (due to Qt's font rendering) 2025-09-11 11:26:50 -04:00
LemmyCook
50ddd2916c autoformatting 2025-09-11 11:26:29 -04:00
Ly-sec
d30e14f611 CompositorService: add tons of null checks to perhaps prevent QS crashes
(and add some logging)
ActiveWindow: added debounce for icons
KeyboardLayoutService: remove console logs
2025-09-11 17:18:25 +02:00
LemmyCook
227b0dd962 removed extra logs 2025-09-11 09:46:45 -04:00
LemmyCook
ac61086c95 Autoformatting 2025-09-11 09:45:26 -04:00
LemmyCook
0980f65751 Cloud-sun icon 2025-09-11 09:45:21 -04:00
LemmyCook
7aa3da2ff4 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-11 09:45:05 -04:00
LemmyCook
83fbb8f95d Clock: factorized many settings in a single combobox 2025-09-11 09:43:52 -04:00
Ly-sec
a029463527 CompositorService: use idx for niri workspaces 2025-09-11 14:31:36 +02:00
Ly-sec
baafe54d13 Clock: small changes to compact mode 2025-09-11 13:53:45 +02:00
Ly-sec
a1cbd35202 Clock: add compact mode with nnumeric/verbose date options 2025-09-11 13:03:39 +02:00
LemmyCook
1337a35a1e Removed video 2025-09-10 22:37:28 -04:00
Lysec
61006fbed0 Update README.md 2025-09-11 04:21:55 +02:00
Ly-sec
eff4337d35 README: more updates 2025-09-11 04:18:33 +02:00
Juve
f0733f19dd add a separate configuration item for edge of screen 2025-09-11 10:11:01 +08:00
Ly-sec
818df48787 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-11 04:01:02 +02:00
Ly-sec
0eedfba071 README: update preview 2025-09-11 04:00:49 +02:00
LemmyCook
2dc9e2f212 Bluetooth: proper synchronisation of the adapter state with the cached setting 2025-09-10 21:29:11 -04:00
LemmyCook
62a3b343cf Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-10 21:14:04 -04:00
LemmyCook
76be93a84d NPanel: fix 3 minor warnings 2025-09-10 21:14:01 -04:00
Ly-sec
b59c56170e Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-11 03:02:44 +02:00
Ly-sec
c9285d8c5b SystemMonitor: remove GPU temp 2025-09-11 03:02:36 +02:00
LemmyCook
b157d855a8 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-10 20:45:56 -04:00
LemmyCook
82ac49ce85 NPanel: simplified screen/scaling management 2025-09-10 20:45:50 -04:00
Ly-sec
7247a26586 KeyboardLayout: add tons of layouts, add Commons/KeyboardLayout.qml for ease of adding new ones 2025-09-11 01:40:25 +02:00
Ly-sec
be0b568f1f KeyboardLayout: increase font size and make it all caps 2025-09-11 01:10:04 +02:00
Lysec
e4b54e518c Merge pull request #243 from juvevood/fix-powerpanel-shortcut
fix for PowerPanel Shortcut invalid
2025-09-11 00:53:50 +02:00
Lysec
6ea1e2b4c7 Update README.md 2025-09-11 00:15:52 +02:00
Ly-sec
5b4c57eae2 Set version to dev 2025-09-10 23:28:23 +02:00
Ly-sec
6e5efc3244 Release v2.8.0
We've been busy squashing bugs and adding some nice improvements based on your feedback.
What's New
New Icon Set - Swapped out Material Symbols for Tabler icons. They look great and load faster since they're built right in.
Works on Any Linux Distro - Dropped the Arch-specific update checker so this works properly on whatever distro you're running. You can build your own update notifications with Custom Buttons if you want.
Icon Picker - Added a proper icon picker for custom button widgets. No more guessing icon names.
Smarter Audio Visualizer - The Cava visualizer actually pays attention now - it only kicks in when you're playing music or videos instead of running all the time.
Better Notifications - Notifications now show actual app names like "Firefox" instead of cryptic IDs like "org.mozilla.firefox".
Less Noise - Turned a bunch of those persistent notification popups into toast notifications so they don't stick around cluttering your screen.
Fixes

Active Window widget finally shows the right app icon and title consistently
Fixed a nasty crash on Hyprland
Screen recorder button disables itself if the recording software isn't installed
Added a force-enable option for Night Light so you can turn it on manually whenever
2025-09-10 23:19:22 +02:00
Ly-sec
271a887bbf Release Notes
We've been busy squashing bugs and adding some nice improvements based on your feedback.
What's New
New Icon Set - Swapped out Material Symbols for Tabler icons. They look great and load faster since they're built right in.
Works on Any Linux Distro - Dropped the Arch-specific update checker so this works properly on whatever distro you're running. You can build your own update notifications with Custom Buttons if you want.
Icon Picker - Added a proper icon picker for custom button widgets. No more guessing icon names.
Smarter Audio Visualizer - The Cava visualizer actually pays attention now - it only kicks in when you're playing music or videos instead of running all the time.
Better Notifications - Notifications now show actual app names like "Firefox" instead of cryptic IDs like "org.mozilla.firefox".
Less Noise - Turned a bunch of those persistent notification popups into toast notifications so they don't stick around cluttering your screen.
Fixes

Active Window widget finally shows the right app icon and title consistently
Fixed a nasty crash on Hyprland
Screen recorder button disables itself if the recording software isn't installed
Added a force-enable option for Night Light so you can turn it on manually whenever
2025-09-10 23:14:58 +02:00
Ly-sec
0571ba7325 test commit 2025-09-10 23:14:39 +02:00
Ly-sec
c2f6c39016 Revert "Release v2.8.0"
This reverts commit 2de2908509.
2025-09-10 23:13:02 +02:00
Ly-sec
2de2908509 Release v2.8.0
We've been busy squashing bugs and adding some nice improvements based on your feedback.
What's New
New Icon Set - Swapped out Material Symbols for Tabler icons. They look great and load faster since they're built right in.
Updater Widget - Dropped the Arch-specific update checker so this works properly on whatever distro you're running. You can build your own update widget with Custom Buttons if you want.
Icon Picker - Added a proper icon picker for custom button widgets. No more guessing icon names.
Better Notifications - Notifications now show actual app names like "Firefox" instead of cryptic IDs like "org.mozilla.firefox".
Less Noise - Turned a bunch of those persistent notification popups into toast notifications so they don't stick around cluttering your screen.
Fixes

Active Window widget finally shows the right app icon and title consistently
Fixed a nasty crash on Hyprland
Screen recorder button disables itself if the recording software isn't installed
Added a force-enable option for Night Light so you can turn it on manually whenever

That's what claude had  to offer😄
2025-09-10 23:07:54 +02:00
LemmyCook
99d56687ef SysStat: house keeping (keep cpu stuff grouped) 2025-09-10 14:34:27 -04:00
LemmyCook
434b8273f0 SystemStats: better gpu logging 2025-09-10 14:31:05 -04:00
LemmyCook
663382c81c Icons: "trash" instead of "trash-filled" 2025-09-10 10:56:31 -04:00
LemmyCook
3f388bdb4b Widgets Drag&Drop: fix for panel closing when clicking rapidly in the background of a widget. 2025-09-10 09:33:08 -04:00
LemmyCook
0a4317f712 More drag and drop fixes 2025-09-10 09:11:13 -04:00
LemmyCook
b9dbbf7bdd Widgets Drag&Drop: drop indicator and improved behavior 2025-09-10 09:02:09 -04:00
LemmyCook
6ed9a8c5ae SysMon: smaller font 2025-09-10 08:30:38 -04:00
LemmyCook
73de564bb6 IconPicker: fixed at 6 columns with slightly bigger icons 2025-09-10 08:13:10 -04:00
LemmyCook
1f62cdedb5 Icons: cloud-fog 2025-09-10 08:04:18 -04:00
Ly-sec
7ed0e894ec Icons: updated TablerIcons, NightLight 2025-09-10 13:51:37 +02:00
Ly-sec
d39a9a85bf SystemMonitor: add GPU temperature option 2025-09-10 13:17:35 +02:00
Ly-sec
d16d1c1d26 NotificationHistory: even more fixes for appIcon 2025-09-10 12:55:56 +02:00
Ly-sec
291ffac102 NotificationHistory: possible visibility fix for app icons 2025-09-10 12:52:32 +02:00
Ly-sec
2b18ed3c41 NotificationHistory: add app icon display 2025-09-10 12:47:04 +02:00
Ly-sec
3b50efc7d0 ColorScheme: possible fix for selecting colorscheme & dark mode toggle 2025-09-10 12:39:15 +02:00
Ly-sec
d91a635781 NightLight: add force activation 2025-09-10 12:34:52 +02:00
Juve
4afe2d8448 Screen Corners use gerneral radius ratio of settings 2025-09-10 12:55:50 +08:00
LemmyCook
74fce51c2d Icons: new aliases image => photo 2025-09-09 23:59:21 -04:00
Juve
44cdbfe5d7 fix for PowerPanel Shortcut invalid 2025-09-10 11:45:49 +08:00
LemmyCook
4fbb8314eb Icons: settings-network: sitemap-filled 2025-09-09 23:13:06 -04:00
LemmyCook
851a5a6f58 Icon: settings-network using 'sitemap' same as lan 2025-09-09 23:08:08 -04:00
LemmyCook
833808152e Icons: added icons to settings main content title + slightly smaller NCircleStat badges 2025-09-09 22:17:48 -04:00
LemmyCook
84706cab4b Icons: sun-wind for weather partly-cloudy 2025-09-09 21:42:43 -04:00
LemmyCook
e571f26583 Icons: improved ethernet icon 2025-09-09 21:33:31 -04:00
LemmyCook
16cea533da Bluetooth: added a button to enable/disable straight from the panel + minor improvements. 2025-09-09 21:23:57 -04:00
LemmyCook
b1f501f3f9 Added tabler icons license 2025-09-09 21:08:13 -04:00
LemmyCook
afcba942c7 Better settings icons 2025-09-09 20:38:16 -04:00
LemmyCook
5e6f77f875 More icons improvements 2025-09-09 20:32:19 -04:00
LemmyCook
1f9247c429 More icons fixes 2025-09-09 19:34:31 -04:00
LemmyCook
d089966249 NetworkService: bugfix the interface could not longer be enabled or disabled. 2025-09-09 18:53:53 -04:00
LemmyCook
b2d629e6a1 More icons 2025-09-09 18:43:39 -04:00
LemmyCook
032087b611 Battery: disabled test mode 2025-09-09 18:31:59 -04:00
LemmyCook
22dd2bf75c All power icons 2025-09-09 18:30:44 -04:00
LemmyCook
7adc7f43cc Bluetooth devices icons 2025-09-09 18:18:44 -04:00
LemmyCook
a38f49cb35 More icons work 2025-09-09 18:10:25 -04:00
LemmyCook
ca7684c944 ArchUpdater: permanently removed 2025-09-09 18:10:11 -04:00
LemmyCook
955369ab13 More icons work 2025-09-09 17:34:14 -04:00
LemmyCook
48f6c0705b New icons: more icons and cleanup 2025-09-09 17:02:57 -04:00
LemmyCook
43eec0e387 Refactor icons font wip 2025-09-09 14:46:11 -04:00
LemmyCook
b1f9609cd3 Renamed Icons.qml to AppIcons.qml for clarity 2025-09-09 14:16:37 -04:00
Ly-sec
4f731b67d1 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-09 18:38:48 +02:00
Ly-sec
6549b0fc57 NotificationHistoryPanel: possible solution for #235 2025-09-09 18:38:43 +02:00
LemmyCook
ffb972f7c6 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-09 12:28:12 -04:00
LemmyCook
6ed2daa386 NIconButton/CustomButton: added an extra flag to allow click when the button is disabled.
Helps with custom button to get redirected to the settings
2025-09-09 12:28:09 -04:00
LemmyCook
8a4042913b Bootstrap: make the icons map readonly 2025-09-09 12:27:25 -04:00
Lysec
fb3c2f3bb2 Merge pull request #241 from lonerOrz/fix/power
Fix: Incorrect 0% battery warning at startup
2025-09-09 18:20:35 +02:00
Ly-sec
5dc4ba504c PowerProfileService: don't show toast on non valid power profile 2025-09-09 18:15:44 +02:00
loner
c31dc75c63 Fix: Incorrect 0% battery warning at startup 2025-09-10 00:13:00 +08:00
Ly-sec
1f0be929d7 Edit README and also the flake
README: remove breaking change notice (we use a fontloader for the
bootstrap font)
flake: remove material and bootstrap font dependency
2025-09-09 17:57:04 +02:00
LemmyCook
56ea1f92fa Merge branch 'bootstrap-icons' 2025-09-09 11:44:27 -04:00
Ly-sec
5042d4d747 BootstrapIcons: bundle font with noctalia, use fontloader 2025-09-09 17:41:16 +02:00
Ly-sec
144406ae0e PowerProfile: create PowerProfileService, use it for the BarWidget and
PowerProfilesCard
2025-09-09 17:12:56 +02:00
Ly-sec
3d51f758f8 README: small change 2025-09-09 17:03:28 +02:00
Ly-sec
107f6fdfce README: Update breaking changes text 2025-09-09 16:31:19 +02:00
Ly-sec
e4d499b550 Revert "README: Update breaking changes text"
This reverts commit 9c3726bdb1.
2025-09-09 16:30:36 +02:00
Ly-sec
9c3726bdb1 README: Update breaking changes text 2025-09-09 16:28:47 +02:00
Ly-sec
a4534b1611 Merge branch 'bootstrap-icons' 2025-09-09 16:14:18 +02:00
Ly-sec
e4cad6ed20 Update README and flake.nix
README: inform users about breaking changes (due to the font change)
flake: attempt to install the bootstrap-icons font
2025-09-09 15:44:11 +02:00
LemmyCook
5f1cfb9072 CustomButton: no border 2025-09-09 09:27:46 -04:00
LemmyCook
a6ccc8b0da NButton: fix issue when no icon defined 2025-09-09 09:22:05 -04:00
LemmyCook
fe139c208a CustomIcomButton: changed default icon to "heart" 2025-09-09 09:16:48 -04:00
LemmyCook
64c1e842f9 Merge branch 'bootstrap-icons' of github.com:noctalia-dev/noctalia-shell into bootstrap-icons 2025-09-09 09:13:29 -04:00
LemmyCook
cfd7dec04d WeatherCard: Vertical centering of icons 2025-09-09 09:13:27 -04:00
Ly-sec
a00676f5db Merge branch 'bootstrap-icons' of https://github.com/noctalia-dev/noctalia-shell into bootstrap-icons 2025-09-09 15:12:49 +02:00
Ly-sec
61cf7ab843 CustomButtonWidget: add icon picker to improve usability 2025-09-09 15:12:46 +02:00
LemmyCook
d76d1c628a NIconButton: animation on color (bg+fg) 2025-09-09 08:56:30 -04:00
LemmyCook
f6b3f6d2ec ProfileCard: more discrete System uptime 2025-09-09 08:49:08 -04:00
LemmyCook
24863c2527 Merge branch 'bootstrap-icons' of github.com:noctalia-dev/noctalia-shell into bootstrap-icons 2025-09-09 08:27:46 -04:00
LemmyCook
ecd6141739 Toast: better spacing/margin 2025-09-09 08:27:44 -04:00
Ly-sec
ee44920fa4 Merge branch 'bootstrap-icons' of https://github.com/noctalia-dev/noctalia-shell into bootstrap-icons 2025-09-09 14:26:29 +02:00
Ly-sec
864cbfcfab NSpinBox: remove unicode, use Bootstrap.qml 2025-09-09 14:26:27 +02:00
LemmyCook
73541eec49 ActiveWindow + MediaMini: width boosted to 6% 2025-09-09 08:25:25 -04:00
LemmyCook
87425efa88 Merge branch 'bootstrap-icons' of github.com:noctalia-dev/noctalia-shell into bootstrap-icons 2025-09-09 08:23:36 -04:00
LemmyCook
4455074493 ActiveWindow+MediaMini: auto min & max width 2025-09-09 08:23:34 -04:00
LemmyCook
5e23476089 NIconButton: font size auto determined by button size 2025-09-09 08:17:00 -04:00
Ly-sec
1232c0268c Merge branch 'bootstrap-icons' of https://github.com/noctalia-dev/noctalia-shell into bootstrap-icons 2025-09-09 14:12:29 +02:00
Ly-sec
ed9ee65885 ActiveWindow: add guarding for null title/icon (Hyprland)
CompositorService: turn title, appId and id into strings to perhaps
prevent crashing (Hyprland)
2025-09-09 14:11:18 +02:00
LemmyCook
bc7fe21d27 Widget Settings: always use MetaData as default + Removed non existing settting from space (debugMode leftovers) 2025-09-09 08:09:22 -04:00
LemmyCook
f7b0a28b1e Icon: different memory usage icon (cpu) 2025-09-09 08:06:14 -04:00
Ly-sec
b422a419cd BatteryWidget: add low battery threshold
NSpinBox: add bootstrap icons
2025-09-09 13:26:15 +02:00
Ly-sec
663f3abff5 Merge branch 'bootstrap-icons' of https://github.com/noctalia-dev/noctalia-shell into bootstrap-icons 2025-09-09 13:21:09 +02:00
Ly-sec
3c9ce6f8b5 ScreenRecorder: check for availability 2025-09-09 13:20:46 +02:00
LemmyCook
c9a128e439 Merge branch 'bootstrap-icons' of github.com:Ly-sec/Noctalia into bootstrap-icons 2025-09-09 07:18:46 -04:00
LemmyCook
e8f356f5ac ActiveWindow+MediaMini: Shifted one color each: mPrimary, mSecondary 2025-09-09 07:17:50 -04:00
Ly-sec
94d64a91b8 Add toasts & tooltips to a lot of things, add Disk Usage 2025-09-09 13:08:48 +02:00
LemmyCook
fdfe9ea2e1 WifiPanel: fix missing device icon 2025-09-09 01:45:32 -04:00
LemmyCook
d4d7b06b64 NPill + Clock color uniformisation 2025-09-09 01:44:14 -04:00
LemmyCook
56d87ecfcf Polishing
- Volume: better spread/usage of the 3 icons
- Rosepine colors: more contrast to compare to matugen
- NPill: different look when pile is always opened
2025-09-09 01:02:53 -04:00
LemmyCook
76ef2469e8 Shaders: path from root for easier maintenance + cleanup fallback icons 2025-09-09 00:35:12 -04:00
LemmyCook
16bd4b41dc Icons: ArchUpdater, LockScreen, PowerMenu, BT Device List 2025-09-08 23:32:35 -04:00
LemmyCook
0e4b79fd16 SystemStats / network: dont show bytes 2025-09-08 22:34:56 -04:00
LemmyCook
ad73f11b69 Removed: old system-stats script 2025-09-08 22:23:02 -04:00
LemmyCook
bacd65b274 Icons: 99% done 2025-09-08 22:21:18 -04:00
LemmyCook
1f8c55d581 Icons: huge cleanup 2025-09-08 22:05:57 -04:00
LemmyCook
ccdb4e0664 Icons: more icons 2025-09-08 21:37:01 -04:00
LemmyCook
c77784b5c1 Icons: most settings tabs 2025-09-08 21:23:57 -04:00
LemmyCook
74cf71755b Icons: battery + bt 2025-09-08 21:10:03 -04:00
LemmyCook
a4107c87c0 Icons: WIP using a proper mapping table 2025-09-08 21:05:48 -04:00
LemmyCook
8da2cdf430 Icons: better nightlight and notification history 2025-09-08 20:29:11 -04:00
LemmyCook
7e93e29f66 Icons: duo for nightlight 2025-09-08 20:20:40 -04:00
LemmyCook
b2e11137d4 Weather icon: fix thunderstorm 2025-09-08 20:11:26 -04:00
LemmyCook
d086d64d5f Icons: half of BT 2025-09-08 18:51:05 -04:00
LemmyCook
fa970986dc Icons: more icons 2025-09-08 18:45:09 -04:00
LemmyCook
4c9e89915e Icons: more icons 2025-09-08 17:53:55 -04:00
LemmyCook
97c7fd8073 Icons: more icons 2025-09-08 17:26:21 -04:00
LemmyCook
29167de546 Icons: picking from the right range 2025-09-08 17:11:24 -04:00
LemmyCook
d6f629d4bb Icon test 2025-09-08 16:50:13 -04:00
LemmyCook
b13c40e238 Icon: new speed icon 2025-09-08 16:46:42 -04:00
LemmyCook
170fbea7a4 Settings: better alignment with new icons + check icon on wallpaper selector 2025-09-08 16:17:34 -04:00
LemmyCook
08d2747f1e Icons: color picker + better tab alignment in settings
+ autoformatting
2025-09-08 16:08:53 -04:00
LemmyCook
b91112fc7a Icons: Plus and Minus
+ removed vertical hack in NIcon
2025-09-08 16:02:21 -04:00
LemmyCook
ea6b8e0c02 Icons: Brightness and battery 2025-09-08 15:53:50 -04:00
LemmyCook
404a1d3e8b New icons + some warning fixes 2025-09-08 15:22:43 -04:00
LemmyCook
6f1b88e76d more icons 2025-09-08 14:44:28 -04:00
LemmyCook
6169f88d90 Default skull icon 2025-09-08 14:33:20 -04:00
LemmyCook
6f4a4bb764 volume icons 2025-09-08 13:55:48 -04:00
LemmyCook
242ae17d0a panel icon 2025-09-08 13:29:17 -04:00
LemmyCook
736979c4dc more icons 2025-09-08 13:25:03 -04:00
LemmyCook
4b775fc29d Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-08 12:35:43 -04:00
LemmyCook
ed78b6b3f5 Basic bootstrap icons test 2025-09-08 12:35:29 -04:00
Lysec
a0494d1759 Merge pull request #236 from ThatOneCalculator/fix/animation-speed-logic
fix: divide instead of multiply animation speed
2025-09-08 18:27:41 +02:00
Kainoa Kanter
1c0c4e955a divide instead of multiply animation speed 2025-09-08 09:22:21 -07:00
LemmyCook
b639c3632d autoformatting 2025-09-08 11:51:32 -04:00
LemmyCook
6c93b1b768 Settings: Fix widget settings upgrade on startup, to never overwrite an existing setting with default value. 2025-09-08 10:32:26 -04:00
Ly-sec
d05255c15b Notification: show resolved app name instead of id (possibly fixes #230) 2025-09-08 15:38:29 +02:00
Lysec
59ef26af1c Merge pull request #233 from msdevpt/fix-clear-message
fix: message when no items on clipboard
2025-09-08 15:29:54 +02:00
LemmyCook
33c6ade8f8 Cava: runs only when MediaService is playing 2025-09-08 09:01:27 -04:00
LemmyCook
8c115b8bb0 Settings: fixed faulty widget upgrade 2025-09-08 08:59:30 -04:00
LemmyCook
3271fa1d23 Init: better widget upgrading process + less warnings when starting up without config or cache 2025-09-08 08:39:56 -04:00
LemmyCook
b43b065cf2 Wallpaper: minor optimizations/simplification 2025-09-08 07:51:01 -04:00
Ly-sec
66a4618d09 switch to dev version 2025-09-08 12:33:17 +02:00
Ly-sec
983e3c5cbe Release v2.7.0
Network: Even more improvements
SysStat: Remove bash script
Notification: Pore image support
NotificationHistory: Proper unread count
Settings: Migrate Bar widgets to new settings
BarWidgets: Easier to access, edit
Background: add default wallpaper (if none is set)
SystemMonitor: add % support for RAM
BarTab:
- remove global settings for widgets
- add settings button per bar widget, this makes it possible to have separate settings of the same kind with different settings. This also makes it way easier to configure.

A decent amount of QoL changes & fixes
2025-09-08 12:28:35 +02:00
Ly-sec
c02d3e3d22 Merge branch 'bartab-overhaul' 2025-09-08 12:21:18 +02:00
Ly-sec
c0900b105b Background: add default wallpaper 2025-09-08 08:46:10 +02:00
Ly-sec
b6166a2a7c SystemMonitor: add % support for RAM usage 2025-09-08 08:04:18 +02:00
Ly-sec
38928abab7 Fix first start noctalia settings & color creation 2025-09-08 07:51:49 +02:00
LemmyCook
849f3c52d7 Notifications badge: hidden by default 2025-09-08 01:10:48 -04:00
LemmyCook
f9e55c8f8d Workspace: removed extra transparent padding around. 2025-09-08 01:03:58 -04:00
LemmyCook
993a7965fd NPill: fixed look at high scaling 2025-09-08 01:00:38 -04:00
LemmyCook
d4f6462e8a Battery: deactivated test mode 2025-09-08 00:40:12 -04:00
LemmyCook
8bfde2f6d8 NPill: fixed, finally! 2025-09-08 00:39:07 -04:00
LemmyCook
b3eea2215d Bar Add Widget: taller NComboBox 2025-09-08 00:05:58 -04:00
LemmyCook
4d7bc811c4 Widget Settings: load settings before triggering the loader to avoid async loading. 2025-09-08 00:02:15 -04:00
LemmyCook
74ec5ea606 Cava: running at all time as its getting to know if a widget needs it. 2025-09-07 23:59:22 -04:00
LemmyCook
dda0266798 Autoformatting 2025-09-07 23:51:31 -04:00
LemmyCook
99d9dbe218 WidgetSettings: replaced all checkboxes by the usual toggles. 2025-09-07 23:51:09 -04:00
LemmyCook
89c7f05782 NLabel: always full width even when there is no description 2025-09-07 23:45:13 -04:00
LemmyCook
d9c36a81c4 NightLight: fixed rightclick to open settings 2025-09-07 23:18:27 -04:00
LemmyCook
91747c71f2 Main Settings: cleaned tabs since we removed many settings 2025-09-07 23:18:10 -04:00
LemmyCook
5a1231a17e Settings: completed migration of old settings on startup 2025-09-07 22:55:28 -04:00
LemmyCook
517c7c97d4 Bar Widgets FrontEnd: Simplified access to editable widget settings 2025-09-07 22:23:45 -04:00
LemmyCook
45af873a6f Bar Widget Settings: One file per Widget settings, refactor - wip 2025-09-07 21:45:28 -04:00
LemmyCook
c01167c9da Settings tabs: adapt to new sizing of NComboBox 2025-09-07 21:24:53 -04:00
LemmyCook
a68b3f49b0 NComboBox: better sizing 2025-09-07 21:13:45 -04:00
LemmyCook
e03042c411 NCheckBox: fast animation speed like the others 2025-09-07 21:13:31 -04:00
LemmyCook
3065bec6c9 BarSectionEditor: Buttons are now easier to click + reverted back to 5 basic colors 2025-09-07 20:03:14 -04:00
LemmyCook
dae1d12b6f NPill: smoother animation when opening and closing (no instant width jump) 2025-09-07 18:50:21 -04:00
LemmyCook
c4846cd977 NPill: improved text centering 2025-09-07 18:42:39 -04:00
LemmyCook
f95c9b76d4 Clock fully migrated to new user settings 2025-09-07 14:40:33 -04:00
LemmyCook
fb01392bc3 Settings: cleanup 2025-09-07 14:29:14 -04:00
LemmyCook
498ee478e7 Settings: centralized migration to user settings. wip 2025-09-07 14:28:50 -04:00
M.Silva
53ff6cc21a fix: message when no items on clipboard 2025-09-07 18:48:50 +01:00
LemmyCook
ba33451957 Network/Wi-Fi: many fixes and robustness improvements
- proper detection when password is wrong
- prevent a new connection while already connecting to a network
- new mechanism to skip scan results if a new scan is incoming (avoid UI
discrepancies)
2025-09-07 13:02:13 -04:00
Ly-sec
d6e253fe7f Replace some double with real 2025-09-07 16:25:11 +02:00
Ly-sec
c32a8a863a WeatherTab: remove useless divider 2025-09-07 16:22:07 +02:00
LemmyCook
4ba0f8d958 Network: Scanning use a more reliable backward parsing + added logs to figure potential bug. 2025-09-07 10:06:53 -04:00
Ly-sec
e4e2ed41b4 Rename TimeWeatherTab to WeatherTab, remove Time settings from said tab
WeatherTab: renamed from TimeWeatherTab, remove Time settings
Time: Time/Date is now widget driven
2025-09-07 15:48:16 +02:00
Ly-sec
888ba108e0 Edit NButton alignment 2025-09-07 15:33:47 +02:00
Ly-sec
c14eb95dba BarWidgetSettingsDialog: remove DND, rename Save to Apply 2025-09-07 15:20:24 +02:00
Ly-sec
dc0ef93680 Notification: DND just uses Settings.data.notifications.doNotDisturb now 2025-09-07 15:18:12 +02:00
Ly-sec
a2ea3c116d NotificationHistory: better display for unread notifications 2025-09-07 15:09:30 +02:00
Ly-sec
4578aad0bc NotificationHistory: properly hook up the unread counter 2025-09-07 14:57:09 +02:00
Ly-sec
57448f100c bartab-overhaul: initial commit 2025-09-07 14:48:20 +02:00
Ly-sec
835f88d71e Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-07 12:51:15 +02:00
Ly-sec
291d919b9f Notification: add -i support 2025-09-07 12:51:13 +02:00
LemmyCook
adac96ee84 SidePanel: proper height computation 2025-09-07 00:49:59 -04:00
LemmyCook
9010a1668b SysStat: fixed warning. cant assign undefined to real 2025-09-07 00:43:57 -04:00
LemmyCook
f27608947c Settings: slightly more compact tabs 2025-09-07 00:06:58 -04:00
LemmyCook
fb2d42da57 SysStat Service: less log on intel CPU 2025-09-06 23:47:17 -04:00
LemmyCook
2bc1d53b18 SysStat Service: Porting code to JS/QML instead of an external bash 2025-09-06 23:43:00 -04:00
LemmyCook
36d3a50f21 Brightness: brings back realtime brightness monitoring for internal(laptop) display.
The pill will open and show the change in real time
2025-09-06 19:27:32 -04:00
LemmyCook
9bc6479c92 NPill: for battery use a very light outline around the icon 2025-09-06 18:34:44 -04:00
LemmyCook
56993d3c00 Battery: Minimal BatteryService which only serve an appropriate icon. Trying different icons rotated 90 degrees to the left. 2025-09-06 18:16:59 -04:00
LemmyCook
86c6135def Network/Wi-Fi: improvements
- Always check for ethernet status every 30s. Should not affect battery
life.
- Less aggressive scan intervals to give more times for slow adapters.
2025-09-06 16:11:16 -04:00
LemmyCook
1bb1015fdf Dock: one tooltip per app instead of a shared tooltip. avoid a few glitches when hovering. 2025-09-06 15:25:57 -04:00
LemmyCook
ac43b6d78a Dock: autoformatting 2025-09-06 15:19:06 -04:00
LemmyCook
809f16c27e Dock: improvements, new animations, always float, better look. 2025-09-06 15:18:53 -04:00
LemmyCook
7860c41959 Network/Wi-Fi: Removed auto polling every 30sec. Factorized more code and cleaned logs 2025-09-06 14:14:47 -04:00
LemmyCook
fc1ee9fb2f Network/WiFi: improve UI with more immediate feedback on operations.
+ proper deletion of profiles when forgetting a network
2025-09-06 13:03:22 -04:00
LemmyCook
5bc8f410e7 Network/Wi-Fi: smarter logging to avoid flood 2025-09-06 09:32:02 -04:00
Ly-sec
3d9ef8c2ed switch to dev version 2025-09-06 14:20:31 +02:00
Ly-sec
0e53ce3ac0 Release v2.6.0
SettingsPanel: added keyboard navigation
BluetoothPanel: UI enhancements
WiFiPanel: UI enhancements
NotificationPanel: UI enhancements
ColorPicker: UI enhancements
Toast: handle switching between toasts much better
Notification: add DND option
Notification: add actions
LauncherTab: add app2unit toggle
Spacer: added spacer widget with configurable width
ActiveWindow: fix hyprland icon display
PowerPanel: add keybind controls
NetworkService: make it way more reliable

More QoL fixes & changes
2025-09-06 14:17:12 +02:00
Ly-sec
4131e6503b Implement keyboard controls for PowerPanel as requested in ##227
PowerPanel: add support for keyboard controls
2025-09-06 12:44:19 +02:00
Ly-sec
0aaf78fc51 ActiveWindow: fix hyprland icon display (fixes #201) 2025-09-06 12:40:29 +02:00
Ly-sec
977b2d9e7c Added a Spacer widget so people can add spacing between other widgets
(as requested in ##226).
Spacer: create variable width invisible rectangle
BarWidgetSettingsDialog: add Spacer support
BarWidgetRegistry: add Spacer
2025-09-06 12:27:06 +02:00
Ly-sec
e76b2c5497 Launcher: fix app2unit execution, implemented #202 2025-09-06 12:18:14 +02:00
Ly-sec
8658e11c1d NotificationHistoryPanel: fix layout alignment 2025-09-06 12:16:53 +02:00
LemmyCook
b3e4486699 Network: better refresh vs wifi scan 2025-09-06 01:14:40 -04:00
LemmyCook
2398961473 Wifi: more clean ups and improvements 2025-09-06 01:04:08 -04:00
LemmyCook
a57bfeba31 Background: Qt.callLater does not accept a delay as parameter. 2025-09-06 00:36:03 -04:00
LemmyCook
2f416a87f0 Wifi/Network: refactoring to something simpler to maintain 2025-09-06 00:02:32 -04:00
LemmyCook
9a6c98c134 WiFi: removed status indicator 2025-09-05 23:18:23 -04:00
LemmyCook
35ca346246 Tooltip 2025-09-05 23:17:18 -04:00
LemmyCook
0fd9ac15cd One more tooltip 2025-09-05 22:38:20 -04:00
LemmyCook
ae12d77e29 Tooltips: should end with a coma. 2025-09-05 22:37:54 -04:00
Lemmy
9065257961 Merge pull request #225 from lonerOrz/fix/keyboard-layout-alignment
fix: align KeyboardLayout widget with other bar components
2025-09-05 22:31:41 -04:00
LemmyCook
561b55cb9e Autoformatting 2025-09-05 22:18:08 -04:00
LemmyCook
4f871296ae ColorPicker: splitted in two NColorPicker + NColorPickerDialog
+ fixed layout and a few little bugs
2025-09-05 22:17:58 -04:00
loner
55b74ad38f fix: align KeyboardLayout widget with other bar components 2025-09-06 09:46:42 +08:00
LemmyCook
8426e36f46 Time: improved human readable time + fixed a few tooltips. 2025-09-05 21:08:30 -04:00
LemmyCook
85d94aca01 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-05 21:04:09 -04:00
LemmyCook
39c7089cbc Notification: fixed persistent DND toast. 2025-09-05 21:04:02 -04:00
Ly-sec
eb072ff88a Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-06 02:16:13 +02:00
Ly-sec
0c4046b993 ColorPicker: UI overhaul 2025-09-06 02:15:51 +02:00
LemmyCook
90cd5467fe Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-05 19:57:24 -04:00
LemmyCook
05bfb6fc37 Do Not Disturb: factorized logic and toast in its proper service. 2025-09-05 19:57:22 -04:00
Lemmy
966b2410d3 Update README.md 2025-09-05 19:49:01 -04:00
LemmyCook
8ec1ad7255 TaskBar converted to Layout 2025-09-05 19:12:32 -04:00
LemmyCook
1efa1f4aa3 ActiveWindow: Converted to Layout 2025-09-05 19:06:15 -04:00
LemmyCook
0a48e5f34f Clock: text was too big 2025-09-05 18:59:53 -04:00
LemmyCook
ad305b3754 Dock: converted to Layout 2025-09-05 18:53:24 -04:00
LemmyCook
78cb7d4c15 MediaMini: fix visualizer not showing when track length is unknown (twitch) 2025-09-05 18:49:59 -04:00
LemmyCook
7b5c97f38a Tray: converted to Layout 2025-09-05 18:49:34 -04:00
Ly-sec
59bf98e04c Vesktop Template: fix placeholder text 2025-09-06 00:44:05 +02:00
LemmyCook
7feab63e5b Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-05 18:33:53 -04:00
LemmyCook
5d7e168a57 NCircleStat + KeyboardLayout: converted to Layout 2025-09-05 18:33:51 -04:00
Ly-sec
8038b7f6a0 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-06 00:31:28 +02:00
Ly-sec
2533c52e27 Launcher: add app2unit options (hopefully) 2025-09-06 00:30:47 +02:00
LemmyCook
cf624f4d65 Notification: Converted to Layout
+ removed fontPointSize on NIconButton. use sizeRatio instead.
2025-09-05 18:29:06 -04:00
LemmyCook
a4c98f1382 NotificationHistory: fully converted to Layout 2025-09-05 18:19:27 -04:00
LemmyCook
4768485974 LockScreen: converted to Layout 2025-09-05 18:15:28 -04:00
LemmyCook
9a14a5cc10 SettingsPanel: converted to layout 2025-09-05 18:05:42 -04:00
LemmyCook
cbffc1a14c SidePanel: height fix 2025-09-05 18:05:23 -04:00
Lysec
25e1c6e759 Merge pull request #224 from ThatOneCalculator/refactor/notification-default-action-text
make default notification action text "Open"
2025-09-05 23:50:06 +02:00
Kainoa Kanter
e41c35cb5b make default notification action text "Open" 2025-09-05 14:47:32 -07:00
LemmyCook
078e111ecd Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-05 17:47:20 -04:00
LemmyCook
01aeceddf4 PowerPanel: converted to Layout 2025-09-05 17:47:19 -04:00
LemmyCook
93a3bc2090 SysMonCard: converted to layout 2025-09-05 17:45:17 -04:00
Ly-sec
28b0536916 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-05 23:44:52 +02:00
Ly-sec
86734f17c4 Notification: remove some logging, implement #223 2025-09-05 23:44:49 +02:00
LemmyCook
94293e4c63 Bar SysMon: converted to Layout 2025-09-05 17:44:04 -04:00
LemmyCook
f06d0f4e1e Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-05 17:32:54 -04:00
Ly-sec
a5fc9d9ca9 Notification: add actions
README: add fix for niri action buttons for notifications
2025-09-05 23:31:55 +02:00
LemmyCook
c85a309aeb MediaMini: converted to Layout 2025-09-05 17:23:02 -04:00
Ly-sec
4cac584409 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-05 23:07:10 +02:00
Ly-sec
b30d3df15c Notification: only display app icon/avatar if the notification requested it 2025-09-05 23:07:05 +02:00
LemmyCook
6f69654816 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-05 17:06:10 -04:00
LemmyCook
8fedd7612d NToast: Column => ColumnLayout 2025-09-05 17:06:09 -04:00
Ly-sec
c16e6e7423 Notification: adjust layout 2025-09-05 23:00:03 +02:00
Ly-sec
c8a056f332 Notification: add DND option to widget and notification panel as requested in #212 2025-09-05 22:42:40 +02:00
Ly-sec
60950fb461 dock: add opacity slider as requested in #222 2025-09-05 22:36:04 +02:00
Ly-sec
a3aba8d0db Toast: update visibility for newest toast 2025-09-05 22:29:20 +02:00
LemmyCook
f9a48becce SettingsPanel: finaly fixed the conflict between scrollview and textinput! 2025-09-05 15:47:07 -04:00
LemmyCook
3140039ccb NTextInput: simplified code in an attempt to fix text selection issues with mouse.
Not fixed yet, but I know where the conflict is!
2025-09-05 15:08:45 -04:00
LemmyCook
56fedcf495 HooksTab: removed ScrollView which already exists in parent (SettingsPanel.qml) 2025-09-05 15:07:31 -04:00
LemmyCook
783e9fb140 AboutTab: improved look of "Download latest release" 2025-09-05 14:46:08 -04:00
LemmyCook
b69d6f57d4 Bump dev version 2025-09-05 14:41:04 -04:00
LemmyCook
125d844e3b NInputAction simplification 2025-09-05 14:19:08 -04:00
LemmyCook
f04ac180f0 NInputAction: use proper label/description + autoformatting 2025-09-05 14:13:05 -04:00
LemmyCook
1cab452352 WiFi: small improvements to UI and service 2025-09-05 13:35:07 -04:00
LemmyCook
f3d1d15b61 NPill: added support for middle mouse button 2025-09-05 13:34:31 -04:00
Lemmy
0915071299 Merge pull request #220 from ThatOneCalculator/refactor/audio-bar-widgets-click-consistency
refactor: consistent click behavior for volume & mic bar widgets
2025-09-05 13:29:16 -04:00
Kainoa Kanter
b787080715 consistent tooltip 2025-09-05 10:28:57 -07:00
LemmyCook
a69a6eda4d FontService: tweaked logs 2025-09-05 13:27:54 -04:00
Kainoa Kanter
dd757c2114 refactor: consistent click behavior for volume & mic bar widgets
- left click: open audio settings panel (unchanged) - right click:
toggle mute - middle click: pwvucontrol
2025-09-05 10:26:46 -07:00
LemmyCook
eedea01679 NetworkService: dont report empty errors 2025-09-05 12:04:49 -04:00
LemmyCook
0567da94dd WiFi: auto formattings (removed es6 syntax for split to not break qmlfmt) 2025-09-05 11:58:30 -04:00
LemmyCook
de92c989f2 Launcher: hotfix clicking on an item would not activate it. 2025-09-05 10:33:57 -04:00
LemmyCook
507843be21 --amend 2025-09-05 08:54:13 -04:00
LemmyCook
b9c1a8a54f WiFi: improved UI and service 2025-09-05 08:36:36 -04:00
LemmyCook
35283a6923 WiFi: cleaner look, similar to BT. 2025-09-05 00:55:47 -04:00
LemmyCook
9ae78eda45 Bluetooth: more UI polish 2025-09-04 23:48:16 -04:00
LemmyCook
cc8a24f445 Bluetooth Panel: UI cleanup/factorization 2025-09-04 23:26:19 -04:00
LemmyCook
5910c65bcf SidePanel: fix #218 sidepanel should open next to the button (as other panels) 2025-09-04 20:38:24 -04:00
LemmyCook
e5aee79d47 Removed all layer.enabled as they do not play well with fractional scaling. 2025-09-04 20:36:32 -04:00
LemmyCook
a249e15c58 WiFi: Fix password input placeholder dots are cut off #203 2025-09-04 19:58:08 -04:00
LemmyCook
cdcfe328d2 NPanel: rounding x,y coordinates to avoid artifacts 2025-09-04 19:53:33 -04:00
LemmyCook
784300f690 Dock: removed unecessary bg hover rect 2025-09-04 19:51:10 -04:00
LemmyCook
8ad2bef2f5 NButton: added support for right click and middle click, removed rippled effect. 2025-09-04 18:54:41 -04:00
LemmyCook
2bd30947fc SettingsPanel: reordered code. 2025-09-04 17:30:52 -04:00
LemmyCook
84e8793a29 SettingsPanel: improved keyboard controls 2025-09-04 17:28:35 -04:00
LemmyCook
be1643c5b8 SettingsPanel: added keyboard navigation (Tab, Vim, Up/Down) to change active tab. 2025-09-04 17:14:54 -04:00
LemmyCook
b00f058eac Removed log 2025-09-04 17:06:46 -04:00
LemmyCook
8bab23cfec Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-04 17:03:13 -04:00
LemmyCook
7b26e38f33 Launcher: improved/fixed keyboard controls (Ctrl+J / Ctrl+K) 2025-09-04 17:03:11 -04:00
Lemmy
9e6bd3be76 Update README.md 2025-09-04 16:17:39 -04:00
LemmyCook
97b016b21b Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-04 16:17:33 -04:00
LemmyCook
84fdb7c647 Wallpaper: added IPC to set a wallpaper
qs -c noctalia-shell ipc call wallpaper set $path $monitor

$monitor can be a monitor name or "all" or "" to assign to all monitors.
2025-09-04 16:17:31 -04:00
Lemmy
6d70944fc8 Update bug_report.md 2025-09-04 15:48:12 -04:00
Lemmy
fcb4fa1b59 Update bug_report.md 2025-09-04 15:46:28 -04:00
Lemmy
e69086f1a6 Merge pull request #213 from ThatOneCalculator/patch-1
docs: power management
2025-09-04 15:42:28 -04:00
Kainoa Kanter
fa22607c2c docs: power management 2025-09-04 12:41:00 -07:00
LemmyCook
9168eba07b autoformatting 2025-09-04 15:37:12 -04:00
Lemmy
5d11e37687 Merge pull request #210 from ThatOneCalculator/feat/caffeine-widget
feat: keep awake bar widget
2025-09-04 15:33:06 -04:00
Lemmy
4ea903b333 Merge pull request #208 from ThatOneCalculator/feat/power-toggle-widget
feat: power toggle bar widget
2025-09-04 15:30:56 -04:00
Lemmy
8fd805815d Merge pull request #205 from ThatOneCalculator/sidebar-toggle-settings
toggle settings panel on right-clicking side panel toggle
2025-09-04 15:29:43 -04:00
LemmyCook
c055690a9b Cleaned up init sequence 2025-09-04 15:27:17 -04:00
Kainoa Kanter
dcf146a097 feat: keep awake bar widget 2025-09-04 12:15:19 -07:00
LemmyCook
e3f50c0ce2 Hotfix: wallpaper was not set on startup. 2025-09-04 15:11:45 -04:00
Kainoa Kanter
c394368dc5 fix sizing 2025-09-04 12:03:41 -07:00
Kainoa Kanter
1f9c54438a feat: power toggle bar widget 2025-09-04 12:00:17 -07:00
Kainoa Kanter
f303f305af Merge branch 'noctalia-dev:main' into sidebar-toggle-settings 2025-09-04 11:54:32 -07:00
LemmyCook
5f1f3dce4a CustomButton: fix size to match other bars button 2025-09-04 14:52:45 -04:00
LemmyCook
f84889ca13 UpdateService: inverted logic 2025-09-04 14:46:29 -04:00
LemmyCook
b778a80c79 Settings: better icons for Hooks tab 2025-09-04 14:46:19 -04:00
Kainoa Kanter
0bf632a4b1 toggle settings panel on right-clicking side panel toggle 2025-09-04 11:20:01 -07:00
Ly-sec
321c513682 UpdateService: set release to false 2025-09-04 19:49:57 +02:00
Ly-sec
9db6a0d438 Release v2.5.0
- Launcher: full rework
- Notification: display app icon
- Hooks: let people create their own commands after wallpaper change &
  light/dark toggle
- NInputAction: create new widget

A lot of quality of life changes & fixes
2025-09-04 19:41:44 +02:00
Ly-sec
a9affb5ae4 Hooks: expose to grab the screen name 2025-09-04 19:15:50 +02:00
Ly-sec
46bc8939b4 Hooks: make hook activate after settings are updated 2025-09-04 18:23:46 +02:00
Ly-sec
fe6ecf7daf Launcher: remove 50 item limit, fixes #200 2025-09-04 18:15:04 +02:00
Ly-sec
a72b896c5f README: small changes 2025-09-04 18:12:36 +02:00
Ly-sec
a91d790074 HooksTab: replace NText with NLabel 2025-09-04 18:01:06 +02:00
Ly-sec
ac21deefa4 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-04 17:57:43 +02:00
Ly-sec
37eefe3663 Created Hook system (let's users run commands after specific actions)
NInputAction: create NTextInput with NButton
HooksService: add dark/light mode hook, add wallpaper change hook
HooksTab: create 1 NInputAction for each hook
Wallpaper: add hook functionallity
2025-09-04 17:54:58 +02:00
LemmyCook
2e082ed8b1 proper border on notifications 2025-09-04 11:45:07 -04:00
LemmyCook
c1bec66151 Cleanup: removed Color.applyOpacity in favor of Qt.alpha 2025-09-04 11:29:45 -04:00
LemmyCook
d53a404bf1 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-04 11:06:56 -04:00
LemmyCook
7ed4c209fe Optims: replaced a bunch of Qt.rgba by Qt.alpha 2025-09-04 11:06:54 -04:00
Ly-sec
83205d57d9 IPCHandler: small change to getActiveScreen() 2025-09-04 16:45:07 +02:00
Ly-sec
43bb3bdd0c IPCHandler: use getActiveScreen() everywhere 2025-09-04 16:42:28 +02:00
LemmyCook
cde3f088d1 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-04 10:26:47 -04:00
LemmyCook
3e7ebf44f3 IPC: more robust screen detection 2025-09-04 10:26:45 -04:00
Ly-sec
ac7092943c Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-04 16:19:02 +02:00
Ly-sec
e7bbb7fc00 CustomButton: let people use quotes etc 2025-09-04 16:18:58 +02:00
LemmyCook
2fda29c185 autoformatting 2025-09-04 10:17:02 -04:00
LemmyCook
2793863689 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-04 10:16:39 -04:00
LemmyCook
7fafda4747 NPanel: clear openedPanel attempt 2025-09-04 10:16:37 -04:00
LemmyCook
bb0f1e84ce IPC: Fail safe when no activeWindow detected 2025-09-04 10:16:15 -04:00
Ly-sec
3ceba43802 Notification: prefer notification image over app image 2025-09-04 16:12:50 +02:00
LemmyCook
f8ed4f48cf logs 2025-09-04 09:52:15 -04:00
LemmyCook
d319ab9bfc IPC: IPC calls now properly identified the proper monitor so that dimming and other stuff works better. 2025-09-04 09:37:50 -04:00
LemmyCook
902cdc39e0 Merge branch 'custom-buttons' 2025-09-04 08:40:00 -04:00
LemmyCook
00d3f81aa1 Bar: check if new widget modelData is available to avoid warnings. 2025-09-04 08:35:57 -04:00
LemmyCook
d8c91a942f SettingsPanel: restoring keyboard focus 2025-09-04 08:28:58 -04:00
LemmyCook
30e1c2d2b3 BarSectionEditor: cleaned up logs 2025-09-04 08:28:41 -04:00
Ly-sec
4229721774 Notification: add app icon support 2025-09-04 13:38:39 +02:00
LemmyCook
4a45e73125 BarSettings: better D&D 2025-09-04 00:58:41 -04:00
LemmyCook
9e819084af BarSettings: reworking drag&drop 2025-09-04 00:04:02 -04:00
LemmyCook
f39dd2aa1c Custom Button: better bar editor 2025-09-03 22:59:59 -04:00
LemmyCook
4f3e0bdb1e SettingsPanel: remove keyboard focus, so it will close gracefully if clicking on something else (like others NPanels) 2025-09-03 22:30:42 -04:00
LemmyCook
21383b03c5 Custom Buttons: working left/right/middle click 2025-09-03 22:22:22 -04:00
LemmyCook
17944211d5 Custom buttons: WIP support for left/right/middle click 2025-09-03 21:59:33 -04:00
LemmyCook
1f919e4469 NIconButton: added support for middle click 2025-09-03 21:59:03 -04:00
LemmyCook
06a11f003b SettingsPanel: fixed audio tab name 2025-09-03 21:58:51 -04:00
LemmyCook
807867ef42 Custom buttons: refactored files structure 2025-09-03 21:27:42 -04:00
LemmyCook
598bc48957 Custom buttons: improved UI, still wip 2025-09-03 20:51:51 -04:00
LemmyCook
7f34ca4122 Custom buttons: WIP implementing custom properties 2025-09-03 19:09:36 -04:00
Lysec
291cd5130d Update README.md 2025-09-04 00:45:37 +02:00
Ly-sec
280952aae3 README: add manual install IPC explanation 2025-09-04 00:45:07 +02:00
LemmyCook
3ba6899e69 Wallpaper: minor improvements 2025-09-03 17:51:02 -04:00
LemmyCook
65f73bb1ba Launcher: Restored keyboard navigation with PageUp/PageDown/Home/End + Vim Keys
Oddly Ctrl+J does not work for me...
2025-09-03 17:02:05 -04:00
LemmyCook
392f0e14b2 Launcher: fixed IPC calls + fix locked up results in clipboard after short successive opening. 2025-09-03 13:49:45 -04:00
LemmyCook
1e81a89a1a Merge branch 'launcher-evolved' 2025-09-03 11:23:15 -04:00
LemmyCook
11a13ce589 Launcher: Fix missing argument to onStatusChanged 2025-09-03 11:11:37 -04:00
LemmyCook
24620210fe Launcher: improved clipboard images look 2025-09-03 10:43:00 -04:00
LemmyCook
7b2d490ba7 Launcher: clipboard, prevent unecessary refresh while browsing 2025-09-03 10:25:44 -04:00
LemmyCook
20b29f98a7 Launcher: deleted ClipboardService, renamed CliphistService to ClipboardService. 2025-09-03 09:35:33 -04:00
LemmyCook
132dbce3a3 Launcher: wip image preview 2025-09-03 09:22:27 -04:00
LemmyCook
ded133d164 Launcher: wip image preview 2025-09-03 08:44:10 -04:00
LemmyCook
7548ffc191 Laucher: Fix wayland warning about focus surface stealing 2025-09-03 08:05:06 -04:00
LemmyCook
1599ee5682 Launcher: Working clipboard plugin 2025-09-03 08:01:24 -04:00
Ly-sec
40b57c2df0 Weather: change how default city is set 2025-09-03 13:50:16 +02:00
Ly-sec
c6e56d4264 Add default fallback city (fixes #199), add beginning of UpdateService
Weather: always fallback to "Tokyo" if the city name is empty
UpdateService: simple versioning control
2025-09-03 13:37:24 +02:00
Ly-sec
7141a91994 DistroLogoService: add NixOS path as requested in #197 2025-09-03 13:05:51 +02:00
LemmyCook
742a600e38 Launcher: first refactoring pass 2025-09-02 22:20:01 -04:00
LemmyCook
80a2e69eaa SidePanel: increased height by 8 pixels. 2025-09-02 20:01:00 -04:00
LemmyCook
a7ce6737ec NComboBox: slightly taller by default 2025-09-02 19:55:23 -04:00
LemmyCook
dfd7edc540 Settings: better default folder for wallpapers and videos 2025-09-02 19:55:06 -04:00
Ly-sec
ac65d19809 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-02 20:09:44 +02:00
Ly-sec
520da3e915 Replace NTextInput with NComboBox for font settings
FontService: use Qt.fontFamilies to grab available fonts and split Mono
fonts
NComboBox: allow height changes
GeneralTab: replace NTextInput with NComboBox
2025-09-02 20:07:10 +02:00
LemmyCook
d79011355c Dock: Fixed dock autohide when bar is at the bottom. 2025-09-02 13:35:24 -04:00
LemmyCook
5859270ad0 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-02 13:06:12 -04:00
LemmyCook
26dc143b1d Dock: allow clicking outside of the dock on the left and right side 2025-09-02 13:06:10 -04:00
Lemmy
76a8e644e0 Update README.md 2025-09-02 12:10:35 -04:00
Ly-sec
8d05cb9f3b README: fix small typo 2025-09-02 17:20:30 +02:00
Ly-sec
7f5d70bcc8 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-02 17:03:36 +02:00
Ly-sec
eea1586772 Added distro logo (for SidePanel widget)
BarTab: add toggle for distro logo replacement
DistroLogoService: handle all logo detection logic
SidePanelToggle: add support for distro logo
WidgetLoader: fix small issue with with screen null warning
2025-09-02 17:01:38 +02:00
Lemmy
6740959866 Update README.md - Cleaned IPC calls area. 2025-09-02 10:50:57 -04:00
Lemmy
f9c1fa78aa Update README.md - added DarkMode IPC calls 2025-09-02 10:42:39 -04:00
LemmyCook
f385b24e8c IPC: added darkMode control
"call darkMode toggle"
"call darkMode setDark"
"call darkMode setLight"
2025-09-02 10:40:47 -04:00
LemmyCook
508c1407be DarkModeToggle: new bar widget 2025-09-02 10:34:05 -04:00
LemmyCook
b908dc0ed2 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-02 08:51:10 -04:00
LemmyCook
6c041fb27f Settings: minor UI improvements 2025-09-02 08:51:08 -04:00
LemmyCook
63a545736c WeatherCard: minor UI improvements (colors and size) 2025-09-02 08:48:24 -04:00
Ly-sec
91b355689c Wallpaper: add ipc call to set new random wallpaper 2025-09-02 14:42:04 +02:00
Ly-sec
3e598cf1cd UtilitiesCard: close SidePanel when we start recording 2025-09-02 14:37:54 +02:00
Ly-sec
9781005a21 Added Thumbnail to ClipboardHistory images, fix keyboard navigation
ClipboardHistory: Add image thumbnail, fix navigation viewport
Launcher: replace TextField with NTextInput
2025-09-02 14:32:02 +02:00
LemmyCook
468272d4c9 NColorPicker: added theme colors to the first row of the palette. 2025-09-01 22:38:01 -04:00
LemmyCook
d5e83aa9de Wallpaper: added fill color that may show up around wallpaper (depends on fillMode)
+ New Widget NColorPicker
+ New Widget NButton
2025-09-01 22:27:49 -04:00
LemmyCook
69a5f0c2c0 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-01 21:30:40 -04:00
LemmyCook
f9194dd741 Wallpaper: added fillMode to all shaders (no, crop, fit, stretch) 2025-09-01 21:30:38 -04:00
Ly-sec
fff7cbde22 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-02 02:11:38 +02:00
Ly-sec
b796a00374 Add proper check if cliphist is available
CliphistService: Check if cliphist is available, if not do not spam logs
2025-09-02 02:10:20 +02:00
LemmyCook
cb7b1d92c6 Wallpaper: improved over conditional wallpaper management
- hide all wallpaper settings if feature is disabled
- hide wallpaper selector if feature is disabled
- hide quick access wallpaper if feature is disabled
2025-09-01 19:58:05 -04:00
LemmyCook
fac72b257c Recompiled stripes shader 2025-09-01 19:56:43 -04:00
Lemmy
e6a1bc6e27 Merge pull request #189 from lonerOrz/feat/wallpaper
Added a toggle for Noctalia-shell wallpaper management
2025-09-01 19:34:47 -04:00
Ly-sec
65794b52ec Fix double click on SidePanel to close
NPanel: remove WlrLayershell.keyboardFocus.OnDemand, only add it to
specific panels.
2025-09-01 23:57:51 +02:00
LemmyCook
9a4317739b SettingsPanel: better var naming 2025-09-01 15:16:28 -04:00
LemmyCook
de32b86f7c SettingsPanel: Improved auto-sizing so it should work well on large and small screens 2025-09-01 15:11:37 -04:00
LemmyCook
5a1faa0fd4 SettingsPanel: ensure we never clip screen height 2025-09-01 15:08:15 -04:00
LemmyCook
57d912efc8 Toast: proper scaling + brought back assignation to WlrLayer.Overlay so its above all. 2025-09-01 15:03:30 -04:00
LemmyCook
87067f7062 TrayMenu: fix dynamic scaling 2025-09-01 14:41:12 -04:00
LemmyCook
210bbac583 ScalingService: 1st pass of the refactoring via signals instead of nested bindings for better efficienty and compatibility with old versions of Qt 2025-09-01 13:52:12 -04:00
loner
81d3bad747 fix: shader error
The shader compilation error occurred in wp_stripes.frag, which handles
the "stripes" wallpaper transition. The bug was
  caused by the modulo operator (%), which is not supported in the older
GLSL version your system is using for
  compilation.

  I fixed it by replacing the incompatible % operator with the standard
mod() function, which works across all GLSL
  versions.
2025-09-02 00:13:54 +08:00
loner
2ddb14a95f Added a toggle for Noctalia-shell wallpaper management 2025-09-02 00:13:54 +08:00
LemmyCook
934c8c61b3 WallpaperSelector: current wallpaper border is a real border not a huge colored rectangle. looks better when switching wallpaper 2025-09-01 11:14:19 -04:00
Ly-sec
459bb59dd5 NightLight: moved from DisplayTab to BrightnessTab 2025-09-01 15:37:25 +02:00
LemmyCook
f1c9ed9caa Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-01 09:30:55 -04:00
LemmyCook
5d950b0a5e LightMode: better overview and transparency 2025-09-01 09:30:51 -04:00
Ly-sec
6f78079bc5 UtilitiesCard WallpaperSelector: add right click to choose random
wallpaper
2025-09-01 15:15:59 +02:00
LemmyCook
e3d62388f7 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-01 09:07:25 -04:00
LemmyCook
5fef9cfe6b WallpaperService: refactored to a simpler signal based approach. 2025-09-01 09:07:23 -04:00
Ly-sec
4a4bec5aec Add support for user based templates (~/.config/matugen/config.toml) as
requested in #185
MatugenService: add logic to scan for the matugen config.toml
ColorSchemeTab: add NCheckbox to toggle user based templates
2025-09-01 14:54:01 +02:00
Ly-sec
4193d3c87c Remove test-microphone.sh 2025-09-01 14:24:59 +02:00
Ly-sec
0fd83498ea Create Microphone widget as requested in #180
Microphone: hook up microphone functionallity to bar widget
2025-09-01 14:22:45 +02:00
Ly-sec
00c94755c5 Replace Mask ScreenCorners with Canvas
ScreenCorners: replace Mask with Canvas, RAM usage seems fine
2025-09-01 14:06:16 +02:00
quadbyte
e8c2042290 Settings: better looking settings panel on 1080p 2025-09-01 00:15:49 -04:00
LemmyCook
6bcb85137b BarTab/NSectionEditor: minor UI improvements 2025-08-31 22:55:51 -04:00
Lemmy
d910f30ed1 Merge pull request #181 from kevindiaz314/main
docs: update README for AUR package changes and improved installation structure
2025-08-31 22:01:19 -04:00
Lemmy
3e8a87c6d6 Merge pull request #182 from nollidnosnhoj/main
disable test mode for battery widget.
2025-08-31 22:00:02 -04:00
LemmyCook
ecb7a9d448 BarWidgets: fixed NPill conditional open left or right that I broke earlier. 2025-08-31 21:57:28 -04:00
LemmyCook
40edc38756 NSectionEditor: Force text width for a more uniform look 2025-08-31 21:42:55 -04:00
LemmyCook
d1f5d301c2 Color animations: more uniform across NWidgets 2025-08-31 21:36:30 -04:00
LemmyCook
102aca0fa0 Settings: less wide + cleanup about 2025-08-31 21:35:41 -04:00
LemmyCook
b0917f5a25 Auto-formatting 2025-08-31 21:35:16 -04:00
Dillon Johnson
5488063490 disable test mode for battery. 2025-08-31 15:16:06 -10:00
Kevin Diaz
ad125d7af9 docs: update README for AUR package changes and improved installation structure
- Update AUR git package description to clarify it "pulls" rather than
"builds" commits
- Change manual installation path to
~/.config/quickshell/noctalia-shell/
- Move installation comments outside bash code blocks so users can
easily copy and paste
- Update AUR/Manual install commands to use "qs -c noctalia-shell ipc
call..." format
- Improve table formatting and command alignment for better readability
2025-08-31 21:12:28 -04:00
LemmyCook
4510762a35 Revert "Wallpaper: attempt to fix wallpaper bindings on Qt 6.8"
This reverts commit c7ee627110.
2025-08-31 18:00:30 -04:00
LemmyCook
c7ee627110 Wallpaper: attempt to fix wallpaper bindings on Qt 6.8 2025-08-31 17:55:08 -04:00
LemmyCook
330eac08cb Wallpaper: back to onClicked for Wallpaper selection 2025-08-31 15:55:12 -04:00
LemmyCook
bb1d56121d Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-31 15:45:32 -04:00
LemmyCook
3683d3c29b NPill: allow to open left or right depending on 2025-08-31 15:45:10 -04:00
Lemmy
0736862a2c Merge pull request #179 from lesi-nedo/main
Fix to issue #165
2025-08-31 15:19:38 -04:00
Oleksiy Nedobiychuk
3891c7008a fix(bluetooth): enable disconnect/remove for paired devices #165 2025-08-31 21:00:15 +02:00
Oleksiy Nedobiychuk
5c729b25b4 Merge remote-tracking branch 'upstream/main' 2025-08-31 20:53:26 +02:00
Oleksiy Nedobiychuk
3151b1634c fix to issue #165 2025-08-31 20:51:04 +02:00
LemmyCook
2498f0273d Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-31 14:44:24 -04:00
LemmyCook
40579e1b80 NIconButton: better disabled state 2025-08-31 14:44:22 -04:00
Ly-sec
2bd6d23467 Vesktop: small fix 2025-08-31 20:38:16 +02:00
Ly-sec
eb7401d693 Vesktop: add more styling 2025-08-31 20:21:39 +02:00
Ly-sec
0f5bbb961d Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-31 20:01:07 +02:00
Ly-sec
fa82dea4d5 Add Vesktop matugen template
vesktop: create matugen template (based on catppuccin)
2025-08-31 20:00:29 +02:00
Oleksiy Nedobiychuk
46ef2b6e53 Merge branch 'fix/ddcutil-hang' 2025-08-31 17:49:53 +02:00
Oleksiy Nedobiychuk
4a799b755f Merge branch 'fix/ddcutil-hang' of https://github.com/lesi-nedo/noctalia-shell into fix/ddcutil-hang 2025-08-31 17:21:55 +02:00
LemmyCook
dda031e73b NightLight: if using autoSchedule, wait for coordinates to be ready 2025-08-31 11:04:09 -04:00
Ly-sec
2f8472f720 Even more ArchUpdater fixes
ArchUpdaterService:properly check for errors
2025-08-31 16:56:52 +02:00
Ly-sec
4520ed3cbf Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-31 16:44:43 +02:00
Ly-sec
1ecc3d9744 Fix ArchUpdater to be able to use ghostty
ArchUpdaterService: add separate ghostty command
ArchUpdater: Change color of symbol if no terminal/aur helper is found,
edited tooltip
ArchUpdaterPanel: Add proper error message if TERMINAL or aur helper
was not found
2025-08-31 16:41:52 +02:00
LemmyCook
fcf627c30b BarHeight: more rounding uniformization 2025-08-31 10:36:40 -04:00
LemmyCook
fdf67ab512 ScreenCorners: use the same Math.round() for bar height so corners dont overlap semitransp bar 2025-08-31 10:34:13 -04:00
LemmyCook
b1daf2e8bc Location: Set stable name on load to the user specified name. Until we get a proper weather update 2025-08-31 10:29:25 -04:00
LemmyCook
6ecbdda121 Location: should fix edge case of location data being not ready on time 2025-08-31 10:24:01 -04:00
Ly-sec
3f0374e1f2 ArchUpdater: more selective update debug logs & tests 2025-08-31 15:50:23 +02:00
Ly-sec
cb345c2364 ArchUpdater: Even more debug logs 2025-08-31 15:39:44 +02:00
Ly-sec
d0b957e998 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-31 15:29:14 +02:00
Ly-sec
7a2fa4a773 Add debug logs to ArchUpdater 2025-08-31 15:28:54 +02:00
LemmyCook
8a198fd707 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-31 09:21:12 -04:00
LemmyCook
7ba3870c82 SystemStats: no space before unit (to match the others stats) 2025-08-31 09:21:10 -04:00
Ly-sec
ff0c83a04c Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-31 15:20:13 +02:00
Ly-sec
9a8046b99f ArchUpdaterService: fix AUR helper detection 2025-08-31 15:19:58 +02:00
LemmyCook
92b37df962 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-31 09:15:17 -04:00
LemmyCook
ab4359b624 ActiveWindo/MediaMini: slight width improvements 2025-08-31 09:15:16 -04:00
Ly-sec
ab5b877dc3 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-31 15:00:56 +02:00
Ly-sec
23a41ff3c6 Fix selective update from ArchUpdater
ArchUpdaterService: edit command builder
ArchUpdaterPanel: fix error state display
2025-08-31 15:00:00 +02:00
LemmyCook
68a44b6ef7 Taskbar: small tweaks for better compliance to codebase 2025-08-31 08:53:01 -04:00
Lemmy
51ea837cd0 Merge pull request #174 from JPratama7/feat/app-taskbar
feat: app taskbar
2025-08-31 08:49:26 -04:00
LemmyCook
6f2d5c2752 Tooltip: more lower case 2025-08-31 08:48:34 -04:00
LemmyCook
d912c2a090 ArchUpdate: last console.log 2025-08-31 08:48:07 -04:00
LemmyCook
21876857fc Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-31 08:41:54 -04:00
LemmyCook
53405c13af NText: Reverted my change to support Richtext by default + All tooltips are no longer using capital letter at the start of every word 2025-08-31 08:41:51 -04:00
Ly-sec
abb5f385d9 Disable Network stats by default
Settings: set showNetworkStats to false
2025-08-31 14:32:30 +02:00
Ly-sec
4ad851fdd2 Update About Tab to handle long names better
AboutTab: adapt cellWidth/height to accommodate longer names
2025-08-31 14:22:54 +02:00
Ly-sec
8509845381 Update README
README: remove old migration informations
2025-08-31 14:20:15 +02:00
Ly-sec
d7eea7fdae Remove useless debug logs from ArchUpdater
ArchUpdaterService: remove 4 useless debug logs
2025-08-31 13:58:29 +02:00
Ly-sec
8395b2640e Fix ArchUpdaterService error codes (once more)
ArchUpdaterService: Update yay error code (1 also means no updates
available just like in paru)
2025-08-31 13:56:01 +02:00
Ly-sec
1eae0eb3d4 Fix ArchUpdater error codes, revert TrayMenu
TrayMenu: reverted it to the old PopupPanel for ignored
ArchUpdater: paru error code 1 = no updates available
2025-08-31 13:47:06 +02:00
Ly-sec
91ffa4a9fd Reimplement the MediaMini and ActiveWindow fix
Revert ScreenCorner fix (didn't work at all)
2025-08-31 11:18:24 +02:00
Ly-sec
58b93c9d22 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-31 11:15:18 +02:00
Ly-sec
6d2f4d51a2 Revert "Possibly fixed #117, format and also change a tiny thing in ActiveWindow"
This reverts commit 1e52e7ca40.
2025-08-31 11:14:23 +02:00
Ly-sec
7b63b6900d Don't dim ScreenCorners
NPanel: add dimOverlay
2025-08-31 10:34:01 +02:00
Ly-sec
1e52e7ca40 Possibly fixed #117, format and also change a tiny thing in ActiveWindow
MediaMini: properly elide and manage width of MediaMini
ActiveWindow: Make it respect width of ActiveWindow title
2025-08-31 09:55:17 +02:00
Ly-sec
2ebdc74f15 Add network stats to SystemMonitor, fix ActiveWindow text display
SystemMonitor: add network up/down stats (also added setting to disable
it in BarTab)
ActiveWindow: add elide if not hovered
2025-08-31 09:22:33 +02:00
Ly-sec
724e55c37d Autoformat 2025-08-31 08:57:00 +02:00
Ly-sec
51f1923e22 Fix TrayMenu crash after display wake. Add checks if screen exists, else set scaling to 1.0
TrayMenu: Replace PopupPanel with NPanel (for better loading & to
prevent QS crash)
Overview, Background etc: add screen checks, if it doesnt exist set
scaling to 1.0
2025-08-31 08:55:20 +02:00
Ly-sec
714f6c058f Small changes for ArchUpdaterService
ArchUpdaterService: remove duplicate AUR helper check and remove any
pacman occurrence
2025-08-31 07:46:28 +02:00
Ly-sec
560f601190 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-31 07:35:13 +02:00
Ly-sec
6deb039906 Autoformat 2025-08-31 07:34:59 +02:00
Ly-sec
f19eaf689b Rework ArchUpdater logic, update UI
ArchUpdater: remove pacman poll fully and rely on paru/yay
ArchUpdaterPanel: Remove scrollbar, remove UI blocking
README: Add `TERMINAL` env var info (again), add DiscoCevapi as Donator
2025-08-31 07:33:03 +02:00
quadbyte
80f6570f04 NightLight/Bar: left click toggle, converted to NIconButton
+ Adapted some tooltip to the new richtext NText
2025-08-31 01:26:04 -04:00
JPratama7
d883096971 feat: add little radius 2025-08-31 11:35:24 +07:00
LemmyCook
87f9afbd85 NightLight: reworked settings, defined fade duration and simplified service. 2025-08-31 00:13:40 -04:00
JPratama7
9bb5241e49 refactor: adjust tooltip 2025-08-31 10:54:13 +07:00
JPratama7
65601cb855 refactor: adjust codestyle 2025-08-31 09:44:20 +07:00
Oleksiy
ef86570b24 removed an extra logger call 2025-08-31 01:36:03 +00:00
Oleksiy Nedobiychuk
821c262a93 remove an extra logger 2025-08-31 03:28:18 +02:00
Oleksiy Nedobiychuk
1c323675d1 fix freezing because of ddcutil 2025-08-31 03:16:42 +02:00
LemmyCook
2c9e675ba4 Volume/bar: removed wrong comment 2025-08-30 21:10:54 -04:00
LemmyCook
2d5bebb969 Volume/bar: Right click opens pwvucontrol. 2025-08-30 21:08:32 -04:00
LemmyCook
a97913fd63 MediaMini: added RMB/MMB to control Next/Previous media/song 2025-08-30 15:59:26 -04:00
JPratama7
9264306a36 refactor: remove debug 2025-08-31 00:22:35 +07:00
Jose Chasey Pratama
d2ac174427 Merge branch 'noctalia-dev:main' into local 2025-08-31 00:20:29 +07:00
JPratama7
d5a8a0d72f refactor: add to registry 2025-08-31 00:20:13 +07:00
JPratama7
36bfbe10ab feat: taskbar 2025-08-31 00:20:01 +07:00
LemmyCook
7ace02dd46 BTService: add percent symbol (%) after battery level 2025-08-30 12:44:30 -04:00
LemmyCook
125a3ace08 Wallpaper: made the selection more responsive to clicks + code cleanup 2025-08-30 12:19:38 -04:00
LemmyCook
3c7d03ada9 Wallpaper: added a bash script to compile all shaders
+ code cleanup
2025-08-30 11:22:09 -04:00
LemmyCook
477d38d928 Wallpaper: shaders improvements with more parameters and new Stripes shader 2025-08-30 10:43:33 -04:00
LemmyCook
d36bcb1d4d Autoformatting 2025-08-30 07:58:30 -04:00
Lysec
4c79999a65 Merge pull request #167 from MarkusVolk/main
Add matugen templates for foot an fuzzel
2025-08-30 04:02:51 +02:00
Ly-sec
cdfed0fe94 Replace pkexec with terminal output (with TERMINAL environment var)
ArchUpdater:use terminal thanks to `TERMINAL` environment variable
README: Add explanation for said environment var
2025-08-30 03:57:59 +02:00
LemmyCook
6af915983c Wallpaper: flush nextWallpaper.source when no longer needed in a attempt to save ram 2025-08-29 21:52:16 -04:00
LemmyCook
da266792df Wallpaper: less login 2025-08-29 21:40:43 -04:00
LemmyCook
91afdf7f13 Wallpaper: added disc transition 2025-08-29 21:40:20 -04:00
LemmyCook
26fc6098dc Wallpaper: added random transition + fixed "none" transition 2025-08-29 21:19:17 -04:00
LemmyCook
3496169c68 Revert "Remove need for polkit, launch any ArchUpdater update through terminal"
This reverts commit 299add4a15.
2025-08-29 20:50:28 -04:00
Ly-sec
299add4a15 Remove need for polkit, launch any ArchUpdater update through terminal
ArchUpdater: rely on `TERMINAL` environment variable
README: Add explanation for the `TERMINAL` environment variable
2025-08-30 02:28:48 +02:00
LemmyCook
5ab76c98e5 wallpaper: renamed Swipe => Wipe 2025-08-29 19:10:16 -04:00
LemmyCook
f5b4984295 Wallpaper: swipe left/right/up/down 2025-08-29 19:06:01 -04:00
Lemmy
a38665fa0d Update README.md - screenshots served by github repo 2025-08-29 17:12:19 -04:00
LemmyCook
cf27ff10c0 Github: Added GitHub screenshots 2025-08-29 17:05:01 -04:00
LemmyCook
8f3f520ef4 Merge branch 'advanced-wallpaper' 2025-08-29 17:02:17 -04:00
LemmyCook
c4e4f78336 Wallpaper/Matugen: Matugen always based on the primary screen wallpaper 2025-08-29 17:00:58 -04:00
LemmyCook
2f9eb28596 Wallpaper: On startup set wallpaper without transition 2025-08-29 16:53:43 -04:00
LemmyCook
63e90a5c17 Wallpaper: cool fade in transition via shader 2025-08-29 16:26:48 -04:00
LemmyCook
61d13a6cab Wallpaper: minor fixes for random wallpaper picking 2025-08-29 15:21:10 -04:00
LemmyCook
a2ecc67643 Wallpaper: less intrusive UI when using per monitor directories 2025-08-29 14:57:03 -04:00
LemmyCook
f679999453 Wallpaper: fixed random wallpaper 2025-08-29 14:44:20 -04:00
LemmyCook
5b8d7dbff5 Wallpaper: fixed all edge cases when toggling on/off multi directories support and invalid directory names 2025-08-29 14:38:27 -04:00
LemmyCook
9bbdf5f6f6 Wallpaper: real support for differents folders per monitor \o/ 2025-08-29 14:09:05 -04:00
LemmyCook
812ddf2ebb WallpaperSelector: syntax fix 2025-08-29 13:11:31 -04:00
LemmyCook
db3ea7ed73 Wallpaper: cleanup 2025-08-29 13:04:11 -04:00
LemmyCook
c37ef867a1 Wallpaper: delay service initialization until settings are ready 2025-08-29 12:41:37 -04:00
LemmyCook
7c6c908076 Logger: new callStack() method 2025-08-29 12:40:19 -04:00
Markus Volk
c510afdc28 Add fuzzel matugen template
Signed-off-by: Markus Volk <f_l_k@t-online.de>
2025-08-29 17:57:47 +02:00
LemmyCook
861e207fb6 Wip! 2025-08-29 09:55:47 -04:00
Markus Volk
c601e45436 Add foot matugen template
Signed-off-by: Markus Volk <f_l_k@t-online.de>
2025-08-29 15:41:23 +02:00
LemmyCook
e79c163dd9 Wallpaper rework
- removed swww to the code is easier to maintain
- basic multi monitor wallpaper support
2025-08-29 08:33:40 -04:00
Lysec
c770b97649 Merge pull request #161 from MichaelThomas0721/main
Added ghostty matugen template
2025-08-29 08:20:43 +02:00
Oleksiy Nedobiychuk
5dedf5c1b5 brightness: avoid DDC on internal panels, add timeouts, auto-blacklist bad DDC buses
Signed-off-by: Oleksiy Nedobiychuk <oleksiy12345@live.it>
2025-08-29 00:43:52 +02:00
Michael Thomas
85bd0ed2f8 Merge branch 'noctalia-dev:main' into main 2025-08-28 16:45:50 -04:00
MichaelThomas0721
cd6a183c28 Added ghostty matugen template 2025-08-28 16:39:56 -04:00
LemmyCook
3cc8c8fb03 ArchUpdater: improved the look 2025-08-28 15:55:03 -04:00
LemmyCook
42408572ab Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-28 15:01:45 -04:00
LemmyCook
c3956c5894 Bluetooth: revamped a lot of code 2025-08-28 15:01:43 -04:00
LemmyCook
b2e9058a2f Auto-formatting 2025-08-28 15:01:23 -04:00
Lemmy
bc28b11763 Update README.md 2025-08-28 14:33:03 -04:00
Ly-sec
cbd71bec49 Fix ArchUpdater NCheckbox binding
ArchUpdater: Create proper binding, make selective update more robust
2025-08-28 19:48:20 +02:00
Ly-sec
6ac172fe02 ArchUpdaterPanel: Fix typo 2025-08-28 19:17:50 +02:00
LemmyCook
8ebcfa4bc6 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-28 12:22:43 -04:00
LemmyCook
3f4cec1719 NTextInput: improved layout and adapted calling code all over the shell. 2025-08-28 12:22:42 -04:00
Ly-sec
156146fd9a Add audio IPC options
AudioService: add a few functions to AudioService
IPCManager: Add 4 Audio IPC calls
README: Add information about new IPC calls
2025-08-28 17:48:02 +02:00
LemmyCook
e86e7344f3 ArchUpdater: better icons (take2) 2025-08-28 11:10:55 -04:00
Ly-sec
8d9f206c45 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-28 17:08:01 +02:00
Ly-sec
39d8d8bcfa Added a check to see if wlsunset is enabled, if it isn't you can change
the NightLight settings.
DisplayTab: add wlsunsetCheck process
2025-08-28 17:06:53 +02:00
LemmyCook
a719db4d0d better comments 2025-08-28 11:06:31 -04:00
Ly-sec
a845067cf0 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-28 17:04:34 +02:00
Ly-sec
82d71d65fa Replaced the old checkboxes in ArchUpdaterPanel with NCheckbox
ArchUpdater: use NCheckbox to make things more uniform
2025-08-28 17:03:28 +02:00
Lysec
a699cfb958 Merge pull request #162 from wer-zen/main
Another Readme Fix
2025-08-28 17:01:55 +02:00
wer-zen
92b24c6eb2 readme_fix4 2025-08-28 16:59:36 +02:00
wer-zen
d57092feae readme_fix3 2025-08-28 16:54:08 +02:00
wer-zen
6c4b495a75 readme_fix3 2025-08-28 16:53:59 +02:00
Ly-sec
d0b7ccf302 Autoformat 2025-08-28 15:35:52 +02:00
Ly-sec
e237bd04ff Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-28 15:35:37 +02:00
Ly-sec
2a686b55c4 Replace our NightLight solution with wlsunset.
NightLight: add temperature solution
NTextInput: add input hint support
2025-08-28 15:34:47 +02:00
LemmyCook
cdc3b18071 ArchUpdater: better icons 2025-08-28 08:58:24 -04:00
quadbyte
c8860a3a9d Volume/Bar: better touchpad support for volume inc/dec 2025-08-28 08:37:44 -04:00
Ly-sec
57a67bf4df Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-28 14:26:29 +02:00
Ly-sec
f932d580af NCheckbox: add scaling 2025-08-28 14:26:13 +02:00
LemmyCook
eadcb3f22b ColorSchemeTab: better presentation 2025-08-28 08:25:43 -04:00
LemmyCook
a6d722f9a9 LocationService + Settings: improved service stability and show geocoding results in the settings 2025-08-28 08:20:17 -04:00
Ly-sec
f10280c8bb Added NCheckbox and used it for Matugen templates
NCheckbox: Added
ColorSchemeTab: replace NToggle with NCheckbox
2025-08-28 14:00:29 +02:00
Ly-sec
a6848be4c2 Create MatugenService, add toggles per template
Matugen: Created Matugen.qml for users to add templates to, add
MatugenService to generate .toml
Notification: possible fix for children null warning
Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell
2025-08-28 13:33:24 +02:00
Ly-sec
f510c1922d Create separate matugen toggles, add MatugenService
Matugen: add Matugen.qml as central place for templates, add
MatugenService to take care of .toml generation
Notification: possible fix for "children of null"
2025-08-28 13:27:49 +02:00
LemmyCook
0562dbbbf9 Settings: more cleanup and conditionnal controls (NightLight)
+ Auto formatting
2025-08-28 06:57:37 -04:00
Ly-sec
85b92d9c6f Added a check if there is any notifications.
Notification: add notificationModel.count check to possibly prevent
unwanted behaviour
2025-08-28 10:51:18 +02:00
Lysec
de465ebcba Merge pull request #158 from MichaelThomas0721/main
Added kitty matugen template
2025-08-28 10:34:40 +02:00
LemmyCook
8302285388 Settings: large cleanup and factorization. Should look much better. 2025-08-27 20:39:50 -04:00
MichaelThomas0721
b502161b11 Added kitty matugen template 2025-08-27 19:22:16 -04:00
LemmyCook
1206be34dc MediaMini: fixed fallback icon 2025-08-27 18:58:21 -04:00
LemmyCook
c6cf5a0fab Bar UI improvements
- better rounding at low scaling, for accurate vertical centering
- use fixed font bar system monitor
- use bold for workspaces name
2025-08-27 14:46:19 -04:00
LemmyCook
d6df496216 ArchUpdater: fixes (part2) 2025-08-27 14:42:15 -04:00
LemmyCook
68874e8680 ArchUpdater: fixes
- replaced all Text by NText for a more streamlined code
- clicking on the icon in the bar should always open the panel even if
there is nothing to update
2025-08-27 14:41:39 -04:00
LemmyCook
67f0c482b3 ActiveWindow+MediaMini: minor adjustments to width and spacing.
- also removed double fallback icon for media mini when no artwork
available
2025-08-27 10:23:43 -04:00
LemmyCook
1ceec16102 MediaCard: AudioVisualizer should not be dependend on tracklength (ex: Twitch) 2025-08-27 10:11:34 -04:00
LemmyCook
9d03006aaa autoformatting 2025-08-27 09:07:36 -04:00
Ly-sec
f7f21f9716 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-27 14:54:12 +02:00
Ly-sec
124d9becc6 Add animation speed slider in general tab, always collapse activeWindow
GeneralTab: add animation speed slider
Workspace: set activeWindow to always collapsed except for hover
Misc: replaced a lot of animations with Style.animationXYZ
2025-08-27 14:52:50 +02:00
LemmyCook
50777ef32f MediaPlayer/sidepanel: slightly less thick time slider 2025-08-27 08:51:43 -04:00
Ly-sec
563a151277 Possible fix for MediaCard slider
MediaCard: use proper seek binding
MediaService: add seek binding
autoformat
2025-08-27 14:21:42 +02:00
Ly-sec
6f7528c87a Added issue templates and fixed screenRecorder status symbol
ScreenRecorder: add proper checks for screenRecorder
ISSUE_TEMPLATE: add bug_report and feature_request
2025-08-27 13:21:53 +02:00
Ly-sec
ae0228dc25 Wallpaper: change random wallpaper delay options 2025-08-27 09:54:46 +02:00
Ly-sec
a1f87c50bc ArchUpdater: add AUR support 2025-08-27 09:28:58 +02:00
Ly-sec
74e65d75cb README: add noctalia-shell-git AUR info 2025-08-27 08:47:28 +02:00
Ly-sec
56967d4c0c Compositor: Fix Hyprland activeWindow icon 2025-08-27 08:23:45 +02:00
Ly-sec
2950862e34 README: update Usage section 2025-08-27 08:14:34 +02:00
LemmyCook
f4ecdd5af3 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-26 20:55:47 -04:00
LemmyCook
dd456edf90 Workspace: ShowLabel replaced toggle by NComboBox so we can choose "Name" or "Index" 2025-08-26 20:55:45 -04:00
Lemmy
e1f1addb35 Merge pull request #152 from Drazzy9295/main
small flake.nix fix - providing a proper 'default' and fixing formatter errors
2025-08-26 18:58:20 -04:00
LemmyCook
94e59592f0 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-26 18:49:10 -04:00
LemmyCook
4cd94f0426 NightLight: refactored the code to make simpler
- using intensity instead of warmth
- animated color transition
- removed unecessary bindings and double properties
- using better icons to avoid confusion with brightness
- polished settings UI
2025-08-26 18:48:10 -04:00
Drazzy9295
c99f470e34 small flake.nix fix 2025-08-26 22:27:51 +01:00
Ly-sec
45b0fbeb4a Release: v2.3.0
- add NightLight
- better positioning of panels below their widgets
- quality of life changes/fixes
2025-08-26 20:42:01 +02:00
LemmyCook
76f0368a64 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-26 14:41:29 -04:00
LemmyCook
da80f47921 MediaCard: less thick ouline 2025-08-26 14:41:27 -04:00
Ly-sec
fa7c19d5de README: add small blockquote to optional dependencies 2025-08-26 20:31:59 +02:00
Lysec
74f54b3d76 Merge pull request #148 from kevindiaz314/main
docs: Update README with AUR and Nix instructions
2025-08-26 20:27:10 +02:00
LemmyCook
2f49643e51 NIconButton + NPill: improved vertical centering 2025-08-26 14:25:36 -04:00
LemmyCook
fabdf67da7 Workspace: use font metrics for vertical centering
Another attempt xD
2025-08-26 14:18:38 -04:00
LemmyCook
fc71f61000 Workspace: another attempt at proper text centering 2025-08-26 14:09:54 -04:00
Ly-sec
aa8a72a9d8 Fix named workspace text positioning
Workspace.qml: add slight centerOffset
2025-08-26 20:05:45 +02:00
LemmyCook
a1dcaa2683 Workspace: attempt to fix tiny vertical offset 2025-08-26 13:53:27 -04:00
LemmyCook
f533e2a547 Workspace: bigger text for names, adaptative height. 2025-08-26 13:45:53 -04:00
Kevin Diaz
496ca05b9d Update README.md to move optional packages to required 2025-08-26 13:41:50 -04:00
Kevin Diaz
f399463a99 Update installation instructions for Arch Linux and Nix in README.md 2025-08-26 13:41:50 -04:00
LemmyCook
44c98553dc Workspace: slimmer look 2025-08-26 13:40:35 -04:00
Lysec
57a8f5f0b3 Update README.md 2025-08-26 18:38:47 +02:00
LemmyCook
ab7c5678c6 Workspace: respect the setting 2025-08-26 12:37:46 -04:00
Ly-sec
c323985f03 Small workspace indicator fix 2025-08-26 18:33:28 +02:00
LemmyCook
024f35496c Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-26 12:21:58 -04:00
Lysec
4d91096ab9 Update README.md 2025-08-26 18:21:53 +02:00
LemmyCook
8148c0fa29 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-26 12:21:49 -04:00
LemmyCook
620b3e3abc Named workspaces improvements
- renamed settings to showWorkspacesNames (plural)
- improved overall look and readability
2025-08-26 12:21:48 -04:00
LemmyCook
22af8e91cc Autoformatting 2025-08-26 12:20:27 -04:00
Ly-sec
9dcefa4357 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-08-26 18:19:41 +02:00
Ly-sec
634d78456d Add NightLight, update README, format 2025-08-26 18:19:35 +02:00
Lemmy
8140ddc2ff Merge pull request #136 from MichaelThomas0721/main
Added setting for workspace names.
Thanks for your contribution.  I'll most likely rework it a tiny bit to make it more aesthetic later today.
2025-08-26 12:04:01 -04:00
LemmyCook
71cfbc8c0a Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-08-26 10:26:04 -04:00
LemmyCook
6a9dee38ef NPanel fixes 2025-08-26 10:26:02 -04:00
Ly-sec
9ee31e3a6a Add IPC for screenRecorder toggle 2025-08-26 15:48:41 +02:00
Lysec
7379bcb5b6 Merge pull request #147 from wer-zen/main
Nix Part for README
2025-08-26 15:38:40 +02:00
wer-zen
246c475dbe Added Usage for binary and non 2025-08-26 15:36:11 +02:00
wer-zen
864631f967 Fixed qs ipc call stuf 2025-08-26 15:31:02 +02:00
wer-zen
4fe5681917 README.md 2025-08-26 15:13:37 +02:00
wer-zen
fe8e5a0464 README.md 2025-08-26 15:12:42 +02:00
wer-zen
7c914b88a3 README.md 2025-08-26 15:09:19 +02:00
wer-zen
256cd06e5c README.md 2025-08-26 15:08:11 +02:00
LemmyCook
f3ae0101d7 NPanel now can properly be positioned relative to their opener (button) 2025-08-26 08:55:29 -04:00
wer-zen
52efc632f8 README.md 2025-08-26 14:34:01 +02:00
Ly-sec
f3f0f611cb Add AppLauncher opacity, topCenter & bottomCenter 2025-08-26 14:31:59 +02:00
zen
07fcd29842 Update README.md 2025-08-26 14:25:35 +02:00
wer-zen
863107670c README.md 2025-08-26 14:23:02 +02:00
wer-zen
c2ca05b117 flake.nix update 2025-08-26 13:51:12 +02:00
Ly-sec
3c39ea192b Format 2025-08-26 13:12:01 +02:00
Ly-sec
1533b2d3a1 Add MPRIS blacklist 2025-08-26 13:11:49 +02:00
Ly-sec
7bcb227d7b Remove kitty template for now 2025-08-26 12:36:43 +02:00
wer-zen
178ad2ac8a flake.nix update 2025-08-26 12:12:53 +02:00
quadbyte
bdd981e15c Bar Brightness: fixed onClicked to open brightness settings 2025-08-25 23:18:13 -04:00
LemmyCook
a61526543d Settings / Display-Scaling tab: improved display at low scaling + fixed refresh button look 2025-08-25 22:48:18 -04:00
LemmyCook
1ab3463e6d Widgets: renamed SizeMultiplier => SizeRatio. Enforced read-only on size property 2025-08-25 22:43:02 -04:00
LemmyCook
269b2765cd More optims and renaming 2025-08-25 22:17:13 -04:00
LemmyCook
d2563db5a0 OPtimization: Notification History only loaded when necessary 2025-08-25 21:54:03 -04:00
LemmyCook
fcedb65119 Optimization: Dock get loaded only on assigned screens instead of being invisble. 2025-08-25 21:42:27 -04:00
LemmyCook
18b79913bd Settings / Brightness: removed non existing setting/toggle since we moved to modular bar 2025-08-25 21:40:00 -04:00
LemmyCook
9fb4aff635 Optimizations memory/cpu
- Only load bar widgets once the settings are done loading, and the
widget is actually in use.
- Only load bar on screens that request it, instead of hiding it.
2025-08-25 21:18:49 -04:00
LemmyCook
38efdc8f36 Settings: dont add ArchUpdater to the bar by default. 2025-08-25 19:03:21 -04:00
LemmyCook
75700e3309 Avoid one extra Loader per bar widget 2025-08-25 18:33:44 -04:00
LemmyCook
48e57a2122 autoformating 2025-08-25 18:33:25 -04:00
Lysec
d791705afa Merge pull request #129 from ThatOneCalculator/fix/heuristic-lookup
fix: use heuristicLookup for desktop entries if available
2025-08-25 23:31:24 +02:00
Kainoa Kanter
38e3d9909f Merge branch 'noctalia-dev:main' into fix/heuristic-lookup 2025-08-25 14:26:35 -07:00
LemmyCook
2a234f5a88 Bar Clock: Improved current date display. 2025-08-25 15:39:29 -04:00
LemmyCook
5f00266df7 WifiPanel: Improved look and functionalities 2025-08-25 15:28:13 -04:00
MichaelThomas0721
c917d7dccb Added setting for workspace names 2025-08-25 13:47:54 -04:00
LemmyCook
54c39ab8a3 TrayMenu: fixed bottom menu margin 2025-08-25 13:47:02 -04:00
LemmyCook
b19fb316d9 ArchUpdater: fixed CPU hogging 2025-08-25 13:44:23 -04:00
LemmyCook
7a849806fb Minor cleanup 2025-08-25 08:28:27 -04:00
Ly-sec
01ccb771e6 Possible fix for battery % notification 2025-08-25 13:13:56 +02:00
Ly-sec
c6683712a4 Add notification when battery is low, fix some warnings 2025-08-25 12:46:55 +02:00
Ly-sec
d2b202c25f Fully comment ArchUpdaterService 2025-08-25 10:33:48 +02:00
Ly-sec
dfbefcd9d2 Edit kitty template 2025-08-25 09:13:35 +02:00
Ly-sec
938c4b2d25 Comment WlrLayer.Overlay from Notification popup, edit Kitty template 2025-08-25 08:45:05 +02:00
Kainoa Kanter
81182aa65b Merge branch 'noctalia-dev:main' into fix/heuristic-lookup 2025-08-24 21:34:47 -07:00
Kainoa Kanter
487158739d Merge branch 'noctalia-dev:main' into fix/heuristic-lookup 2025-08-22 12:37:26 -07:00
Kainoa Kanter
7899b124b7 fix: check for correct method 2025-08-22 10:57:37 -07:00
Kainoa Kanter
e1d623be9c fix: use heuristicLookup for desktop entries if available 2025-08-22 09:04:48 -07:00
269 changed files with 45220 additions and 12479 deletions

29
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,29 @@
---
name: Bug Report
about: Report a bug from noctalia-shell
title: "[Bug] "
labels: bug
assignees: ''
---
### Description
A clear and concise description of the bug.
### Steps to Reproduce
1. Go to '...'
2. Click on '...'
3. See the error.
### Expected Behavior
Explain what you expected to happen.
### Screenshots
Add screenshots if applicable.
### Environment
- Distro: [e.g., CachyOS, NixOS, Arch, ...]
- Compositor: [ e.g., Hyprland, Niri, ...]
- Noctalia-shell Version: [e.g., 1.0.0, available in About tab]
### Additional Context
Add any other context about the problem here.

12
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
blank_issues_enabled: false
issue_templates:
- name: "Bug Report"
description: "Report a bug in the system."
title: "[Bug]: "
labels: ["bug"]
body: "./ISSUE_TEMPLATE/bug_report.md"
- name: "Feature Request"
description: "Propose a new feature or improvement."
title: "[Feature]: "
labels: ["enhancement"]
body: "./ISSUE_TEMPLATE/feature_request.md"

View File

@@ -0,0 +1,19 @@
---
name: Feature Request
about: Suggest a new feature or improvement
title: "[Feature] "
labels: enhancement
assignees: ''
---
### Feature Description
What feature would you like to see?
### Why Is This Needed?
Explain the problem or need for this feature.
### Suggested Solutions
Describe how this feature could be implemented.
### Additional Context
Add any relevant screenshots, links, or resources.

110
.github/workflows/update-aur-package.yml vendored Normal file
View File

@@ -0,0 +1,110 @@
name: Update AUR Package
on:
push:
tags:
- 'v*'
workflow_dispatch:
jobs:
aur-sync:
name: Sync PKGBUILD with release
runs-on: ubuntu-latest
container:
image: archlinux:latest
defaults:
run:
shell: bash
env:
AUR_REPO: ssh://aur@aur.archlinux.org/noctalia-shell.git
GIT_SSH_COMMAND: ssh -i /root/.ssh/id_aur -o StrictHostKeyChecking=yes -o IdentitiesOnly=yes
PKGNAME: noctalia-shell
AUR_LINK: https://aur.archlinux.org/packages/noctalia-shell
steps:
- name: Install dependencies
run: |
set -euo pipefail
pacman -Syu --noconfirm git base-devel pacman-contrib openssh
- name: Create build user
run: |
set -euo pipefail
useradd -m builduser
echo 'builduser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
- name: Configure SSH
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
run: |
set -euo pipefail
mkdir -p /root/.ssh
chmod 700 /root/.ssh
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > /root/.ssh/id_aur
chmod 600 /root/.ssh/id_aur
ssh-keyscan aur.archlinux.org >> /root/.ssh/known_hosts
chmod 600 /root/.ssh/known_hosts
- name: Determine version
id: vars
env:
TAG_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
PKGVER="${TAG_NAME#v}"
echo "pkgver=$PKGVER" >> "$GITHUB_OUTPUT"
- name: Clone AUR repository
run: |
set -euo pipefail
git clone "$AUR_REPO" "$GITHUB_WORKSPACE/aur"
- name: Update PKGBUILD
env:
PKGVER: ${{ steps.vars.outputs.pkgver }}
working-directory: ${{ github.workspace }}/aur
run: |
set -euo pipefail
sed -i "s/^pkgver=.*/pkgver=${PKGVER}/" PKGBUILD
sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD
- name: Refresh checksums and metadata
env:
AUR_DIR: ${{ github.workspace }}/aur
run: |
set -euo pipefail
chown -R builduser:builduser "$AUR_DIR"
su - builduser -c "cd $AUR_DIR && updpkgsums"
su - builduser -c "cd $AUR_DIR && makepkg --printsrcinfo > .SRCINFO"
- name: Commit and push changes
env:
PKGVER: ${{ steps.vars.outputs.pkgver }}
working-directory: ${{ github.workspace }}/aur
run: |
set -euo pipefail
git config --global --add safe.directory "$PWD"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if [[ -n "$(git status --porcelain)" ]]; then
git add PKGBUILD .SRCINFO
git commit -m "chore(package): release ${PKGVER}"
git push origin HEAD
else
echo "No updates necessary."
fi
- name: Summarize update
env:
PKGNAME: noctalia-shell
PKGVER: ${{ steps.vars.outputs.pkgver }}
AUR_LINK: https://aur.archlinux.org/packages/noctalia-shell
run: |
set -euo pipefail
{
echo "## AUR Update"
echo ""
echo "- Package: ${PKGNAME}"
echo "- Updated version: ${PKGVER}"
echo "- AUR page: ${AUR_LINK}"
} >> "$GITHUB_STEP_SUMMARY"

0
.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,34 @@
{
"dark": {
"mPrimary": "#E6B450",
"mOnPrimary": "#0B0E14",
"mSecondary": "#AAD94C",
"mOnSecondary": "#0B0E14",
"mTertiary": "#39BAE6",
"mOnTertiary": "#0B0E14",
"mError": "#D95757",
"mOnError": "#0B0E14",
"mSurface": "#1e222a",
"mOnSurface": "#BFBDB6",
"mSurfaceVariant": "#0B0E14",
"mOnSurfaceVariant": "#636A72",
"mOutline": "#565B66",
"mShadow": "#000000"
},
"light": {
"mPrimary": "#FF8F40",
"mOnPrimary": "#F8F9FA",
"mSecondary": "#86B300",
"mOnSecondary": "#F8F9FA",
"mTertiary": "#55B4D4",
"mOnTertiary": "#F8F9FA",
"mError": "#E65050",
"mOnError": "#F8F9FA",
"mSurface": "#E4E6E9",
"mOnSurface": "#5C6166",
"mSurfaceVariant": "#F8F9FA",
"mOnSurfaceVariant": "#ABADB1",
"mOutline": "#8A9199",
"mShadow": "#F8F9FA"
}
}

View File

@@ -4,7 +4,7 @@
"mOnPrimary": "#11111b",
"mSecondary": "#fab387",
"mOnSecondary": "#11111b",
"mTertiary": "#a6e3a1",
"mTertiary": "#94e2d5",
"mOnTertiary": "#11111b",
"mError": "#f38ba8",
"mOnError": "#11111b",
@@ -16,19 +16,19 @@
"mShadow": "#11111b"
},
"light": {
"mPrimary": "#9349ef",
"mPrimary": "#8839ef",
"mOnPrimary": "#eff1f5",
"mSecondary": "#f67525",
"mSecondary": "#fe640b",
"mOnSecondary": "#eff1f5",
"mTertiary": "#40b635",
"mTertiary": "#40a02b",
"mOnTertiary": "#eff1f5",
"mError": "#f38ba8",
"mOnError": "#11111b",
"mError": "#d20f39",
"mOnError": "#dce0e8",
"mSurface": "#eff1f5",
"mOnSurface": "#4c4f69",
"mSurfaceVariant": "#ccd0da",
"mOnSurfaceVariant": "#6c6f85",
"mOutline": "#aeb5c4",
"mOutline": "#a5adcb",
"mShadow": "#dce0e8"
}
}

View File

@@ -0,0 +1,34 @@
{
"dark": {
"mPrimary": "#D3C6AA",
"mOnPrimary": "#232A2E",
"mSecondary": "#D3C6AA",
"mOnSecondary": "#232A2E",
"mTertiary": "#9DA9A0",
"mOnTertiary": "#232A2E",
"mError": "#E67E80",
"mOnError": "#232A2E",
"mSurface": "#232A2E",
"mOnSurface": "#859289",
"mSurfaceVariant": "#2D353B",
"mOnSurfaceVariant": "#D3C6AA",
"mOutline": "#D3C6AA",
"mShadow": "#475258"
},
"light": {
"mPrimary": "#434F55",
"mOnPrimary": "#D3C6AA",
"mSecondary": "#232a2e",
"mOnSecondary": "#D3C6AA",
"mTertiary": "#333c43",
"mOnTertiary": "#9DA9A0",
"mError": "#E66868",
"mOnError": "#9DA9A0",
"mSurface": "#BEC5B2",
"mOnSurface": "#333C43",
"mSurfaceVariant": "#9DA9A0",
"mOnSurfaceVariant": "#232A2E",
"mOutline": "#232A2E",
"mShadow": "#ECF5ED"
}
}

View File

@@ -0,0 +1,34 @@
{
"dark": {
"mPrimary": "#76946a",
"mOnPrimary": "#1f1f28",
"mSecondary": "#c0a36e",
"mOnSecondary": "#1f1f28",
"mTertiary": "#7e9cd8",
"mOnTertiary": "#1f1f28",
"mError": "#c34043",
"mOnError": "#1f1f28",
"mSurface": "#1f1f28",
"mOnSurface": "#717c7c",
"mSurfaceVariant": "#2a2a37",
"mOnSurfaceVariant": "#c8c093",
"mOutline": "#363646",
"mShadow": "#1f1f28"
},
"light": {
"mPrimary": "#6f894e",
"mOnPrimary": "#f2ecbc",
"mSecondary": "#77713f",
"mOnSecondary": "#f2ecbc",
"mTertiary": "#4d699b",
"mOnTertiary": "#f2ecbc",
"mError": "#c84053",
"mOnError": "#f2ecbc",
"mSurface": "#f2ecbc",
"mOnSurface": "#8a8980",
"mSurfaceVariant": "#e5ddb0",
"mOnSurfaceVariant": "#545464",
"mOutline": "#cfc49c",
"mShadow": "#f2ecbc"
}
}

View File

@@ -0,0 +1,34 @@
{
"dark": {
"mPrimary": "#aaaaaa",
"mOnPrimary": "#111111",
"mSecondary": "#a7a7a7",
"mOnSecondary": "#111111",
"mTertiary": "#cccccc",
"mOnTertiary": "#111111",
"mError": "#dddddd",
"mOnError": "#111111",
"mSurface": "#111111",
"mOnSurface": "#828282",
"mSurfaceVariant": "#191919",
"mOnSurfaceVariant": "#5d5d5d",
"mOutline": "#3c3c3c",
"mShadow": "#000000"
},
"light": {
"mPrimary": "#555555",
"mOnPrimary": "#eeeeee",
"mSecondary": "#505058",
"mOnSecondary": "#eeeeee",
"mTertiary": "#333333",
"mOnTertiary": "#eeeeee",
"mError": "#222222",
"mOnError": "#efefef",
"mSurface": "#d4d4d4",
"mOnSurface": "#696969",
"mSurfaceVariant": "#e8e8e8",
"mOnSurfaceVariant": "#9e9e9e",
"mOutline": "#c3c3c3",
"mShadow": "#fafafa"
}
}

View File

@@ -1,35 +1,34 @@
{
"dark": {
"mPrimary": "#c7a1d8",
"mOnPrimary": "#1a151f",
"mSecondary": "#a984c4",
"mOnSecondary": "#f3edf7",
"mTertiary": "#e0b7c9",
"mOnTertiary": "#20161f",
"mError": "#e9899d",
"mOnError": "#1e1418",
"mSurface": "#1c1822",
"mOnSurface": "#e9e4f0",
"mSurfaceVariant": "#262130",
"mOnSurfaceVariant": "#a79ab0",
"mOutline": "#3e364e",
"mShadow": "#120f18"
"mPrimary": "#fff59b",
"mOnPrimary": "#0e0e43",
"mSecondary": "#a9aefe",
"mOnSecondary": "#0e0e43",
"mTertiary": "#9BFECE",
"mOnTertiary": "#0e0e43",
"mError": "#FD4663",
"mOnError": "#0e0e43",
"mSurface": "#070722",
"mOnSurface": "#f3edf7",
"mSurfaceVariant": "#11112d",
"mOnSurfaceVariant": "#7c80b4",
"mOutline": "#21215F",
"mShadow": "#070722"
},
"light": {
"mPrimary": "#9b59ba",
"mOnPrimary": "#ffffff",
"mSecondary": "#784999",
"mOnSecondary": "#ffffff",
"mTertiary": "#c17093",
"mOnTertiary": "#ffffff",
"mError": "#e9899d",
"mOnError": "#1e1418",
"mSurface": "#f5f1fa",
"mOnSurface": "#1c1822",
"mSurfaceVariant": "#e7dfee",
"mOnSurfaceVariant": "#4a3d59",
"mOutline": "#cebedc",
"mShadow": "#ffffff"
"mPrimary": "#5d65f5",
"mOnPrimary": "#dadcff",
"mSecondary": "#8E93D8",
"mOnSecondary": "#dadcff",
"mTertiary": "#0e0e43",
"mOnTertiary": "#fef29a",
"mError": "#FD4663",
"mOnError": "#0e0e43",
"mSurface": "#e6e8fa",
"mOnSurface": "#4b55c8",
"mSurfaceVariant": "#eff0ff",
"mOnSurfaceVariant": "#0e0e43",
"mOutline": "#8288fc",
"mShadow": "#f3edf7"
}
}

View File

@@ -0,0 +1,34 @@
{
"dark": {
"mPrimary": "#c7a1d8",
"mOnPrimary": "#1a151f",
"mSecondary": "#a984c4",
"mOnSecondary": "#f3edf7",
"mTertiary": "#e0b7c9",
"mOnTertiary": "#20161f",
"mError": "#e9899d",
"mOnError": "#1e1418",
"mSurface": "#1c1822",
"mOnSurface": "#e9e4f0",
"mSurfaceVariant": "#262130",
"mOnSurfaceVariant": "#a79ab0",
"mOutline": "#3e364e",
"mShadow": "#120f18"
},
"light": {
"mPrimary": "#9b59ba",
"mOnPrimary": "#ffffff",
"mSecondary": "#784999",
"mOnSecondary": "#ffffff",
"mTertiary": "#c17093",
"mOnTertiary": "#ffffff",
"mError": "#e9899d",
"mOnError": "#1e1418",
"mSurface": "#f5f1fa",
"mOnSurface": "#1c1822",
"mSurfaceVariant": "#e7dfee",
"mOnSurfaceVariant": "#4a3d59",
"mOutline": "#cebedc",
"mShadow": "#ffffff"
}
}

View File

@@ -1,34 +1,34 @@
{
"dark": {
"mPrimary": "#ebbcba",
"mOnPrimary": "#1f1d2e",
"mOnPrimary": "#191724",
"mSecondary": "#9ccfd8",
"mOnSecondary": "#1f1d2e",
"mTertiary": "#f6c177",
"mOnTertiary": "#1f1d2e",
"mOnSecondary": "#191724",
"mTertiary": "#524f67",
"mOnTertiary": "#e0def4",
"mError": "#eb6f92",
"mOnError": "#1f1d2e",
"mSurface": "#1f1d2e",
"mOnError": "#191724",
"mSurface": "#191724",
"mOnSurface": "#e0def4",
"mSurfaceVariant": "#26233a",
"mOnSurfaceVariant": "#908caa",
"mOutline": "#403d52",
"mShadow": "#1f1d2e"
"mShadow": "#191724"
},
"light": {
"mPrimary": "#d46e6b",
"mPrimary": "#d7827e",
"mOnPrimary": "#faf4ed",
"mSecondary": "#56949f",
"mOnSecondary": "#faf4ed",
"mTertiary": "#31748f",
"mOnTertiary": "#232136",
"mTertiary": "#cecacd",
"mOnTertiary": "#575279",
"mError": "#b4637a",
"mOnError": "#f2e9e1",
"mSurface": "#e0def4",
"mOnSurface": "#232136",
"mSurfaceVariant": "#bcb8e7",
"mOnError": "#faf4ed",
"mSurface": "#faf4ed",
"mOnSurface": "#575279",
"mSurfaceVariant": "#f2e9e1",
"mOnSurfaceVariant": "#797593",
"mOutline": "#9893a5",
"mShadow": "#575279"
"mOutline": "#dfdad9",
"mShadow": "#faf4ed"
}
}

View File

@@ -1,34 +1,34 @@
{
"dark": {
"mPrimary": "#ff9e64",
"mOnPrimary": "#1a1b26",
"mSecondary": "#e0af68",
"mOnSecondary": "#1a1b26",
"mTertiary": "#7aa2f7",
"mOnTertiary": "#1a1b26",
"mPrimary": "#7aa2f7",
"mOnPrimary": "#16161e",
"mSecondary": "#bb9af7",
"mOnSecondary": "#16161e",
"mTertiary": "#9ece6a",
"mOnTertiary": "#16161e",
"mError": "#f7768e",
"mOnError": "#1a1b26",
"mOnError": "#16161e",
"mSurface": "#1a1b26",
"mOnSurface": "#a9b1d6",
"mSurfaceVariant": "#292e42",
"mOnSurfaceVariant": "#787c99",
"mOutline": "#3d4462",
"mShadow": "#1a1b26"
"mOnSurface": "#c0caf5",
"mSurfaceVariant": "#24283b",
"mOnSurfaceVariant": "#9aa5ce",
"mOutline": "#565f89",
"mShadow": "#15161e"
},
"light": {
"mPrimary": "#fd5d00",
"mOnPrimary": "#e6e7ed",
"mSecondary": "#bb8027",
"mOnSecondary": "#e6e7ed",
"mTertiary": "#4a80f4",
"mOnTertiary": "#e6e7ed",
"mError": "#965027",
"mOnError": "#e6e7ed",
"mSurface": "#e6e7ed",
"mOnSurface": "#343b58",
"mSurfaceVariant": "#d5d6db",
"mOnSurfaceVariant": "#40434f",
"mOutline": "#babbc3",
"mShadow": "#c0caf5"
"mPrimary": "#2e7de9",
"mOnPrimary": "#e1e2e7",
"mSecondary": "#9854f1",
"mOnSecondary": "#e1e2e7",
"mTertiary": "#587539",
"mOnTertiary": "#e1e2e7",
"mError": "#f52a65",
"mOnError": "#e1e2e7",
"mSurface": "#e1e2e7",
"mOnSurface": "#3760bf",
"mSurfaceVariant": "#d0d5e3",
"mOnSurfaceVariant": "#6172b0",
"mOutline": "#b4b5b9",
"mShadow": "#a8aecb"
}
}

View File

@@ -0,0 +1,16 @@
Tabler Licenses - Detailed Usage Rights and Guidelines
This is a legal agreement between you, the Purchaser, and Tabler. Purchasing or downloading of any Tabler product (Tabler Admin Template, Tabler Icons, Tabler Emails, Tabler Illustrations), constitutes your acceptance of the terms of this license, Tabler terms of service and Tabler private policy.
Tabler Admin Template and Tabler Icons License*
Tabler Admin Template and Tabler Icons are available under MIT License.
Copyright (c) 2018-2025 Tabler
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
See more at Tabler Admin Template MIT License See more at Tabler Icons MIT License

Binary file not shown.

View File

@@ -0,0 +1,84 @@
pragma Singleton
import QtQuick
import Quickshell
import qs.Commons
// Central place to define which templates we generate and where they write.
// Users can extend it by dropping additional templates into:
// - Assets/Matugen/templates/
// - ~/.config/matugen/ (when enableUserTemplates is true)
Singleton {
id: root
// Build the base TOML using current settings
function buildConfigToml() {
var lines = []
lines.push("[config]")
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
// Always include noctalia colors output for the shell
lines.push("[templates.noctalia]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/noctalia.json"')
lines.push('output_path = "' + Settings.configDir + 'colors.json"')
if (Settings.data.matugen.gtk4) {
lines.push("\n[templates.gtk4]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/gtk4.css"')
lines.push('output_path = "~/.config/gtk-4.0/gtk.css"')
lines.push("post_hook = 'gsettings set org.gnome.desktop.interface color-scheme prefer-" + mode + "'")
}
if (Settings.data.matugen.gtk3) {
lines.push("\n[templates.gtk3]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/gtk3.css"')
lines.push('output_path = "~/.config/gtk-3.0/gtk.css"')
lines.push("post_hook = 'gsettings set org.gnome.desktop.interface color-scheme prefer-" + mode + "'")
}
if (Settings.data.matugen.qt6) {
lines.push("\n[templates.qt6]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/qtct.conf"')
lines.push('output_path = "~/.config/qt6ct/colors/noctalia.conf"')
}
if (Settings.data.matugen.qt5) {
lines.push("\n[templates.qt5]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/qtct.conf"')
lines.push('output_path = "~/.config/qt5ct/colors/noctalia.conf"')
}
if (Settings.data.matugen.kitty) {
lines.push("\n[templates.kitty]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/kitty.conf"')
lines.push('output_path = "~/.config/kitty/themes/noctalia.conf"')
lines.push("post_hook = 'kitty +kitten themes --reload-in=all noctalia'")
}
if (Settings.data.matugen.ghostty) {
lines.push("\n[templates.ghostty]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/ghostty.conf"')
lines.push('output_path = "~/.config/ghostty/themes/noctalia"')
lines.push("post_hook = \"grep -q '^theme *= *' ~/.config/ghostty/config; and sed -i 's/^theme *= *.*/theme = noctalia/' ~/.config/ghostty/config; or echo 'theme = noctalia' >> ~/.config/ghostty/config; and pkill -SIGUSR2 ghostty\"")
}
if (Settings.data.matugen.foot) {
lines.push("\n[templates.foot]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/foot.conf"')
lines.push('output_path = "~/.config/foot/themes/noctalia"')
lines.push('post_hook = "sed -i /themes/d ~/.config/foot/foot.ini && echo include=~/.config/foot/themes/noctalia >> ~/.config/foot/foot.ini"')
}
if (Settings.data.matugen.fuzzel) {
lines.push("\n[templates.fuzzel]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/fuzzel.conf"')
lines.push('output_path = "~/.config/fuzzel/themes/noctalia"')
lines.push('post_hook = "sed -i /themes/d ~/.config/fuzzel/fuzzel.ini && echo include=~/.config/fuzzel/themes/noctalia >> ~/.config/fuzzel/fuzzel.ini"')
}
if (Settings.data.matugen.vesktop) {
lines.push("\n[templates.vesktop]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/vesktop.css"')
lines.push('output_path = "~/.config/vesktop/themes/noctalia.theme.css"')
}
if (Settings.data.matugen.pywalfox) {
lines.push("\n[templates.pywalfox]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/pywalfox.json"')
lines.push('output_path = "~/.cache/wal/colors.json"')
lines.push('post_hook = "pywalfox update"')
}
return lines.join("\n") + "\n"
}
}

View File

@@ -1,6 +0,0 @@
# Base: only write Noctalia colors.json for the shell
[config]
[templates.noctalia]
input_path = "templates/noctalia.json"
output_path = "~/.config/noctalia/colors.json"

View File

@@ -1,30 +0,0 @@
# This file configures how matugen generates colors from wallpapers for Noctalia
[config]
[templates.noctalia]
input_path = "templates/noctalia.json"
output_path = "~/.config/noctalia/colors.json"
# GTK 4 (libadwaita) variables override
[templates.gtk4]
input_path = "templates/gtk4.css"
output_path = "~/.config/gtk-4.0/gtk.css"
# GTK 3 named-colors fallback for legacy apps
[templates.gtk3]
input_path = "templates/gtk3.css"
output_path = "~/.config/gtk-3.0/gtk.css"
# Qt6ct color scheme (can also be used by qt5ct in many distros)
[templates.qt6]
input_path = "templates/qtct.conf"
output_path = "~/.config/qt6ct/colors/noctalia.conf"
[templates.qt5]
input_path = "templates/qtct.conf"
output_path = "~/.config/qt5ct/colors/noctalia.conf"
[templates.kitty]
input_path = "templates/kitty.conf"
output_path = "~/.config/kitty/noctalia.conf"

View File

@@ -0,0 +1,30 @@
[colors]
background={{ colors.background.default.hex_stripped }}
foreground={{ colors.on_surface.default.hex_stripped }}
regular0={{ colors.surface.default.hex_stripped }}
regular1={{ colors.error.default.hex_stripped }}
regular2={{ colors.primary.default.hex_stripped }}
regular3={{ colors.tertiary.default.hex_stripped }}
regular4={{ colors.on_primary_container.default.hex_stripped }}
regular5={{ colors.on_secondary_container.default.hex_stripped }}
regular6={{ colors.secondary.default.hex_stripped }}
regular7={{ colors.on_surface.default.hex_stripped }}
bright0={{ colors.surface_bright.default.hex_stripped }}
bright1={{ colors.error.default.hex_stripped }}
bright2={{ colors.primary.default.hex_stripped }}
bright3={{ colors.tertiary.default.hex_stripped }}
bright4={{ colors.on_primary_container.default.hex_stripped }}
bright5={{ colors.on_secondary_container.default.hex_stripped }}
bright6={{ colors.secondary.default.hex_stripped }}
bright7={{ colors.on_surface.default.hex_stripped }}
dim0=45475A
dim1=F38BA8
dim2=A6E3A1
dim3=F9E2AF
dim4=89B4FA
dim5=F5C2E7
dim6=94E2D5
dim7=BAC2DE
selection-foreground={{ colors.primary.default.hex_stripped }}
selection-background={{ colors.on_primary.default.hex_stripped }}
cursor={{ colors.surface_variant.default.hex_stripped }} {{ colors.on_surface.default.hex_stripped }}

View File

@@ -0,0 +1,15 @@
# Fuzzel Colors
# Generated with Matugen
[colors]
background={{colors.background.default.hex_stripped}}CC
text={{colors.on_surface.default.hex_stripped}}ff
prompt={{colors.secondary.default.hex_stripped}}ff
placeholder={{colors.tertiary.default.hex_stripped}}ff
input={{colors.primary.default.hex_stripped}}ff
match={{colors.tertiary.default.hex_stripped}}ff
selection={{colors.primary.default.hex_stripped}}80
selection-text={{colors.on_surface.default.hex_stripped}}ff
selection-match={{colors.on_primary.default.hex_stripped}}ff
counter={{colors.secondary.default.hex_stripped}}ff
border={{colors.primary.default.hex_stripped}}ff

View File

@@ -0,0 +1,25 @@
palette = 0= {{colors.shadow.default.hex}}
palette = 1= {{colors.error.default.hex}}
palette = 2= {{colors.tertiary.default.hex}}
palette = 3= {{colors.secondary.default.hex}}
palette = 4= {{colors.primary.default.hex}}
palette = 5= {{colors.primary.default.hex}}
palette = 6= {{colors.secondary.default.hex}}
palette = 7= {{colors.on_background.default.hex}}
palette = 8= {{colors.outline.default.hex}}
palette = 9= {{colors.secondary_fixed_dim.default.hex}}
palette = 10= {{colors.tertiary_container.default.hex}}
palette = 11= {{colors.surface_container.default.hex}}
palette = 12= {{colors.primary_container.default.hex}}
palette = 13= {{colors.on_primary_container.default.hex}}
palette = 14= {{colors.surface_variant.default.hex}}
palette = 15= {{colors.primary.default.hex}}
foreground={{colors.on_surface.default.hex}}
background={{colors.surface.default.hex}}
cursor-color = {{colors.on_surface.default.hex}}
cursor-text = {{colors.on_surface.default.hex}}
selection-background = {{colors.on_secondary.default.hex}}
selection-foreground = {{colors.secondary_fixed_dim.default.hex}}

View File

@@ -1,40 +1,25 @@
cursor {{colors.on_surface.default.hex}}
cursor_text_color {{colors.on_surface_variant.default.hex}}
color0 {{colors.surface.default.hex}}
color1 {{colors.error.default.hex}}
color2 {{colors.tertiary.default.hex}}
color3 {{colors.secondary.default.hex}}
color4 {{colors.primary.default.hex}}
color5 {{colors.primary.default.hex}}
color6 {{colors.secondary.default.hex}}
color7 {{colors.on_background.default.hex}}
color8 {{colors.outline.default.hex}}
color9 {{colors.secondary_fixed_dim.default.hex}}
color10 {{colors.tertiary_container.default.hex}}
color11 {{colors.surface_container.default.hex}}
color12 {{colors.primary_container.default.hex}}
color13 {{colors.on_primary_container.default.hex}}
color14 {{colors.surface_variant.default.hex}}
color15 {{colors.on_background.default.hex}}
cursor {{colors.primary.default.hex}}
cursor_text_color {{colors.on_surface.default.hex}}
foreground {{colors.on_surface.default.hex}}
background {{colors.surface.default.hex}}
selection_foreground {{colors.on_secondary.default.hex}}
selection_background {{colors.secondary.default.hex}}
selection_background {{colors.secondary_fixed_dim.default.hex}}
url_color {{colors.primary.default.hex}}
# black
color0 {{colors.surface_container.default.hex}}
color8 {{colors.on_surface_variant.default.hex}}
# red
color1 {{colors.error.default.hex}}
color9 {{colors.on_error.default.hex}}
# green
color2 {{colors.tertiary.default.hex}}
color10 {{colors.on_tertiary.default.hex}}
# yellow
color3 {{colors.secondary.default.hex}}
color11 {{colors.on_secondary.default.hex}}
# blue
color4 {{colors.primary.default.hex}}
color12 {{colors.on_primary.default.hex}}
# magenta
color5 {{colors.surface_container.default.hex}}
color13 {{colors.on_surface.default.hex}}
# cyan
color6 {{colors.outline_variant.default.hex}}
color14 {{colors.on_surface_variant.default.hex}}
# white
color7 {{colors.on_surface_variant.default.hex}}
color15 {{colors.on_surface.default.hex}}

View File

@@ -0,0 +1,22 @@
{
"wallpaper": "{{image}}",
"alpha": "100",
"colors": {
"color0": "{{colors.background.default.hex}}",
"color1": "",
"color2": "",
"color3": "",
"color4": "",
"color5": "",
"color6": "",
"color7": "",
"color8": "",
"color9": "",
"color10": "{{colors.primary.default.hex}}",
"color11": "",
"color12": "",
"color13": "{{colors.surface_bright.default.hex}}",
"color14": "",
"color15": "{{colors.on_surface.default.hex}}"
}
}

View File

@@ -0,0 +1,113 @@
/**
* @name noctalia
* @description Original theme: midnight | A dark, rounded discord theme.
* @author refact0r
* @version 1.6.2
* @invite nz87hXyvcy
* @website https://github.com/refact0r/midnight-discord
* @source https://github.com/refact0r/midnight-discord/blob/master/midnight.theme.css
* @authorId 508863359777505290
* @authorLink https://www.refact0r.dev
*/
/* IMPORTANT: make sure to enable dark mode in discord settings for the theme to apply properly!!! */
@import url('https://refact0r.github.io/midnight-discord/build/midnight.css');
/* customize things here */
:root {
/* font, change to 'gg sans' for default discord font*/
--font: 'figtree';
/* top left corner text */
--corner-text: 'Midnight';
/* color of status indicators and window controls */
--online-indicator: {{colors.inverse_primary.default.hex}}; /* change to #23a55a for default green */
--dnd-indicator: {{colors.error.default.hex}}; /* change to #f13f43 for default red */
--idle-indicator: {{colors.tertiary_container.default.hex}}; /* change to #f0b232 for default yellow */
--streaming-indicator: {{colors.on_primary.default.hex}}; /* change to #593695 for default purple */
/* accent colors */
--accent-1: {{colors.tertiary.default.hex}}; /* links */
--accent-2: {{colors.primary.default.hex}}; /* general unread/mention elements, some icons when active */
--accent-3: {{colors.primary.default.hex}}; /* accent buttons */
--accent-4: {{colors.surface_bright.default.hex}}; /* accent buttons when hovered */
--accent-5: {{colors.primary_fixed_dim.default.hex}}; /* accent buttons when clicked */
--mention: {{colors.surface.default.hex}}; /* mentions & mention messages */
--mention-hover: {{colors.surface_bright.default.hex}}; /* mentions & mention messages when hovered */
/* text colors */
--text-0: {{colors.surface.default.hex}}; /* text on colored elements */
--text-1: {{colors.on_surface.default.hex}}; /* other normally white text */
--text-2: {{colors.on_surface.default.hex}}; /* headings and important text */
--text-3: {{colors.on_surface_variant.default.hex}}; /* normal text */
--text-4: {{colors.on_surface_variant.default.hex}}; /* icon buttons and channels */
--text-5: {{colors.outline.default.hex}}; /* muted channels/chats and timestamps */
/* background and dark colors */
--bg-1: {{colors.primary.default.hex}}; /* dark buttons when clicked */
--bg-2: {{colors.surface_container_high.default.hex}}; /* dark buttons */
--bg-3: {{colors.surface_container_low.default.hex}}; /* spacing, secondary elements */
--bg-4: {{colors.surface.default.hex}}; /* main background color */
--hover: {{colors.surface_bright.default.hex}}; /* channels and buttons when hovered */
--active: {{colors.surface_bright.default.hex}}; /* channels and buttons when clicked or selected */
--message-hover: {{colors.surface_bright.default.hex}}; /* messages when hovered */
/* amount of spacing and padding */
--spacing: 12px;
/* animations */
/* ALL ANIMATIONS CAN BE DISABLED WITH REDUCED MOTION IN DISCORD SETTINGS */
--list-item-transition: 0.2s ease; /* channels/members/settings hover transition */
--unread-bar-transition: 0.2s ease; /* unread bar moving into view transition */
--moon-spin-transition: 0.4s ease; /* moon icon spin */
--icon-spin-transition: 1s ease; /* round icon button spin (settings, emoji, etc.) */
/* corner roundness (border-radius) */
--roundness-xl: 22px; /* roundness of big panel outer corners */
--roundness-l: 20px; /* popout panels */
--roundness-m: 16px; /* smaller panels, images, embeds */
--roundness-s: 12px; /* members, settings inputs */
--roundness-xs: 10px; /* channels, buttons */
--roundness-xxs: 8px; /* searchbar, small elements */
/* direct messages moon icon */
/* change to block to show, none to hide */
--discord-icon: none; /* discord icon */
--moon-icon: block; /* moon icon */
--moon-icon-url: url('https://upload.wikimedia.org/wikipedia/commons/c/c4/Font_Awesome_5_solid_moon.svg'); /* custom icon url */
--moon-icon-size: auto;
/* filter uncolorable elements to fit theme */
/* (just set to none, they're too much work to configure) */
--login-bg-filter: saturate(0.3) hue-rotate(-15deg) brightness(0.4); /* login background artwork */
--green-to-accent-3-filter: hue-rotate(56deg) saturate(1.43); /* add friend page explore icon */
--blurple-to-accent-3-filter: hue-rotate(304deg) saturate(0.84) brightness(1.2); /* add friend page school icon */
}
/* Selected chat/friend text */
.selected_f5eb4b,
.selected_f6f816 .link_d8bfb3 {
color: var(--text-0) !important;
background: var(--accent-3) !important;
}
.selected_f6f816 .link_d8bfb3 * {
color: var(--text-0) !important;
fill: var(--text-0) !important;
}
/* Make channel name text less visible (darker) */
.name__2ea32 {
color: var(--text-5) !important;
opacity: 0.7 !important;
}
/* Make unread channel names brighter */
.link__2ea32[aria-label*="unread"] .name__2ea32 {
color: var(--text-2) !important;
opacity: 1 !important;
font-weight: 600 !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 KiB

1433
Assets/Translations/de.json Normal file

File diff suppressed because it is too large Load Diff

1433
Assets/Translations/en.json Normal file

File diff suppressed because it is too large Load Diff

1429
Assets/Translations/es.json Normal file

File diff suppressed because it is too large Load Diff

1429
Assets/Translations/fr.json Normal file

File diff suppressed because it is too large Load Diff

1429
Assets/Translations/pt.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

View File

@@ -0,0 +1,194 @@
{
"settingsVersion": 12,
"bar": {
"position": "top",
"backgroundOpacity": 1,
"monitors": [],
"density": "default",
"showCapsule": true,
"floating": false,
"marginVertical": 0.25,
"marginHorizontal": 0.25,
"widgets": {
"left": [
{
"id": "SystemMonitor"
},
{
"id": "ActiveWindow"
},
{
"id": "MediaMini"
}
],
"center": [
{
"id": "Workspace"
}
],
"right": [
{
"id": "ScreenRecorder"
},
{
"id": "Tray"
},
{
"id": "NotificationHistory"
},
{
"id": "WiFi"
},
{
"id": "Bluetooth"
},
{
"id": "Battery"
},
{
"id": "Volume"
},
{
"id": "Brightness"
},
{
"id": "Clock"
},
{
"id": "ControlCenter"
}
]
}
},
"general": {
"avatarImage": "",
"dimDesktop": true,
"showScreenCorners": false,
"forceBlackScreenCorners": false,
"radiusRatio": 1,
"screenRadiusRatio": 1,
"animationSpeed": 1,
"animationDisabled": false
},
"location": {
"name": "Tokyo",
"useFahrenheit": false,
"use12hourFormat": false,
"showWeekNumberInCalendar": false
},
"screenRecorder": {
"directory": "",
"frameRate": 60,
"audioCodec": "opus",
"videoCodec": "h264",
"quality": "very_high",
"colorRange": "limited",
"showCursor": true,
"audioSource": "default_output",
"videoSource": "portal"
},
"wallpaper": {
"enabled": true,
"directory": "",
"enableMultiMonitorDirectories": false,
"setWallpaperOnAllMonitors": true,
"fillMode": "crop",
"fillColor": "#000000",
"randomEnabled": false,
"randomIntervalSec": 300,
"transitionDuration": 1500,
"transitionType": "random",
"transitionEdgeSmoothness": 0.05,
"monitors": []
},
"appLauncher": {
"enableClipboardHistory": false,
"position": "center",
"backgroundOpacity": 1,
"pinnedExecs": [],
"useApp2Unit": false,
"sortByMostUsed": true,
"terminalCommand": "xterm -e"
},
"dock": {
"autoHide": false,
"exclusive": false,
"backgroundOpacity": 1,
"floatingRatio": 1,
"onlySameOutput": true,
"monitors": [],
"pinnedApps": []
},
"network": {
"wifiEnabled": true
},
"notifications": {
"doNotDisturb": false,
"monitors": [],
"location": "top_right",
"alwaysOnTop": false,
"lastSeenTs": 0,
"respectExpireTimeout": false,
"lowUrgencyDuration": 3,
"normalUrgencyDuration": 8,
"criticalUrgencyDuration": 15
},
"osd": {
"enabled": true,
"location": "top_right",
"monitors": [],
"autoHideMs": 2000
},
"audio": {
"volumeStep": 5,
"volumeOverdrive": false,
"cavaFrameRate": 60,
"visualizerType": "linear",
"mprisBlacklist": [],
"preferredPlayer": ""
},
"ui": {
"fontDefault": "Roboto",
"fontFixed": "DejaVu Sans Mono",
"fontDefaultScale": 1,
"fontFixedScale": 1,
"monitorsScaling": [],
"idleInhibitorEnabled": false
},
"brightness": {
"brightnessStep": 5
},
"colorSchemes": {
"useWallpaperColors": false,
"predefinedScheme": "Noctalia (default)",
"darkMode": true,
"matugenSchemeType": "scheme-fruit-salad"
},
"matugen": {
"gtk4": false,
"gtk3": false,
"qt6": false,
"qt5": false,
"kitty": false,
"ghostty": false,
"foot": false,
"fuzzel": false,
"vesktop": false,
"pywalfox": false,
"enableUserTemplates": false
},
"nightLight": {
"enabled": false,
"forced": false,
"autoSchedule": true,
"nightTemp": "4000",
"dayTemp": "6500",
"manualSunrise": "06:30",
"manualSunset": "18:30"
},
"hooks": {
"enabled": false,
"wallpaperChange": "",
"darkModeChange": ""
}
}

636
Bin/i18n-json-check.sh Executable file
View File

@@ -0,0 +1,636 @@
#!/bin/bash
# JSON Language File Comparison Script
# Compares language files against en.json reference and generates a report
set -euo pipefail
# Configuration
FOLDER_PATH="Assets/Translations"
REFERENCE_FILE="en.json"
TRANSLATE_MODE=false
# Colors for terminal output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_color() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# Function to check if jq is installed
check_dependencies() {
if ! command -v jq &> /dev/null; then
print_color $RED "Error: 'jq' is required but not installed. Please install jq first." >&2
print_color $YELLOW "On Ubuntu/Debian: sudo apt-get install jq" >&2
print_color $YELLOW "On CentOS/RHEL: sudo yum install jq" >&2
print_color $YELLOW "On macOS: brew install jq" >&2
exit 1
fi
if $TRANSLATE_MODE && ! command -v curl &> /dev/null; then
print_color $RED "Error: 'curl' is required for translation mode but not installed." >&2
exit 1
fi
}
# Function to get Gemini API key
get_gemini_api_key() {
if [[ -z "${GEMINI_API_KEY:-}" ]]; then
print_color $RED "Error: GEMINI_API_KEY environment variable is not set" >&2
print_color $YELLOW "Please set it with: export GEMINI_API_KEY='your-api-key'" >&2
exit 1
fi
echo "$GEMINI_API_KEY"
}
# Function to get value from JSON using key path
get_json_value() {
local json_file=$1
local key_path=$2
# Convert dot-separated path to jq path
local jq_path=$(echo "$key_path" | sed 's/\./\.\["/g' | sed 's/$/"]/' | sed 's/^\.//')
local jq_query=".${jq_path}"
# Use a more robust approach: split by dots and build path
local -a path_parts
IFS='.' read -ra path_parts <<< "$key_path"
local jq_filter="."
for part in "${path_parts[@]}"; do
jq_filter="${jq_filter}[\"${part}\"]"
done
jq -r "$jq_filter // empty" "$json_file" 2>/dev/null || echo ""
}
# Function to list available Gemini models
list_gemini_models() {
local api_key=$(get_gemini_api_key)
print_color $BLUE "Fetching available Gemini models..." >&2
echo "" >&2
local response=$(curl -s -X GET \
"https://generativelanguage.googleapis.com/v1/models?key=${api_key}" \
-H "Content-Type: application/json" 2>/dev/null)
# Parse and display models
echo "$response" | jq -r '.models[] | "- \(.name) (\(.displayName))"' 2>/dev/null || {
print_color $RED "Failed to parse models list" >&2
echo "$response" >&2
exit 1
}
exit 0
}
# Function to translate text using Gemini API
translate_text() {
local text=$1
local target_language=$2
local api_key=$3
# Escape text for JSON
local escaped_text=$(echo "$text" | jq -Rs .)
# Prepare the API request
local prompt="Translate the following English text to ${target_language}. Return ONLY the translation, no explanations or additional text:\n\n${text}"
local escaped_prompt=$(echo "$prompt" | jq -Rs .)
local request_body=$(cat <<EOF
{
"contents": [{
"parts": [{
"text": ${escaped_prompt}
}]
}],
"generationConfig": {
"temperature": 0.3,
"maxOutputTokens": 1000
}
}
EOF
)
# Make API call to Gemini
local api_url="https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${api_key}"
print_color $BLUE " API URL: $api_url" >&2
local response=$(curl -s -X POST "$api_url" \
-H "Content-Type: application/json" \
-d "$request_body" 2>/dev/null)
print_color $BLUE " API Response: $response" >&2
# Extract the translation from response - try multiple parsing approaches
local translation=$(echo "$response" | jq -r '.candidates[0].content.parts[0].text // .text // empty' 2>/dev/null | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [[ -z "$translation" ]]; then
print_color $RED " Failed to parse translation. Full response:" >&2
echo "$response" | jq . >&2 2>/dev/null || echo "$response" >&2
echo ""
return 1
fi
print_color $GREEN " Parsed translation: $translation" >&2
echo "$translation"
}
# Function to inject translation into JSON file using jq
inject_translation() {
local json_file=$1
local key_path=$2
local value=$3
# Split key path into array
local -a path_parts
IFS='.' read -ra path_parts <<< "$key_path"
# Build jq path array
local jq_path="["
for i in "${!path_parts[@]}"; do
if [[ $i -gt 0 ]]; then
jq_path+=","
fi
jq_path+="\"${path_parts[$i]}\""
done
jq_path+="]"
# Create a temporary file
local temp_file=$(mktemp)
# Use jq to set the value at the path
jq --argjson path "$jq_path" --arg value "$value" 'setpath($path; $value)' "$json_file" > "$temp_file"
if [[ $? -eq 0 ]]; then
mv "$temp_file" "$json_file"
return 0
else
rm -f "$temp_file"
return 1
fi
}
# Function to extract all keys from a JSON file recursively
extract_keys() {
local json_file=$1
if [[ ! -f "$json_file" ]]; then
echo "Error: File $json_file not found" >&2
return 1
fi
# Extract all keys recursively using jq
jq -r '
def keys_recursive:
if type == "object" then
keys[] as $k |
if (.[$k] | type) == "object" then
($k + "." + (.[$k] | keys_recursive))
else
$k
end
else
empty
end;
keys_recursive
' "$json_file" 2>/dev/null | sort
}
# Function to get language files
get_language_files() {
find "$FOLDER_PATH" -maxdepth 1 -name "*.json" -type f | sort
}
# Function to generate report header
generate_header() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "================================================================================"
echo " LANGUAGE FILE COMPARISON REPORT"
echo "================================================================================"
echo "Generated: $timestamp"
echo "Reference file: $REFERENCE_FILE"
echo "Folder: $(realpath "$FOLDER_PATH")"
if $TRANSLATE_MODE; then
echo "Mode: TRANSLATION ENABLED"
fi
echo ""
echo "Notes:"
echo "- Keys are compared recursively through all nested JSON objects"
echo "- Missing keys indicate incomplete translations"
echo "- Extra keys might indicate deprecated keys or translation-specific additions"
echo "- Translation completion percentage is calculated based on English reference"
echo "- Results are sorted by descending line numbers for easier editing"
echo ""
echo "This report compares all language JSON files against the English reference file"
echo "and identifies missing keys and extra keys in each language."
echo ""
}
# Function to find line number of a key in JSON file
find_key_line_number() {
local json_file=$1
local key_path=$2
# Extract the final key name (after last dot)
local key_name="${key_path##*.}"
# Search for the key in the file with line numbers
# Look for the pattern "key": (with quotes and colon)
local line_num=$(grep -n "\"$key_name\":" "$json_file" 2>/dev/null | head -1 | cut -d: -f1 || echo "")
if [[ -n "$line_num" ]]; then
echo "$line_num"
else
# If not found with quotes, try without (though less reliable)
line_num=$(grep -n "$key_name:" "$json_file" 2>/dev/null | head -1 | cut -d: -f1 || echo "")
if [[ -n "$line_num" ]]; then
echo "$line_num"
else
echo "?"
fi
fi
}
# Function to safely count lines
count_non_empty_lines() {
local content="$1"
if [[ -z "$content" ]]; then
echo "0"
else
echo "$content" | grep -c -v '^$' || echo "0"
fi
}
# Function to compare keys and generate report section
compare_language() {
local lang_file="$1"
local lang_name="$2"
local ref_keys_file="$3"
local ref_file_path="$FOLDER_PATH/$REFERENCE_FILE"
# Create temporary file for language keys
local lang_keys_file=$(mktemp)
extract_keys "$lang_file" > "$lang_keys_file" || {
echo "Error: Failed to extract keys from $lang_file" >&2
rm -f "$lang_keys_file"
return 1
}
# Get missing and extra keys safely
local missing_keys=""
local extra_keys=""
missing_keys=$(comm -23 "$ref_keys_file" "$lang_keys_file" 2>/dev/null || echo "")
extra_keys=$(comm -13 "$ref_keys_file" "$lang_keys_file" 2>/dev/null || echo "")
# Count lines safely
local missing_count=$(count_non_empty_lines "$missing_keys")
local extra_count=$(count_non_empty_lines "$extra_keys")
local total_ref_keys=$(wc -l < "$ref_keys_file" 2>/dev/null || echo "0")
local total_lang_keys=$(wc -l < "$lang_keys_file" 2>/dev/null || echo "0")
# Calculate completion percentage safely
local completion_percentage=0
if [[ $total_ref_keys -gt 0 ]]; then
completion_percentage=$(( (total_ref_keys - missing_count) * 100 / total_ref_keys ))
fi
print_color $YELLOW "================================================================================"
print_color $YELLOW "LANGUAGE: $lang_name"
print_color $YELLOW "================================================================================"
echo "File: $lang_file"
echo "Total keys in reference (en): $total_ref_keys"
echo "Total keys in $lang_name: $total_lang_keys"
# Color code the completion percentage
if [[ $completion_percentage -eq 100 ]]; then
echo -e "Translation completion: ${GREEN}${completion_percentage}%${NC}"
else
echo -e "Translation completion: ${RED}${completion_percentage}%${NC}"
fi
echo ""
echo "SUMMARY:"
echo "- Missing keys (exist in English but not in $lang_name): $missing_count"
echo "- Extra keys (exist in $lang_name but not in English): $extra_count"
echo ""
# Handle missing keys
if [[ $missing_count -gt 0 && -n "$missing_keys" ]]; then
echo "MISSING KEYS IN $lang_name:"
# Collect keys with line numbers and sort by line number (descending)
local temp_missing=$(mktemp)
while IFS= read -r key; do
if [[ -n "$key" ]]; then
local ref_line=$(find_key_line_number "$ref_file_path" "$key")
# Use numeric sort padding for proper sorting
if [[ "$ref_line" =~ ^[0-9]+$ ]]; then
printf "%06d|%s|en.json:%s\n" "$ref_line" "$key" "$ref_line" >> "$temp_missing"
else
printf "999999|%s|en.json:%s\n" "$key" "$ref_line" >> "$temp_missing"
fi
fi
done <<< "$missing_keys"
# Sort by line number (descending) and display
local counter=1
sort -t'|' -k1,1nr "$temp_missing" | while IFS='|' read -r sort_key key location; do
printf " %3d. %s (%s)\n" "$counter" "$key" "$location"
counter=$((counter + 1))
done
rm -f "$temp_missing"
echo ""
# Translate missing keys if in translate mode
if $TRANSLATE_MODE; then
print_color $BLUE "Translating missing keys for $lang_name..." >&2
local api_key=$(get_gemini_api_key)
local translated_count=0
local failed_count=0
while IFS= read -r key; do
if [[ -n "$key" ]]; then
# Get English value
local en_value=$(get_json_value "$ref_file_path" "$key")
if [[ -n "$en_value" ]]; then
print_color $YELLOW " Translating: $key" >&2
# Translate the value
local translated_value=$(translate_text "$en_value" "$lang_name" "$api_key")
if [[ -n "$translated_value" ]]; then
# Inject translation into the file
if inject_translation "$lang_file" "$key" "$translated_value"; then
print_color $GREEN " ✓ Translated: $key" >&2
translated_count=$((translated_count + 1))
else
print_color $RED " ✗ Failed to inject: $key" >&2
failed_count=$((failed_count + 1))
fi
else
print_color $RED " ✗ Translation failed: $key" >&2
failed_count=$((failed_count + 1))
fi
# Small delay to avoid rate limiting
sleep 0.5
fi
fi
done <<< "$missing_keys"
echo ""
print_color $GREEN "Translation complete: $translated_count succeeded, $failed_count failed" >&2
echo ""
fi
else
echo "✅ No missing keys in $lang_name"
echo ""
fi
# Handle extra keys
if [[ $extra_count -gt 0 && -n "$extra_keys" ]]; then
echo "EXTRA KEYS IN $lang_name (not in English):"
# Collect keys with line numbers and sort by line number (descending)
local temp_extra=$(mktemp)
while IFS= read -r key; do
if [[ -n "$key" ]]; then
local lang_line=$(find_key_line_number "$lang_file" "$key")
# Use numeric sort padding for proper sorting
if [[ "$lang_line" =~ ^[0-9]+$ ]]; then
printf "%06d|%s|%s:%s\n" "$lang_line" "$key" "$(basename "$lang_file")" "$lang_line" >> "$temp_extra"
else
printf "999999|%s|%s:%s\n" "$key" "$(basename "$lang_file")" "$lang_line" >> "$temp_extra"
fi
fi
done <<< "$extra_keys"
# Sort by line number (descending) and display
local counter=1
sort -t'|' -k1,1nr "$temp_extra" | while IFS='|' read -r sort_key key location; do
printf " %3d. %s (%s)\n" "$counter" "$key" "$location"
counter=$((counter + 1))
done
rm -f "$temp_extra"
echo ""
else
echo "✅ No extra keys in $lang_name"
echo ""
fi
# Clean up
rm -f "$lang_keys_file"
}
# Main function
main() {
local target_language="$1"
print_color $BLUE "Starting language file comparison..." >&2
# Check dependencies
check_dependencies
# Validate folder path
if [[ ! -d "$FOLDER_PATH" ]]; then
print_color $RED "Error: Folder '$FOLDER_PATH' does not exist" >&2
exit 1
fi
# Check if reference file exists
local ref_file_path="$FOLDER_PATH/$REFERENCE_FILE"
if [[ ! -f "$ref_file_path" ]]; then
print_color $RED "Error: Reference file '$ref_file_path' does not exist" >&2
exit 1
fi
print_color $GREEN "Reference file found: $ref_file_path" >&2
# Extract keys from reference file
local ref_keys_file=$(mktemp)
if ! extract_keys "$ref_file_path" > "$ref_keys_file"; then
print_color $RED "Error: Failed to extract keys from reference file" >&2
rm -f "$ref_keys_file"
exit 1
fi
local total_ref_keys=$(wc -l < "$ref_keys_file" 2>/dev/null || echo "0")
print_color $BLUE "Extracted $total_ref_keys keys from reference file" >&2
# Get all language files or just the target language
local -a language_files
if [[ -n "$target_language" ]]; then
# Single language mode
local target_file="$FOLDER_PATH/${target_language}.json"
if [[ ! -f "$target_file" ]]; then
print_color $RED "Error: Language file '$target_file' does not exist" >&2
rm -f "$ref_keys_file"
exit 1
fi
if [[ "$target_language" == "${REFERENCE_FILE%.json}" ]]; then
print_color $RED "Error: Cannot compare reference file against itself" >&2
rm -f "$ref_keys_file"
exit 1
fi
language_files=("$target_file")
print_color $BLUE "Checking single language: $target_language" >&2
else
# All languages mode
while IFS= read -r -d '' file; do
language_files+=("$file")
done < <(find "$FOLDER_PATH" -maxdepth 1 -name "*.json" -type f -print0 | sort -z)
if [[ ${#language_files[@]} -eq 0 ]]; then
print_color $RED "Error: No JSON files found in $FOLDER_PATH" >&2
rm -f "$ref_keys_file"
exit 1
fi
print_color $BLUE "Found ${#language_files[@]} JSON files to process" >&2
fi
echo "" >&2
# Generate report header
generate_header
local processed=0
for lang_file in "${language_files[@]}"; do
local filename=$(basename "$lang_file")
local lang_name="${filename%.json}"
# Skip the reference file in all-languages mode
if [[ -z "$target_language" && "$filename" == "$REFERENCE_FILE" ]]; then
continue
fi
print_color $YELLOW "Processing: $filename" >&2
# Validate JSON syntax
if ! jq empty "$lang_file" 2>/dev/null; then
print_color $RED "Warning: $lang_file contains invalid JSON syntax. Skipping..." >&2
echo "ERROR: $lang_file contains invalid JSON syntax and was skipped."
echo ""
continue
fi
if compare_language "$lang_file" "$lang_name" "$ref_keys_file"; then
processed=$((processed + 1))
else
print_color $RED "Error processing $lang_file" >&2
fi
done
# Add summary at the end
echo "================================================================================"
echo "SUMMARY"
echo "================================================================================"
echo "Total files processed: $processed"
echo "Reference file: $REFERENCE_FILE (English)"
if [[ -n "$target_language" ]]; then
echo "Target language: $target_language"
fi
if $TRANSLATE_MODE; then
echo "Translation mode: ENABLED"
fi
echo "Report generated: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
echo "================================================================================"
# Clean up
rm -f "$ref_keys_file"
if [[ -n "$target_language" ]]; then
print_color $GREEN "Comparison completed for language: $target_language" >&2
else
print_color $GREEN "Comparison completed: Processed $processed language files against English reference" >&2
fi
}
# Usage information
show_usage() {
echo "Usage: $0 [--translate] [language_code]" >&2
echo "" >&2
echo "This script compares JSON language files in '$FOLDER_PATH' against the English reference." >&2
echo "" >&2
echo "Arguments:" >&2
echo " --translate Enable automatic translation of missing keys using Gemini API" >&2
echo " --list-models List all available Gemini models and exit" >&2
echo " language_code Optional. Compare only the specified language (e.g., 'fr', 'es', 'de')" >&2
echo " If not provided, all language files will be compared" >&2
echo "" >&2
echo "Configuration:" >&2
echo " - Folder path: $FOLDER_PATH (hardcoded)" >&2
echo " - Reference file: $REFERENCE_FILE" >&2
echo "" >&2
echo "Examples:" >&2
echo " $0 # Compare all languages" >&2
echo " $0 fr # Compare only French (fr.json)" >&2
echo " $0 --list-models # List available Gemini models" >&2
echo " $0 --translate # Compare all and translate missing keys" >&2
echo " $0 --translate fr # Translate missing keys for French only" >&2
echo "" >&2
echo "Requirements:" >&2
echo " - jq must be installed" >&2
echo " - curl must be installed (for --translate mode)" >&2
echo " - $REFERENCE_FILE must exist in $FOLDER_PATH" >&2
echo " - Target language file must exist if specified" >&2
echo " - GEMINI_API_KEY environment variable must be set (for --translate mode)" >&2
echo "" >&2
echo "Output:" >&2
echo " - Comparison report is printed to stdout" >&2
echo " - Progress messages are printed to stderr" >&2
echo " - Results are sorted by descending line numbers for easier editing" >&2
}
# Handle command line arguments
target_lang=""
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
--list-models)
list_gemini_models
;;
--translate)
TRANSLATE_MODE=true
shift
;;
*)
if [[ -n "$target_lang" ]]; then
echo "Error: Too many arguments. Only one language code is allowed." >&2
echo "" >&2
show_usage
exit 1
fi
# Validate language code format (basic check for reasonable filename)
if [[ ! "$1" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then
echo "Error: Invalid language code format '$1'. Use alphanumeric characters, hyphens, and underscores only." >&2
echo "" >&2
show_usage
exit 1
fi
target_lang="$1"
shift
;;
esac
done
# Run main function
main "$target_lang"

31
Bin/i18n-qml-check.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Comprehensive i18n checker for QML files
# Finds hardcoded strings in various QML properties
find . -name "*.qml" -type f | while read -r file; do
# Skip if file doesn't exist or is not readable
[[ ! -r "$file" ]] && continue
# Check for hardcoded strings in common properties
# Matches: property: "text with letters" but excludes I18n.tr calls
issues=$(grep -n -E '(label|text|title|description|placeholder|tooltipText|tooltip):\s*"[^"]*[a-zA-Z][^"]*"' "$file" | grep -v 'I18n\.tr')
# Also check for template literals with hardcoded text
template_issues=$(grep -n -E '(label|text|title|description|placeholder|tooltipText|tooltip):\s*`[^`]*[a-zA-Z][^`]*`' "$file" | grep -v 'I18n\.tr')
# Check for property assignments with hardcoded strings
property_issues=$(grep -n -E 'property\s+string\s+\w+:\s*"[^"]*[a-zA-Z][^"]*"' "$file" | grep -v 'I18n\.tr')
# Check for JavaScript object properties with hardcoded strings (like in arrays/models)
js_object_issues=$(grep -n -E '"(label|text|title|description|placeholder|name)":\s*"[^"]*[a-zA-Z][^"]*"' "$file" | grep -v 'I18n\.tr')
if [[ -n "$issues" || -n "$template_issues" || -n "$property_issues" || -n "$js_object_issues" ]]; then
echo "$file"
[[ -n "$issues" ]] && echo "$issues"
[[ -n "$template_issues" ]] && echo "$template_issues"
[[ -n "$property_issues" ]] && echo "$property_issues"
[[ -n "$js_object_issues" ]] && echo "$js_object_issues"
echo
fi
done

46
Bin/notifications-test.sh Executable file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env -S bash
echo "Sending test notifications..."
# Send a bunch of notifications with numbers
for i in {1..4}; do
notify-send "Notification $i" "This is test notification number $i with a very long text that will probably break the layout or maybe not? Who knows? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
sleep 1
done
echo "All notifications sent!"
# Additional tests for icon/image handling
if command -v notify-send >/dev/null 2>&1; then
echo "Sending icon/image tests..."
# 1) Themed icon name
notify-send -i dialog-information "Icon name test" "Should resolve from theme (dialog-information)"
# 2) Absolute path if a sample image exists
SAMPLE_IMG="/usr/share/pixmaps/steam.png"
if [ -f "$SAMPLE_IMG" ]; then
notify-send -i "$SAMPLE_IMG" "Absolute path test" "Should show the provided image path"
fi
# 3) file:// URL form
if [ -f "$SAMPLE_IMG" ]; then
notify-send -i "file://$SAMPLE_IMG" "file:// URL test" "Should display after stripping scheme"
fi
echo "Icon/image tests sent!"
fi
# A test notification with actions
gdbus call --session \
--dest org.freedesktop.Notifications \
--object-path /org/freedesktop/Notifications \
--method org.freedesktop.Notifications.Notify \
"my-app" \
0 \
"dialog-question" \
"Confirmation Required" \
"Do you want to proceed with the action?" \
"['default', 'OK', 'cancel', 'Cancel', 'maybe', 'Maybe', 'undecided', 'Undecided']" \
"{}" \
5000

View File

@@ -4,4 +4,4 @@
# Can be installed from AUR "qmlfmt-git"
# Requires qt6-5compat
find . -name "*.qml" -print -exec qmlfmt -e -b 120 -t 2 -i 2 -w {} \;
find . -name "*.qml" -print -exec qmlfmt -e -b 360 -t 2 -i 2 -w {} \;

36
Bin/shaders-compile.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Directory containing the source shaders.
SOURCE_DIR="Shaders/frag/"
# Directory where the compiled shaders will be saved.
DEST_DIR="Shaders/qsb/"
# Check if the source directory exists.
if [ ! -d "$SOURCE_DIR" ]; then
echo "Source directory $SOURCE_DIR not found!"
exit 1
fi
# Create the destination directory if it doesn't exist.
mkdir -p "$DEST_DIR"
# Loop through all files in the source directory ending with .frag
for shader in "$SOURCE_DIR"*.frag; do
# Check if a file was found (to handle the case of no .frag files).
if [ -f "$shader" ]; then
# Get the base name of the file (e.g., wp_fade).
shader_name=$(basename "$shader" .frag)
# Construct the output path for the compiled shader.
output_path="$DEST_DIR$shader_name.frag.qsb"
# Construct and run the qsb command.
qsb --qt6 -o "$output_path" "$shader"
# Print a message to confirm compilation.
echo "Compiled $shader to $output_path"
fi
done
echo "Shader compilation complete."

View File

@@ -1,219 +0,0 @@
#!/usr/bin/env -S bash
# A Bash script to monitor system stats and output them in JSON format.
# --- Configuration ---
# Default sleep duration in seconds. Can be overridden by the first argument.
SLEEP_DURATION=3
# --- Argument Parsing ---
# Check if a command-line argument is provided for the sleep duration.
if [[ -n "$1" ]]; then
# Basic validation to ensure the argument is a number (integer or float).
if [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
SLEEP_DURATION=$1
else
# Output to stderr if the format is invalid.
echo "Warning: Invalid duration format '$1'. Using default of ${SLEEP_DURATION}s." >&2
fi
fi
# --- Global Cache Variables ---
# These variables will store the discovered CPU temperature sensor path and type
# to avoid searching for it on every loop iteration.
TEMP_SENSOR_PATH=""
TEMP_SENSOR_TYPE=""
# --- Data Collection Functions ---
#
# Gets memory usage in GB, MB, and as a percentage.
#
get_memory_info() {
awk '
/MemTotal/ {total=$2}
/MemAvailable/ {available=$2}
END {
if (total > 0) {
usage_kb = total - available
usage_gb = usage_kb / 1000000
usage_percent = (usage_kb / total) * 100
printf "%.1f %.0f\n", usage_gb, usage_percent
} else {
# Fallback if /proc/meminfo is unreadable or empty.
print "0.0 0 0"
}
}
' /proc/meminfo
}
#
# Gets the usage percentage of the root filesystem ("/").
#
get_disk_usage() {
# df gets disk usage. --output=pcent shows only the percentage for the root path.
# tail -1 gets the data line, and tr removes the '%' sign and whitespace.
df --output=pcent / | tail -1 | tr -d ' %'
}
#
# Calculates current CPU usage over a short interval.
#
get_cpu_usage() {
# Read all 10 CPU time fields to prevent errors on newer kernels.
read -r cpu prev_user prev_nice prev_system prev_idle prev_iowait prev_irq prev_softirq prev_steal prev_guest prev_guest_nice < /proc/stat
# Calculate previous total and idle times.
local prev_total_idle=$((prev_idle + prev_iowait))
local prev_total=$((prev_user + prev_nice + prev_system + prev_idle + prev_iowait + prev_irq + prev_softirq + prev_steal + prev_guest + prev_guest_nice))
# Wait for a short period.
sleep 0.05
# Read all 10 CPU time fields again for the second measurement.
read -r cpu user nice system idle iowait irq softirq steal guest guest_nice < /proc/stat
# Calculate new total and idle times.
local total_idle=$((idle + iowait))
local total=$((user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice))
# Add a check to prevent division by zero if total hasn't changed.
if (( total <= prev_total )); then
echo "0.0"
return
fi
# Calculate the difference over the interval.
local diff_total=$((total - prev_total))
local diff_idle=$((total_idle - prev_total_idle))
# Use awk for floating-point calculation and print the percentage.
awk -v total="$diff_total" -v idle="$diff_idle" '
BEGIN {
if (total > 0) {
# Formula: 100 * (Total - Idle) / Total
usage = 100 * (total - idle) / total
printf "%.1f\n", usage
} else {
print "0.0"
}
}'
}
#
# Finds and returns the CPU temperature in degrees Celsius.
# Caches the sensor path for efficiency.
#
get_cpu_temp() {
# If the sensor path hasn't been found yet, search for it.
if [[ -z "$TEMP_SENSOR_PATH" ]]; then
for dir in /sys/class/hwmon/hwmon*; do
# Check if the 'name' file exists and read it.
if [[ -f "$dir/name" ]]; then
local name
name=$(<"$dir/name")
# Check for supported sensor types.
if [[ "$name" == "coretemp" || "$name" == "k10temp" || "$name" == "zenpower" ]]; then
TEMP_SENSOR_PATH=$dir
TEMP_SENSOR_TYPE=$name
break # Found it, no need to keep searching.
fi
fi
done
fi
# If after searching no sensor was found, return 0.
if [[ -z "$TEMP_SENSOR_PATH" ]]; then
echo 0
return
fi
# --- Get temp based on sensor type ---
if [[ "$TEMP_SENSOR_TYPE" == "coretemp" ]]; then
# For Intel 'coretemp', average all available temperature sensors.
local total_temp=0
local sensor_count=0
# Use a for loop with a glob to iterate over all temp input files.
# This is more efficient than 'find' for this simple case.
for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
# The glob returns the pattern itself if no files match,
# so we must check if the file actually exists.
if [[ -f "$temp_file" ]]; then
total_temp=$((total_temp + $(<"$temp_file")))
sensor_count=$((sensor_count + 1))
fi
done
if (( sensor_count > 0 )); then
# Use awk for the final division to handle potential floating point numbers
# and convert from millidegrees to integer degrees Celsius.
awk -v total="$total_temp" -v count="$sensor_count" 'BEGIN { print int(total / count / 1000) }'
else
# If no sensor files were found, return 0.
echo 0
fi
elif [[ "$TEMP_SENSOR_TYPE" == "k10temp" ]]; then
# For AMD 'k10temp', find the 'Tctl' sensor, which is the control temperature.
local tctl_input=""
for label_file in "$TEMP_SENSOR_PATH"/temp*_label; do
if [[ -f "$label_file" ]] && [[ $(<"$label_file") == "Tctl" ]]; then
# The input file has the same name but with '_input' instead of '_label'.
tctl_input="${label_file%_label}_input"
break
fi
done
if [[ -f "$tctl_input" ]]; then
# Read the temperature and convert from millidegrees to degrees.
echo "$(( $(<"$tctl_input") / 1000 ))"
else
echo 0 # Fallback
fi
elif [[ "$TEMP_SENSOR_TYPE" == "zenpower" ]]; then
# For zenpower, read the first available temp sensor
for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
if [[ -f "$temp_file" ]]; then
local temp_value
temp_value=$(cat "$temp_file" | tr -d '\n\r') # Remove any newlines
echo "$((temp_value / 1000))"
return
fi
done
echo 0
if [[ -f "$tctl_input" ]]; then
# Read the temperature and convert from millidegrees to degrees.
echo "$(($(<"$tctl_input") / 1000))"
else
echo 0 # Fallback
fi
else
echo 0 # Should not happen if cache logic is correct.
fi
}
# --- Main Loop ---
# This loop runs indefinitely, gathering and printing stats.
while true; do
# Call the functions to gather all the data.
# get_memory_info
read -r mem_gb mem_per <<< "$(get_memory_info)"
# Command substitution captures the single output from the other functions.
disk_per=$(get_disk_usage)
cpu_usage=$(get_cpu_usage)
cpu_temp=$(get_cpu_temp)
# Use printf to format the final JSON output string, adding the mem_mb key.
printf '{"cpu": "%s", "cputemp": "%s", "memgb":"%s", "memper": "%s", "diskper": "%s"}\n' \
"$cpu_usage" \
"$cpu_temp" \
"$mem_gb" \
"$mem_per" \
"$disk_per"
# Wait for the specified duration before the next update.
sleep "$SLEEP_DURATION"
done

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env -S bash
echo "Sending 8 test notifications..."
# Send 8 notifications with numbers
for i in {1..8}; do
notify-send "Notification $i" "This is test notification number $i of 8"
sleep 1
done
echo "All notifications sent!"

View File

@@ -44,14 +44,6 @@ Singleton {
property color transparent: "transparent"
// -----------
function applyOpacity(color, opacity) {
// Convert color to string and apply opacity
if (!color)
return "transparent"
return color.toString().replace("#", "#" + opacity)
}
// --------------------------------
// Default colors: RosePine
QtObject {
@@ -110,7 +102,8 @@ Singleton {
// FileView to load custom colors data from colors.json
FileView {
id: customColorsFile
path: Settings.configDir + "colors.json"
path: Settings.directoriesCreated ? (Settings.configDir + "colors.json") : undefined
printErrors: false
watchChanges: true
onFileChanged: {
Logger.log("Color", "Reloading colors from disk")
@@ -120,6 +113,13 @@ Singleton {
Logger.log("Color", "Writing colors to disk")
writeAdapter()
}
// Trigger initial load when path changes from empty to actual path
onPathChanged: {
if (path !== undefined) {
reload()
}
}
onLoadFailed: function (error) {
if (error.toString().includes("No such file") || error === 2) {
// File doesn't exist, create it with default values

365
Commons/I18n.qml Normal file
View File

@@ -0,0 +1,365 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
Singleton {
id: root
property bool debug: false
property string debugForceLanguage: ""
property bool isLoaded: false
property string langCode: ""
property var availableLanguages: []
property var translations: ({})
property var fallbackTranslations: ({})
// Signals for reactive updates
signal languageChanged(string newLanguage)
signal translationsLoaded
// Process to list directory contents
property Process directoryScanner: Process {
id: directoryProcess
command: ["ls", `${Quickshell.shellDir}/Assets/Translations/`]
running: false
stdout: StdioCollector {
id: stdoutCollector
}
onExited: function (exitCode, exitStatus) {
if (exitCode === 0) {
var output = stdoutCollector.text || ""
parseDirectoryListing(output)
} else {
Logger.error("I18n", `Failed to scan translation directory`)
// Fallback to default languages
availableLanguages = ["en"]
detectLanguage()
}
}
}
// FileView to load translation files
property FileView translationFile: FileView {
id: fileView
watchChanges: true
onFileChanged: reload()
onLoaded: {
try {
var data = JSON.parse(text())
root.translations = data
Logger.log("I18n", `Loaded translations for "${root.langCode}"`)
root.isLoaded = true
root.translationsLoaded()
} catch (e) {
Logger.error("I18n", `Failed to parse translation file: ${e}`)
setLanguage("en")
}
}
onLoadFailed: function (error) {
setLanguage("en")
Logger.error("I18n", `Failed to load translation file: ${error}`)
}
}
// FileView to load fallback translation files
property FileView fallbackTranslationFile: FileView {
id: fallbackFileView
watchChanges: true
onFileChanged: reload()
onLoaded: {
try {
var data = JSON.parse(text())
root.fallbackTranslations = data
Logger.log("I18n", `Loaded english fallback translations`)
} catch (e) {
Logger.error("I18n", `Failed to parse fallback translation file: ${e}`)
}
}
onLoadFailed: function (error) {
Logger.error("I18n", `Failed to load fallback translation file: ${error}`)
}
}
Component.onCompleted: {
Logger.log("I18n", "Service started")
scanAvailableLanguages()
}
// -------------------------------------------
function scanAvailableLanguages() {
Logger.log("I18n", "Scanning for available translation files...")
directoryScanner.running = true
}
// -------------------------------------------
function parseDirectoryListing(output) {
var languages = []
try {
if (!output || output.trim() === "") {
Logger.warn("I18n", "Empty directory listing output")
availableLanguages = ["en"]
detectLanguage()
return
}
const entries = output.trim().split('\n')
for (var i = 0; i < entries.length; i++) {
const entry = entries[i].trim()
if (entry && entry.endsWith('.json')) {
// Extract language code from filename (e.g., "en.json" -> "en")
const langCode = entry.substring(0, entry.lastIndexOf('.json'))
if (langCode.length >= 2 && langCode.length <= 5) {
// Basic validation for language codes
languages.push(langCode)
}
}
}
// Sort languages alphabetically, but ensure "en" comes first if available
languages.sort()
const enIndex = languages.indexOf("en")
if (enIndex > 0) {
languages.splice(enIndex, 1)
languages.unshift("en")
}
if (languages.length === 0) {
Logger.warn("I18n", "No translation files found, using fallback")
languages = ["en"] // Fallback
}
availableLanguages = languages
Logger.log("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`)
// Detect language after scanning
detectLanguage()
} catch (e) {
Logger.error("I18n", `Failed to parse directory listing: ${e}`)
// Fallback to default languages
availableLanguages = ["en"]
detectLanguage()
}
}
// -------------------------------------------
function detectLanguage() {
Logger.log("I18n", `detectLanguage() called. Available languages: [${availableLanguages.join(', ')}]`)
if (availableLanguages.length === 0) {
Logger.warn("I18n", "No available languages found")
return
}
if (debug && debugForceLanguage !== "") {
Logger.log("I18n", `Debug mode: forcing language to "${debugForceLanguage}"`)
if (availableLanguages.includes(debugForceLanguage)) {
setLanguage(debugForceLanguage)
return
} else {
Logger.warn("I18n", `Debug language "${debugForceLanguage}" not available in [${availableLanguages.join(', ')}]`)
}
}
// Detect user's favorite locale - languages
for (var i = 0; i < Qt.locale().uiLanguages.length; i++) {
const fullUserLang = Qt.locale().uiLanguages[i]
// Try full code match (such as zh CN, en US)
if (availableLanguages.includes(fullUserLang)) {
Logger.log("I18n", `Exact match found: "${fullUserLang}"`)
setLanguage(fullUserLang)
return
}
// If full code match fails, try short code matching (such as zh, en)
const shortUserLang = fullUserLang.substring(0, 2)
if (availableLanguages.includes(shortUserLang)) {
Logger.log("I18n", `Short code match found: "${shortUserLang}" from "${fullUserLang}"`)
setLanguage(shortUserLang)
return
}
Logger.log("I18n", `No match for system language: "${fullUserLang}"`)
}
// Fallback to first available language (preferably "en" if available)
const fallbackLang = availableLanguages.includes("en") ? "en" : availableLanguages[0]
setLanguage(fallbackLang)
}
// -------------------------------------------
function setLanguage(newLangCode) {
if (newLangCode !== langCode && availableLanguages.includes(newLangCode)) {
langCode = newLangCode
Logger.log("I18n", `Language set to "${langCode}"`)
languageChanged(langCode)
loadTranslations()
} else if (!availableLanguages.includes(newLangCode)) {
Logger.warn("I18n", `Language "${newLangCode}" is not available`)
}
}
// -------------------------------------------
function loadTranslations() {
if (langCode === "")
return
const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json`
fileView.path = filePath
isLoaded = false
Logger.log("I18n", `Loading translations: ${langCode}`)
// Only load fallback translations if we are not using english and english is available
if (langCode !== "en" && availableLanguages.includes("en")) {
fallbackFileView.path = `file://${Quickshell.shellDir}/Assets/Translations/en.json`
}
}
// -------------------------------------------
// Check if a translation exists
function hasTranslation(key) {
if (!isLoaded)
return false
const keys = key.split(".")
var value = translations
for (var i = 0; i < keys.length; i++) {
if (value && typeof value === "object" && keys[i] in value) {
value = value[keys[i]]
} else {
return false
}
}
return typeof value === "string"
}
// -------------------------------------------
// Get all translation keys (useful for debugging)
function getAllKeys(obj, prefix) {
if (typeof obj === "undefined")
obj = translations
if (typeof prefix === "undefined")
prefix = ""
var keys = []
for (var key in (obj || {})) {
const value = obj[key]
const fullKey = prefix ? `${prefix}.${key}` : key
if (typeof value === "object" && value !== null) {
keys = keys.concat(getAllKeys(value, fullKey))
} else if (typeof value === "string") {
keys.push(fullKey)
}
}
return keys
}
// -------------------------------------------
// Reload translations (useful for development)
function reload() {
Logger.log("I18n", "Reloading translations")
loadTranslations()
}
// -------------------------------------------
// Main translation function
function tr(key, interpolations) {
if (typeof interpolations === "undefined")
interpolations = {}
if (!isLoaded) {
// if (debug) {
// Logger.warn("I18n", "Translations not loaded yet")
// }
return key
}
// Navigate nested keys (e.g., "menu.file.open")
const keys = key.split(".")
// Look-up translation in the active language
var value = translations
var notFound = false
for (var i = 0; i < keys.length; i++) {
if (value && typeof value === "object" && keys[i] in value) {
value = value[keys[i]]
} else {
if (debug) {
Logger.warn("I18n", `Translation key "${key}" not found`)
}
notFound = true
break
}
}
// Fallback to english if not found
if (notFound && availableLanguages.includes("en") && langCode !== "en") {
value = fallbackTranslations
for (var i = 0; i < keys.length; i++) {
if (value && typeof value === "object" && keys[i] in value) {
value = value[keys[i]]
} else {
// Indicate this key does not even exists in the english fallback
return `## ${key} ##`
}
}
// Make untranslated string easy to spot
value = `<i>${value}</i>`
} else if (notFound) {
// No fallback available
return `## ${key} ##`
}
if (typeof value !== "string") {
if (debug) {
Logger.warn("I18n", `Translation key "${key}" is not a string`)
}
return key
}
// Handle interpolations (e.g., "Hello {name}!")
var result = value
for (var placeholder in interpolations) {
const regex = new RegExp(`\\{${placeholder}\\}`, 'g')
result = result.replace(regex, interpolations[placeholder])
}
return result
}
// -------------------------------------------
// Plural translation function
function trp(key, count, defaultSingular, defaultPlural, interpolations) {
if (typeof defaultSingular === "undefined")
defaultSingular = ""
if (typeof defaultPlural === "undefined")
defaultPlural = ""
if (typeof interpolations === "undefined")
interpolations = {}
const pluralKey = count === 1 ? key : `${key}_plural`
const defaultValue = count === 1 ? defaultSingular : defaultPlural
// Merge interpolations with count (QML doesn't support spread operator)
var finalInterpolations = {
"count": count
}
for (var prop in interpolations) {
finalInterpolations[prop] = interpolations[prop]
}
return tr(pluralKey, finalInterpolations)
}
}

View File

@@ -1,43 +1,85 @@
pragma Singleton
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Commons
Singleton {
id: icons
id: root
function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable"
try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback)
if (p && p !== "")
return p
}
} catch (e) {
// Expose the font family name for easy access
readonly property string fontFamily: currentFontLoader ? currentFontLoader.name : ""
readonly property string defaultIcon: TablerIcons.defaultIcon
readonly property var icons: TablerIcons.icons
readonly property var aliases: TablerIcons.aliases
readonly property string fontPath: "/Assets/Fonts/tabler/tabler-icons.ttf"
// ignore and fall back
}
try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : ""
} catch (e2) {
return ""
// Current active font loader
property FontLoader currentFontLoader: null
property int fontVersion: 0
// Create a unique cache-busting path
readonly property string cacheBustingPath: Quickshell.shellDir + fontPath + "?v=" + fontVersion + "&t=" + Date.now()
// Signal emitted when font is reloaded
signal fontReloaded
Component.onCompleted: {
Logger.log("Icons", "Service started")
loadFontWithCacheBusting()
}
Connections {
target: Quickshell
function onReloadCompleted() {
Logger.log("Icons", "Quickshell reload completed - forcing font reload")
reloadFont()
}
}
// Resolve icon path for a DesktopEntries appId - safe on missing entries
function iconForAppId(appId, fallbackName) {
const fallback = fallbackName || "application-x-executable"
if (!appId)
return iconFromName(fallback, fallback)
try {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return iconFromName(fallback, fallback)
const entry = DesktopEntries.byId(appId)
const name = entry && entry.icon ? entry.icon : ""
return iconFromName(name || fallback, fallback)
} catch (e) {
return iconFromName(fallback, fallback)
// ---------------------------------------
function get(iconName) {
// Check in aliases first
if (aliases[iconName] !== undefined) {
iconName = aliases[iconName]
}
// Find the appropriate codepoint
return icons[iconName]
}
function loadFontWithCacheBusting() {
Logger.log("Icons", "Loading font with cache busting")
// Destroy old loader first
if (currentFontLoader) {
currentFontLoader.destroy()
currentFontLoader = null
}
// Create new loader with cache-busting URL
currentFontLoader = Qt.createQmlObject(`
import QtQuick
FontLoader {
source: "${cacheBustingPath}"
}
`, root, "dynamicFontLoader_" + fontVersion)
// Connect to the new loader's status changes
currentFontLoader.statusChanged.connect(function () {
if (currentFontLoader.status === FontLoader.Ready) {
Logger.log("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")")
fontReloaded()
} else if (currentFontLoader.status === FontLoader.Error) {
Logger.error("Icons", "Font failed to load (version " + fontVersion + ")")
}
})
}
function reloadFont() {
Logger.log("Icons", "Forcing font reload...")
fontVersion++
loadFontWithCacheBusting()
}
}

205
Commons/KeyboardLayout.qml Normal file
View File

@@ -0,0 +1,205 @@
pragma Singleton
import QtQuick
QtObject {
id: root
// Comprehensive language name to ISO code mapping
property var languageMap: {
"english"// English variants
: "us",
"american": "us",
"united states": "us",
"us english": "us",
"british": "gb",
"uk": "ua",
"united kingdom"// FIXED: Ukrainian language code should map to Ukraine
: "gb",
"english (uk)": "gb",
"canadian": "ca",
"canada": "ca",
"canadian english": "ca",
"australian": "au",
"australia": "au",
"swedish"// Nordic countries
: "se",
"svenska": "se",
"sweden": "se",
"norwegian": "no",
"norsk": "no",
"norway": "no",
"danish": "dk",
"dansk": "dk",
"denmark": "dk",
"finnish": "fi",
"suomi": "fi",
"finland": "fi",
"icelandic": "is",
"íslenska": "is",
"iceland": "is",
"german"// Western/Central European Germanic
: "de",
"deutsch": "de",
"germany": "de",
"austrian": "at",
"austria": "at",
"österreich": "at",
"swiss": "ch",
"switzerland": "ch",
"schweiz": "ch",
"suisse": "ch",
"dutch": "nl",
"nederlands": "nl",
"netherlands": "nl",
"holland": "nl",
"belgian": "be",
"belgium": "be",
"belgië": "be",
"belgique": "be",
"french"// Romance languages (Western/Southern Europe)
: "fr",
"français": "fr",
"france": "fr",
"canadian french": "ca",
"spanish": "es",
"español": "es",
"spain": "es",
"castilian": "es",
"italian": "it",
"italiano": "it",
"italy": "it",
"portuguese": "pt",
"português": "pt",
"portugal": "pt",
"catalan": "ad",
"català": "ad",
"andorra": "ad",
"romanian"// Eastern European Romance
: "ro",
"română": "ro",
"romania": "ro",
"russian"// Slavic languages (Eastern Europe)
: "ru",
"русский": "ru",
"russia": "ru",
"polish": "pl",
"polski": "pl",
"poland": "pl",
"czech": "cz",
"čeština": "cz",
"czech republic": "cz",
"slovak": "sk",
"slovenčina": "sk",
"slovakia": "sk",
"uk": "ua",
"ukrainian"// Ukrainian language code
: "ua",
"українська": "ua",
"ukraine": "ua",
"bulgarian": "bg",
"български": "bg",
"bulgaria": "bg",
"serbian": "rs",
"srpski": "rs",
"serbia": "rs",
"croatian": "hr",
"hrvatski": "hr",
"croatia": "hr",
"slovenian": "si",
"slovenščina": "si",
"slovenia": "si",
"bosnian": "ba",
"bosanski": "ba",
"bosnia": "ba",
"macedonian": "mk",
"македонски": "mk",
"macedonia": "mk",
"irish"// Celtic languages (Western Europe)
: "ie",
"gaeilge": "ie",
"ireland": "ie",
"welsh": "gb",
"cymraeg": "gb",
"wales": "gb",
"scottish": "gb",
"gàidhlig": "gb",
"scotland": "gb",
"estonian"// Baltic languages (Northern Europe)
: "ee",
"eesti": "ee",
"estonia": "ee",
"latvian": "lv",
"latviešu": "lv",
"latvia": "lv",
"lithuanian": "lt",
"lietuvių": "lt",
"lithuania": "lt",
"hungarian"// Other European languages
: "hu",
"magyar": "hu",
"hungary": "hu",
"greek": "gr",
"ελληνικά": "gr",
"greece": "gr",
"albanian": "al",
"shqip": "al",
"albania": "al",
"maltese": "mt",
"malti": "mt",
"malta": "mt",
"turkish"// West/Southwest Asian languages
: "tr",
"türkçe": "tr",
"turkey": "tr",
"arabic": "ar",
"العربية": "ar",
"arab": "ar",
"hebrew": "il",
"עברית": "il",
"israel": "il",
"brazilian"// South American languages
: "br",
"brazilian portuguese": "br",
"brasil": "br",
"brazil": "br",
"japanese"// East Asian languages
: "jp",
"日本語": "jp",
"japan": "jp",
"korean": "kr",
"한국어": "kr",
"korea": "kr",
"south korea": "kr",
"chinese": "cn",
"中文": "cn",
"china": "cn",
"simplified chinese": "cn",
"traditional chinese": "tw",
"taiwan": "tw",
"繁體中文": "tw",
"thai"// Southeast Asian languages
: "th",
"ไทย": "th",
"thailand": "th",
"vietnamese": "vn",
"tiếng việt": "vn",
"vietnam": "vn",
"hindi"// South Asian languages
: "in",
"हिन्दी": "in",
"india": "in",
"afrikaans"// African languages
: "za",
"south africa": "za",
"south african": "za",
"qwerty"// Layout variants
: "us",
"dvorak": "us",
"colemak": "us",
"workman": "us",
"azerty": "fr",
"norman": "fr",
"qwertz": "de"
}
}

View File

@@ -17,6 +17,14 @@ Singleton {
}
}
function _getStackTrace() {
try {
throw new Error("Stack trace")
} catch (e) {
return e.stack
}
}
function log(...args) {
var msg = _formatMessage(...args)
console.log(msg)
@@ -31,4 +39,20 @@ Singleton {
var msg = _formatMessage(...args)
console.error(msg)
}
function callStack() {
var stack = _getStackTrace()
Logger.log("Debug", "--------------------------")
Logger.log("Debug", "Current call stack")
// Split the stack into lines and log each one
var stackLines = stack.split('\n')
for (var i = 0; i < stackLines.length; i++) {
var line = stackLines[i].trim() // Remove leading/trailing whitespace
if (line.length > 0) {
// Only log non-empty lines
Logger.log("Debug", `- ${line}`)
}
}
Logger.log("Debug", "--------------------------")
}
}

View File

@@ -5,32 +5,369 @@ import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services
import "../Helpers/QtObj2JS.js" as QtObj2JS
Singleton {
id: root
// Used to access via Settings.data.xxx.yyy
readonly property alias data: adapter
property bool isLoaded: false
property bool directoriesCreated: 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 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 defaultWallpaper: Qt.resolvedUrl("../Assets/Tests/wallpaper.png")
property string defaultLocation: "Tokyo"
property string defaultWallpaper: Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png"
property string defaultAvatar: Quickshell.env("HOME") + "/.face"
property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos"
property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
// Used to access via Settings.data.xxx.yyy
property alias data: adapter
// Signal emitted when settings are loaded after startupcale changes
signal settingsLoaded
// Flag to prevent unnecessary wallpaper calls during reloads
property bool isInitialLoad: true
// -----------------------------------------------------
// -----------------------------------------------------
// Ensure directories exist before FileView tries to read files
Component.onCompleted: {
// ensure settings dir exists
Quickshell.execDetached(["mkdir", "-p", configDir])
Quickshell.execDetached(["mkdir", "-p", cacheDir])
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesWallpapers])
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesNotifications])
// Mark directories as created and trigger file loading
directoriesCreated = true
// This should only be activated once when the settings structure has changed
// Then it should be commented out again, regular users don't need to generate
// default settings on every start
// TODO: automate this someday!
// generateDefaultSettings()
// Patch-in the local default, resolved to user's home
adapter.general.avatarImage = defaultAvatar
adapter.screenRecorder.directory = defaultVideosDirectory
adapter.wallpaper.directory = defaultWallpapersDirectory
// Set the adapter to the settingsFileView to trigger the real settings load
settingsFileView.adapter = adapter
}
// Don't write settings to disk immediately
// This avoid excessive IO when a variable changes rapidly (ex: sliders)
Timer {
id: saveTimer
running: false
interval: 1000
onTriggered: {
settingsFileView.writeAdapter()
// Write to fallback location if set
if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
settingsFallbackFileView.writeAdapter()
}
}
}
FileView {
id: settingsFileView
path: directoriesCreated ? settingsFile : undefined
printErrors: false
watchChanges: true
onFileChanged: reload()
onAdapterUpdated: saveTimer.start()
// Trigger initial load when path changes from empty to actual path
onPathChanged: {
if (path !== undefined) {
reload()
}
}
onLoaded: function () {
if (!isLoaded) {
Logger.log("Settings", "Settings loaded")
upgradeSettingsData()
validateMonitorConfigurations()
isLoaded = true
// Emit the signal
root.settingsLoaded()
}
}
onLoadFailed: function (error) {
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()
}
}
}
}
// Fallback FileView for writing settings to alternate location
FileView {
id: settingsFallbackFileView
path: Quickshell.env("NOCTALIA_SETTINGS_FALLBACK") || ""
adapter: Quickshell.env("NOCTALIA_SETTINGS_FALLBACK") ? adapter : null
printErrors: false
watchChanges: false
}
JsonAdapter {
id: adapter
property int settingsVersion: 12
// bar
property JsonObject bar: JsonObject {
property string position: "top" // "top", "bottom", "left", or "right"
property real backgroundOpacity: 1.0
property list<string> monitors: []
property string density: "default" // "compact", "default", "comfortable"
property bool showCapsule: true
// Floating bar settings
property bool floating: false
property real marginVertical: 0.25
property real marginHorizontal: 0.25
// Widget configuration for modular bar system
property JsonObject widgets
widgets: JsonObject {
property list<var> left: [{
"id": "SystemMonitor"
}, {
"id": "ActiveWindow"
}, {
"id": "MediaMini"
}]
property list<var> center: [{
"id": "Workspace"
}]
property list<var> right: [{
"id": "ScreenRecorder"
}, {
"id": "Tray"
}, {
"id": "NotificationHistory"
}, {
"id": "WiFi"
}, {
"id": "Bluetooth"
}, {
"id": "Battery"
}, {
"id": "Volume"
}, {
"id": "Brightness"
}, {
"id": "Clock"
}, {
"id": "ControlCenter"
}]
}
}
// general
property JsonObject general: JsonObject {
property string avatarImage: ""
property bool dimDesktop: true
property bool showScreenCorners: false
property bool forceBlackScreenCorners: false
property real radiusRatio: 1.0
property real screenRadiusRatio: 1.0
property real animationSpeed: 1.0
property bool animationDisabled: false
}
// location
property JsonObject location: JsonObject {
property string name: defaultLocation
property bool useFahrenheit: false
property bool use12hourFormat: false
property bool showWeekNumberInCalendar: false
}
// screen recorder
property JsonObject screenRecorder: JsonObject {
property string directory: ""
property int frameRate: 60
property string audioCodec: "opus"
property string videoCodec: "h264"
property string quality: "very_high"
property string colorRange: "limited"
property bool showCursor: true
property string audioSource: "default_output"
property string videoSource: "portal"
}
// wallpaper
property JsonObject wallpaper: JsonObject {
property bool enabled: true
property string directory: ""
property bool enableMultiMonitorDirectories: false
property bool setWallpaperOnAllMonitors: true
property string fillMode: "crop"
property color fillColor: "#000000"
property bool randomEnabled: false
property int randomIntervalSec: 300 // 5 min
property int transitionDuration: 1500 // 1500 ms
property string transitionType: "random"
property real transitionEdgeSmoothness: 0.05
property list<var> monitors: []
}
// applauncher
property JsonObject appLauncher: JsonObject {
property bool enableClipboardHistory: false
// Position: center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center
property string position: "center"
property real backgroundOpacity: 1.0
property list<string> pinnedExecs: []
property bool useApp2Unit: false
property bool sortByMostUsed: true
property string terminalCommand: "xterm -e"
}
// dock
property JsonObject dock: JsonObject {
property bool autoHide: false
property bool exclusive: false
property real backgroundOpacity: 1.0
property real floatingRatio: 1.0
property bool onlySameOutput: true
property list<string> monitors: []
// Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop")
property list<string> pinnedApps: []
}
// network
property JsonObject network: JsonObject {
property bool wifiEnabled: true
}
// notifications
property JsonObject notifications: JsonObject {
property bool doNotDisturb: false
property list<string> monitors: []
property string location: "top_right"
property bool alwaysOnTop: false
property real lastSeenTs: 0
property bool respectExpireTimeout: false
property int lowUrgencyDuration: 3
property int normalUrgencyDuration: 8
property int criticalUrgencyDuration: 15
}
// on-screen display
property JsonObject osd: JsonObject {
property bool enabled: true
property string location: "top_right"
property list<string> monitors: []
property int autoHideMs: 2000
}
// audio
property JsonObject audio: JsonObject {
property int volumeStep: 5
property bool volumeOverdrive: false
property int cavaFrameRate: 60
property string visualizerType: "linear"
property list<string> mprisBlacklist: []
property string preferredPlayer: ""
}
// ui
property JsonObject ui: JsonObject {
property string fontDefault: "Roboto"
property string fontFixed: "DejaVu Sans Mono"
property real fontDefaultScale: 1.0
property real fontFixedScale: 1.0
property list<var> monitorsScaling: []
property bool idleInhibitorEnabled: false
}
// brightness
property JsonObject brightness: JsonObject {
property int brightnessStep: 5
}
property JsonObject colorSchemes: JsonObject {
property bool useWallpaperColors: false
property string predefinedScheme: "Noctalia (default)"
property bool darkMode: true
property string matugenSchemeType: "scheme-fruit-salad"
}
// matugen templates toggles
property JsonObject matugen: JsonObject {
// Per-template flags to control dynamic config generation
property bool gtk4: false
property bool gtk3: false
property bool qt6: false
property bool qt5: false
property bool kitty: false
property bool ghostty: false
property bool foot: false
property bool fuzzel: false
property bool vesktop: false
property bool pywalfox: false
property bool enableUserTemplates: false
}
// night light
property JsonObject nightLight: JsonObject {
property bool enabled: false
property bool forced: false
property bool autoSchedule: true
property string nightTemp: "4000"
property string dayTemp: "6500"
property string manualSunrise: "06:30"
property string manualSunset: "18:30"
}
// hooks
property JsonObject hooks: JsonObject {
property bool enabled: false
property string wallpaperChange: ""
property string darkModeChange: ""
}
}
// -----------------------------------------------------
// Generate default settings at the root of the repo
function generateDefaultSettings() {
try {
Logger.log("Settings", "Generating settings-default.json")
// Prepare a clean JSON
var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter)
var jsonData = JSON.stringify(plainAdapter, null, 2)
var defaultPath = Quickshell.shellDir + "/Assets/settings-default.json"
// Encode transfer it has base64 to avoid any escaping issue
var base64Data = Qt.btoa(jsonData)
Quickshell.execDetached(["sh", "-c", `echo "${base64Data}" | base64 -d > "${defaultPath}"`])
} catch (error) {
Logger.error("Settings", "Failed to generate default settings file: " + error)
}
}
// -----------------------------------------------------
// Function to validate monitor configurations
function validateMonitorConfigurations() {
var availableScreenNames = []
@@ -51,205 +388,149 @@ Singleton {
}
}
if (!hasValidBarMonitor) {
Logger.log("Settings",
"No configured bar monitors found on system, clearing bar monitor list to show on all screens")
Logger.warn("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens")
adapter.bar.monitors = []
} else {
Logger.log("Settings", "Found valid bar monitors, keeping configuration")
//Logger.log("Settings", "Found valid bar monitors, keeping configuration")
}
} else {
Logger.log("Settings", "Bar monitor list is empty, will show on all available screens")
}
}
Item {
Component.onCompleted: {
// ensure settings dir exists
Quickshell.execDetached(["mkdir", "-p", configDir])
Quickshell.execDetached(["mkdir", "-p", cacheDir])
Quickshell.execDetached(["mkdir", "-p", cacheDirImages])
//Logger.log("Settings", "Bar monitor list is empty, will show on all available screens")
}
}
// Don't write settings to disk immediately
// This avoid excessive IO when a variable changes rapidly (ex: sliders)
Timer {
id: saveTimer
running: false
interval: 1000
onTriggered: settingsFileView.writeAdapter()
}
FileView {
id: settingsFileView
path: settingsFile
watchChanges: true
onFileChanged: reload()
onAdapterUpdated: saveTimer.start()
Component.onCompleted: function () {
reload()
// -----------------------------------------------------
// 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.warn("Settings", "BarWidgetRegistry not ready, deferring upgrade")
Qt.callLater(upgradeSettingsData)
return
}
onLoaded: function () {
Qt.callLater(function () {
if (isInitialLoad) {
Logger.log("Settings", "OnLoaded")
// Only set wallpaper on initial load, not on reloads
if (adapter.wallpaper.current !== "") {
Logger.log("Settings", "Set current wallpaper", adapter.wallpaper.current)
WallpaperService.setCurrentWallpaper(adapter.wallpaper.current, true)
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.warn(`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.log("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
}
// Validate monitor configurations, only once
// if none of the configured monitors exist, clear the lists
validateMonitorConfigurations()
}
isInitialLoad = false
})
}
onLoadFailed: function (error) {
if (error.toString().includes("No such file") || error === 2)
// File doesn't exist, create it with default values
writeAdapter()
}
JsonAdapter {
id: adapter
// bar
property JsonObject bar: JsonObject {
property string position: "top" // Possible values: "top", "bottom"
property bool showActiveWindowIcon: true
property bool alwaysShowBatteryPercentage: false
property real backgroundOpacity: 1.0
property list<string> monitors: []
// Widget configuration for modular bar system
property JsonObject widgets
widgets: JsonObject {
property list<string> left: ["SystemMonitor", "ActiveWindow", "MediaMini"]
property list<string> center: ["Workspace"]
property list<string> right: ["ScreenRecorderIndicator", "Tray", "ArchUpdater", "NotificationHistory", "WiFi", "Bluetooth", "Battery", "Volume", "Brightness", "Clock", "SidePanelToggle"]
}
}
// general
property JsonObject general: JsonObject {
property string avatarImage: defaultAvatar
property bool dimDesktop: false
property bool showScreenCorners: false
property real radiusRatio: 1.0
}
// location
property JsonObject location: JsonObject {
property string name: "Tokyo"
property bool useFahrenheit: false
property bool reverseDayMonth: false
property bool use12HourClock: false
property bool showDateWithClock: false
}
// screen recorder
property JsonObject screenRecorder: JsonObject {
property string directory: "~/Videos"
property int frameRate: 60
property string audioCodec: "opus"
property string videoCodec: "h264"
property string quality: "very_high"
property string colorRange: "limited"
property bool showCursor: true
property string audioSource: "default_output"
property string videoSource: "portal"
}
// wallpaper
property JsonObject wallpaper: JsonObject {
property string directory: "/usr/share/wallpapers"
property string current: ""
property bool isRandom: false
property int randomInterval: 300
property JsonObject swww
onDirectoryChanged: WallpaperService.listWallpapers()
onIsRandomChanged: WallpaperService.toggleRandomWallpaper()
onRandomIntervalChanged: WallpaperService.restartRandomWallpaperTimer()
swww: JsonObject {
property bool enabled: false
property string resizeMethod: "crop"
property int transitionFps: 60
property string transitionType: "random"
property real transitionDuration: 1.1
}
}
// applauncher
property JsonObject appLauncher: JsonObject {
// When disabled, Launcher hides clipboard command and ignores cliphist
property bool enableClipboardHistory: true
// Position: center, top_left, top_right, bottom_left, bottom_right
property string position: "center"
property list<string> pinnedExecs: []
}
// dock
property JsonObject dock: JsonObject {
property bool autoHide: false
property bool exclusive: false
property list<string> monitors: []
}
// network
property JsonObject network: JsonObject {
property bool wifiEnabled: true
property bool bluetoothEnabled: true
}
// notifications
property JsonObject notifications: JsonObject {
property list<string> monitors: []
}
// audio
property JsonObject audio: JsonObject {
property bool showMiniplayerAlbumArt: false
property bool showMiniplayerCava: false
property string visualizerType: "linear"
property int volumeStep: 5
property int cavaFrameRate: 60
}
// ui
property JsonObject ui: JsonObject {
property string fontDefault: "Roboto" // Default font for all text
property string fontFixed: "DejaVu Sans Mono" // Fixed width font for terminal
property string fontBillboard: "Inter" // Large bold font for clocks and prominent displays
// Legacy compatibility
property string fontFamily: fontDefault // Keep for backward compatibility
// Idle inhibitor state
property bool idleInhibitorEnabled: false
}
// Scaling (not stored inside JsonObject, or it crashes)
property var monitorsScaling: {
}
// brightness
property JsonObject brightness: JsonObject {
property int brightnessStep: 5
}
property JsonObject colorSchemes: JsonObject {
property bool useWallpaperColors: false
property string predefinedScheme: ""
property bool darkMode: true
// External app theming (GTK & Qt)
property bool themeApps: false
if (!gotControlCenter) {
//const obj = JSON.parse('{"id": "ControlCenter"}');
adapter.bar.widgets["right"].push(({
"id": "ControlCenter"
}))
Logger.warn("Settings", "Added a ControlCenter widget to the right section")
}
}
}
// -----------------------------------------------------
function upgradeWidget(widget) {
// Backup the widget definition before altering
const widgetBefore = JSON.stringify(widget)
// Get all existing custom settings keys
const keys = Object.keys(BarWidgetRegistry.widgetMetadata[widget.id])
// Delete deprecated user settings from the wiget
for (const k of Object.keys(widget)) {
if (k === "id" || k === "allowUserSettings") {
continue
}
if (!keys.includes(k)) {
delete widget[k]
}
}
// Inject missing default setting (metaData) from BarWidgetRegistry
for (var i = 0; i < keys.length; i++) {
const k = keys[i]
if (k === "id" || k === "allowUserSettings") {
continue
}
if (widget[k] === undefined) {
widget[k] = BarWidgetRegistry.widgetMetadata[widget.id][k]
}
}
// Compare settings, to detect if something has been upgraded
const widgetAfter = JSON.stringify(widget)
return (widgetAfter !== widgetBefore)
}
}

View File

@@ -13,6 +13,7 @@ Singleton {
*/
// Font size
property real fontSizeXXS: 8
property real fontSizeXS: 9
property real fontSizeS: 10
property real fontSizeM: 11
@@ -28,11 +29,15 @@ Singleton {
property int fontWeightBold: 700
// Radii
property int radiusXXS: 4 * Settings.data.general.radiusRatio
property int radiusXS: 8 * Settings.data.general.radiusRatio
property int radiusS: 12 * Settings.data.general.radiusRatio
property int radiusM: 16 * Settings.data.general.radiusRatio
property int radiusL: 20 * Settings.data.general.radiusRatio
//screen Radii
property int screenRadius: 20 * Settings.data.general.screenRadiusRatio
// Border
property int borderS: 1
property int borderM: 2
@@ -55,18 +60,41 @@ Singleton {
property real opacityFull: 1.0
// Animation duration (ms)
property int animationFast: 150
property int animationNormal: 300
property int animationSlow: 450
// Dimensions
property int barHeight: 36
property int capsuleHeight: (barHeight * 0.73)
property int baseWidgetSize: 32
property int sliderWidth: 200
property int animationFast: Settings.data.general.animationDisabled ? 0 : Math.round(150 / Settings.data.general.animationSpeed)
property int animationNormal: Settings.data.general.animationDisabled ? 0 : Math.round(300 / Settings.data.general.animationSpeed)
property int animationSlow: Settings.data.general.animationDisabled ? 0 : Math.round(450 / Settings.data.general.animationSpeed)
property int animationSlowest: Settings.data.general.animationDisabled ? 0 : Math.round(750 / Settings.data.general.animationSpeed)
// Delays
property int tooltipDelay: 300
property int tooltipDelayLong: 1200
property int pillDelay: 500
// Settings widgets base size
property real baseWidgetSize: 33
property real sliderWidth: 200
// Bar Dimensions
property real barHeight: {
if (Settings.data.bar.density === "compact") {
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25
}
if (Settings.data.bar.density === "default") {
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31
}
if (Settings.data.bar.density === "comfortable") {
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37
}
}
property real capsuleHeight: {
if (Settings.data.bar.density === "compact") {
return barHeight * 0.85
}
if (Settings.data.bar.density === "default") {
return barHeight * 0.82
}
if (Settings.data.bar.density === "comfortable") {
return barHeight * 0.73
}
}
}

6169
Commons/TablerIcons.qml Normal file

File diff suppressed because it is too large Load Diff

53
Commons/ThemeIcons.qml Normal file
View File

@@ -0,0 +1,53 @@
pragma Singleton
import QtQuick
import Quickshell
import qs.Services
Singleton {
id: root
function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable"
try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback)
if (p && p !== "")
return p
}
} catch (e) {
// ignore and fall back
}
try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : ""
} catch (e2) {
return ""
}
}
// Resolve icon path for a DesktopEntries appId - safe on missing entries
function iconForAppId(appId, fallbackName) {
const fallback = fallbackName || "application-x-executable"
if (!appId)
return iconFromName(fallback, fallback)
try {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return iconFromName(fallback, fallback)
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId)
const name = entry && entry.icon ? entry.icon : ""
return iconFromName(name || fallback, fallback)
} catch (e) {
return iconFromName(fallback, fallback)
}
}
// Distro logo helper (absolute path or empty string)
function distroLogoPath() {
try {
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : ""
} catch (e) {
return ""
}
}
}

View File

@@ -8,99 +8,81 @@ import qs.Services
Singleton {
id: root
// Current date
property var date: new Date()
property string time: {
let timeFormat = Settings.data.location.use12HourClock ? "h:mm AP" : "HH:mm"
let timeString = Qt.formatDateTime(date, timeFormat)
if (Settings.data.location.showDateWithClock) {
let dayName = date.toLocaleDateString(Qt.locale(), "ddd")
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
let day = date.getDate()
let month = date.toLocaleDateString(Qt.locale(), "MMM")
return timeString + " - " + dayName + ", " + day + " " + month
}
return timeString
}
readonly property string dateString: {
let now = date
let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
let day = now.getDate()
let suffix
if (day > 3 && day < 21)
suffix = 'th'
else
switch (day % 10) {
case 1:
suffix = "st"
break
case 2:
suffix = "nd"
break
case 3:
suffix = "rd"
break
default:
suffix = "th"
}
let month = now.toLocaleDateString(Qt.locale(), "MMMM")
let year = now.toLocaleDateString(Qt.locale(), "yyyy")
return `${dayName}, `
+ (Settings.data.location.reverseDayMonth ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`)
}
// Returns a Unix Timestamp (in seconds)
readonly property int timestamp: {
return Math.floor(date / 1000)
}
/**
* Formats a Date object into a YYYYMMDD-HHMMSS string.
* @param {Date} [date=new Date()] - The date to format. Defaults to the current date and time.
* @returns {string} The formatted date string.
*/
function getFormattedTimestamp(date = new Date()) {
const year = date.getFullYear()
// getMonth() is zero-based, so we add 1
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}${month}${day}-${hours}${minutes}${seconds}`
}
// Format an easy to read approximate duration ex: 4h32m
// Used to display the time remaining on the Battery widget
function formatVagueHumanReadableDuration(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600)
const minutes = Math.floor((totalSeconds - (hours * 3600)) / 60)
const seconds = totalSeconds - (hours * 3600) - (minutes * 60)
var str = ""
if (hours) {
str += hours.toString() + "h"
Timer {
interval: 1000
repeat: true
running: true
onTriggered: root.date = new Date()
}
if (minutes) {
str += minutes.toString() + "m"
}
if (!hours && !minutes) {
str += seconds.toString() + "s"
}
return str
}
Timer {
interval: 1000
repeat: true
running: true
// Formats a Date object into a YYYYMMDD-HHMMSS string.
function getFormattedTimestamp(date) {
if (!date) {
date = new Date()
}
const year = date.getFullYear()
onTriggered: root.date = new Date()
}
// getMonth() is zero-based, so we add 1
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}${month}${day}-${hours}${minutes}${seconds}`
}
// Format an easy to read approximate duration ex: 4h32m
// Used to display the time remaining on the Battery widget, computer uptime, etc..
function formatVagueHumanReadableDuration(totalSeconds) {
if (typeof totalSeconds !== 'number' || totalSeconds < 0) {
return '0s'
}
// Floor the input to handle decimal seconds
totalSeconds = Math.floor(totalSeconds)
const days = Math.floor(totalSeconds / 86400)
const hours = Math.floor((totalSeconds % 86400) / 3600)
const minutes = Math.floor((totalSeconds % 3600) / 60)
const seconds = totalSeconds % 60
const parts = []
if (days)
parts.push(`${days}d`)
if (hours)
parts.push(`${hours}h`)
if (minutes)
parts.push(`${minutes}m`)
// Only show seconds if no hours and no minutes
if (!hours && !minutes) {
parts.push(`${seconds}s`)
}
return parts.join('')
}
// Format a date into
function formatRelativeTime(date) {
if (!date)
return ""
const diff = Date.now() - date.getTime()
if (diff < 60000)
return "now"
if (diff < 3600000)
return `${Math.floor(diff / 60000)}m ago`
if (diff < 86400000)
return `${Math.floor(diff / 3600000)}h ago`
return `${Math.floor(diff / 86400000)}d ago`
}
}

114
Helpers/QtObj2JS.js Normal file
View File

@@ -0,0 +1,114 @@
// -----------------------------------------------------
// Helper function to convert Qt objects to plain JavaScript objects
// Only used when generating settings-default.json
function qtObjectToPlainObject(obj) {
if (obj === null || obj === undefined) {
return obj;
}
// Handle primitive types
if (typeof obj !== "object") {
return obj;
}
// Handle native JavaScript arrays
if (Array.isArray(obj)) {
return obj.map((item) => qtObjectToPlainObject(item));
}
// Detect QML arrays FIRST (before color detection)
// QML arrays have a numeric length property and indexed properties
if (typeof obj.length === "number" && obj.length >= 0) {
// Check if it has indexed properties - be more flexible about detection
var hasIndexedProps = true;
var hasNumericKeys = false;
// Check if we have at least some numeric properties
for (var i = 0; i < obj.length; i++) {
if (obj.hasOwnProperty(i) || obj[i] !== undefined) {
hasNumericKeys = true;
break;
}
}
// If we have length > 0 and some numeric keys, treat as array
if (obj.length > 0 && hasNumericKeys) {
var arr = [];
for (var i = 0; i < obj.length; i++) {
// Use direct property access, handle undefined gracefully
var item = obj[i];
if (item !== undefined) {
arr.push(qtObjectToPlainObject(item));
}
}
return arr; // Return here to avoid processing as object
}
// Handle empty arrays (length = 0)
if (obj.length === 0) {
return [];
}
}
// Detect and convert QML color objects to hex strings
if (
typeof obj.r === "number" &&
typeof obj.g === "number" &&
typeof obj.b === "number" &&
typeof obj.a === "number" &&
typeof obj.valid === "boolean"
) {
// This looks like a QML color object
try {
// Try to get the string representation (should be hex like "#000000")
if (typeof obj.toString === "function") {
return obj.toString();
} else {
// Fallback: convert RGBA to hex manually
var r = Math.round(obj.r * 255);
var g = Math.round(obj.g * 255);
var b = Math.round(obj.b * 255);
var hex =
"#" +
r.toString(16).padStart(2, "0") +
g.toString(16).padStart(2, "0") +
b.toString(16).padStart(2, "0");
return hex;
}
} catch (e) {
// If conversion fails, fall through to regular object handling
}
}
// Handle regular objects
var plainObj = {};
// Get all property names, but filter out Qt-specific ones
var propertyNames = Object.getOwnPropertyNames(obj);
for (var i = 0; i < propertyNames.length; i++) {
var propName = propertyNames[i];
// Skip Qt-specific properties, functions, and array-like properties
if (
propName === "objectName" ||
propName === "objectNameChanged" ||
propName === "length" || // Skip length property
/^\d+$/.test(propName) || // Skip numeric keys (0, 1, 2, etc.)
propName.endsWith("Changed") ||
typeof obj[propName] === "function"
) {
continue;
}
try {
var value = obj[propName];
plainObj[propName] = qtObjectToPlainObject(value);
} catch (e) {
// Skip properties that can't be accessed
continue;
}
}
return plainObj;
}

View File

@@ -1,230 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
NPanel {
id: root
panelWidth: 380 * scaling
panelHeight: 500 * scaling
panelAnchorRight: true
// Auto-refresh when service updates
Connections {
target: ArchUpdaterService
function onUpdatePackagesChanged() {
// Force UI update when packages change
if (root.visible) {
// Small delay to ensure data is fully updated
Qt.callLater(() => {
// Force a UI update by triggering a property change
ArchUpdaterService.updatePackages = ArchUpdaterService.updatePackages
}, 100)
}
}
}
panelContent: Rectangle {
color: Color.mSurface
radius: Style.radiusL * scaling
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling
// Header
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
NIcon {
text: "system_update"
font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary
}
Text {
text: "System Updates"
font.pointSize: Style.fontSizeL * scaling
font.family: Settings.data.ui.fontDefault
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
}
NIconButton {
icon: "close"
tooltipText: "Close"
sizeMultiplier: 0.8
onClicked: root.close()
}
}
NDivider {
Layout.fillWidth: true
}
// Update summary
Text {
text: ArchUpdaterService.updatePackages.length + " package" + (ArchUpdaterService.updatePackages.length
!== 1 ? "s" : "") + " can be updated"
font.pointSize: Style.fontSizeL * scaling
font.family: Settings.data.ui.fontDefault
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
}
// Package selection info
Text {
text: ArchUpdaterService.selectedPackagesCount + " of " + ArchUpdaterService.updatePackages.length + " packages selected"
font.pointSize: Style.fontSizeS * scaling
font.family: Settings.data.ui.fontDefault
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
}
// Package list
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Color.mSurfaceVariant
radius: Style.radiusM * scaling
ListView {
id: packageListView
anchors.fill: parent
anchors.margins: Style.marginS * scaling
clip: true
model: ArchUpdaterService.updatePackages
spacing: Style.marginXS * scaling
delegate: Rectangle {
width: packageListView.width
height: 50 * scaling
color: Color.transparent
radius: Style.radiusS * scaling
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginS * scaling
spacing: Style.marginS * scaling
// Checkbox for selection
NIconButton {
id: checkbox
icon: "check_box_outline_blank"
onClicked: {
const isSelected = ArchUpdaterService.isPackageSelected(modelData.name)
if (isSelected) {
ArchUpdaterService.togglePackageSelection(modelData.name)
icon = "check_box_outline_blank"
colorFg = Color.mOnSurfaceVariant
} else {
ArchUpdaterService.togglePackageSelection(modelData.name)
icon = "check_box"
colorFg = Color.mPrimary
}
}
colorBg: Color.transparent
colorFg: Color.mOnSurfaceVariant
Layout.preferredWidth: 30 * scaling
Layout.preferredHeight: 30 * scaling
Component.onCompleted: {
// Set initial state
if (ArchUpdaterService.isPackageSelected(modelData.name)) {
icon = "check_box"
colorFg = Color.mPrimary
}
}
}
// Package info
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXXS * scaling
Text {
text: modelData.name
font.pointSize: Style.fontSizeM * scaling
font.family: Settings.data.ui.fontDefault
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
}
Text {
text: modelData.oldVersion + " → " + modelData.newVersion
font.pointSize: Style.fontSizeS * scaling
font.family: Settings.data.ui.fontDefault
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
}
}
}
}
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
}
}
// Action buttons
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS * scaling
NIconButton {
icon: "refresh"
tooltipText: "Check for updates"
onClicked: {
ArchUpdaterService.doPoll()
}
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
}
NIconButton {
icon: ArchUpdaterService.updateInProgress ? "hourglass_empty" : "system_update"
tooltipText: ArchUpdaterService.updateInProgress ? "Update in progress..." : "Update all packages"
enabled: !ArchUpdaterService.updateInProgress
onClicked: {
ArchUpdaterService.runUpdate()
root.close()
}
colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : Color.mPrimary
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : Color.mOnPrimary
Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
}
NIconButton {
icon: ArchUpdaterService.updateInProgress ? "hourglass_empty" : "settings"
tooltipText: ArchUpdaterService.updateInProgress ? "Update in progress..." : "Update selected packages"
enabled: !ArchUpdaterService.updateInProgress && ArchUpdaterService.selectedPackagesCount > 0
onClicked: {
if (ArchUpdaterService.selectedPackagesCount > 0) {
ArchUpdaterService.runSelectiveUpdate()
root.close()
}
}
colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : (ArchUpdaterService.selectedPackagesCount
> 0 ? Color.mSecondary : Color.mSurfaceVariant)
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : (ArchUpdaterService.selectedPackagesCount
> 0 ? Color.mOnSecondary : Color.mOnSurfaceVariant)
Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
}
}
}
}
}

View File

@@ -3,28 +3,89 @@ import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
Loader {
active: !Settings.data.wallpaper.swww.enabled
Variants {
id: backgroundVariants
model: Quickshell.screens
sourceComponent: Variants {
model: Quickshell.screens
delegate: Loader {
delegate: PanelWindow {
required property ShellScreen modelData
property string wallpaperSource: WallpaperService.currentWallpaper !== ""
&& !Settings.data.wallpaper.swww.enabled ? WallpaperService.currentWallpaper : ""
required property ShellScreen modelData
visible: wallpaperSource !== "" && !Settings.data.wallpaper.swww.enabled
active: Settings.isLoaded && modelData && Settings.data.wallpaper.enabled
// Force update when SWWW setting changes
onVisibleChanged: {
if (visible) {
sourceComponent: PanelWindow {
id: root
} else {
// Internal state management
property string transitionType: "fade"
property real transitionProgress: 0
readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness
readonly property var allTransitions: WallpaperService.allTransitions
readonly property bool transitioning: transitionAnimation.running
// Wipe direction: 0=left, 1=right, 2=up, 3=down
property real wipeDirection: 0
// Disc
property real discCenterX: 0.5
property real discCenterY: 0.5
// Stripe
property real stripesCount: 16
property real stripesAngle: 0
// Used to debounce wallpaper changes
property string futureWallpaper: ""
// Fillmode default is "crop"
property real fillMode: 1.0
property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0)
// On startup, defer assigning wallpaper until the service cache is ready
function _startWallpaperOnceReady() {
if (!modelData) {
Qt.callLater(_startWallpaperOnceReady)
return
}
var cacheReady = WallpaperService && WallpaperService.currentWallpapers && Object.keys(WallpaperService.currentWallpapers).length > 0
if (!cacheReady) {
// Try again on the next tick until WallpaperService.init() populates cache
Qt.callLater(_startWallpaperOnceReady)
return
}
fillMode = WallpaperService.getFillModeUniform()
var path = WallpaperService.getWallpaper(modelData.name)
setWallpaperImmediate(path)
}
Component.onCompleted: _startWallpaperOnceReady()
Connections {
target: Settings.data.wallpaper
function onFillModeChanged() {
fillMode = WallpaperService.getFillModeUniform()
}
}
// External state management
Connections {
target: WallpaperService
function onWallpaperChanged(screenName, path) {
if (screenName === modelData.name) {
// Update wallpaper display
// Set wallpaper immediately on startup
futureWallpaper = path
debounceTimer.restart()
}
}
}
color: Color.transparent
screen: modelData
WlrLayershell.layer: WlrLayer.Background
@@ -38,18 +99,228 @@ Loader {
left: true
}
margins {
top: 0
Timer {
id: debounceTimer
interval: 333
running: false
repeat: false
onTriggered: {
changeWallpaper()
}
}
Image {
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
source: wallpaperSource
visible: wallpaperSource !== ""
cache: true
id: currentWallpaper
source: ""
smooth: true
mipmap: false
visible: false
cache: false
asynchronous: true
}
Image {
id: nextWallpaper
source: ""
smooth: true
mipmap: false
visible: false
cache: false
asynchronous: true
}
// Fade or None transition shader
ShaderEffect {
id: fadeShader
anchors.fill: parent
visible: transitionType === "fade" || transitionType === "none"
property variant source1: currentWallpaper
property variant source2: nextWallpaper
property real progress: root.transitionProgress
// Fill mode properties
property real fillMode: root.fillMode
property vector4d fillColor: root.fillColor
property real imageWidth1: source1.sourceSize.width
property real imageHeight1: source1.sourceSize.height
property real imageWidth2: source2.sourceSize.width
property real imageHeight2: source2.sourceSize.height
property real screenWidth: width
property real screenHeight: height
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_fade.frag.qsb")
}
// Wipe transition shader
ShaderEffect {
id: wipeShader
anchors.fill: parent
visible: transitionType === "wipe"
property variant source1: currentWallpaper
property variant source2: nextWallpaper
property real progress: root.transitionProgress
property real smoothness: root.edgeSmoothness
property real direction: root.wipeDirection
// Fill mode properties
property real fillMode: root.fillMode
property vector4d fillColor: root.fillColor
property real imageWidth1: source1.sourceSize.width
property real imageHeight1: source1.sourceSize.height
property real imageWidth2: source2.sourceSize.width
property real imageHeight2: source2.sourceSize.height
property real screenWidth: width
property real screenHeight: height
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_wipe.frag.qsb")
}
// Disc reveal transition shader
ShaderEffect {
id: discShader
anchors.fill: parent
visible: transitionType === "disc"
property variant source1: currentWallpaper
property variant source2: nextWallpaper
property real progress: root.transitionProgress
property real smoothness: root.edgeSmoothness
property real aspectRatio: root.width / root.height
property real centerX: root.discCenterX
property real centerY: root.discCenterY
// Fill mode properties
property real fillMode: root.fillMode
property vector4d fillColor: root.fillColor
property real imageWidth1: source1.sourceSize.width
property real imageHeight1: source1.sourceSize.height
property real imageWidth2: source2.sourceSize.width
property real imageHeight2: source2.sourceSize.height
property real screenWidth: width
property real screenHeight: height
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_disc.frag.qsb")
}
// Diagonal stripes transition shader
ShaderEffect {
id: stripesShader
anchors.fill: parent
visible: transitionType === "stripes"
property variant source1: currentWallpaper
property variant source2: nextWallpaper
property real progress: root.transitionProgress
property real smoothness: root.edgeSmoothness
property real aspectRatio: root.width / root.height
property real stripeCount: root.stripesCount
property real angle: root.stripesAngle
// Fill mode properties
property real fillMode: root.fillMode
property vector4d fillColor: root.fillColor
property real imageWidth1: source1.sourceSize.width
property real imageHeight1: source1.sourceSize.height
property real imageWidth2: source2.sourceSize.width
property real imageHeight2: source2.sourceSize.height
property real screenWidth: width
property real screenHeight: height
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_stripes.frag.qsb")
}
// Animation for the transition progress
NumberAnimation {
id: transitionAnimation
target: root
property: "transitionProgress"
from: 0.0
to: 1.0
// The stripes shader feels faster visually, we make it a bit slower here.
duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration * 1.6 : Settings.data.wallpaper.transitionDuration
easing.type: Easing.InOutCubic
onFinished: {
// Swap images after transition completes
if (currentWallpaper.source !== "") {
currentWallpaper.source = ""
}
currentWallpaper.source = nextWallpaper.source
nextWallpaper.source = ""
transitionProgress = 0.0
Qt.callLater(() => {
currentWallpaper.asynchronous = true
})
}
}
function setWallpaperImmediate(source) {
transitionAnimation.stop()
transitionProgress = 0.0
if (currentWallpaper.source !== "") {
currentWallpaper.source = ""
}
currentWallpaper.source = source
nextWallpaper.source = ""
}
function setWallpaperWithTransition(source) {
if (source === currentWallpaper.source) {
return
}
if (transitioning) {
// We are interrupting a transition
transitionAnimation.stop()
transitionProgress = 0
currentWallpaper.source = nextWallpaper.source
nextWallpaper.source = ""
}
nextWallpaper.source = source
currentWallpaper.asynchronous = false
transitionAnimation.start()
}
// Main method that actually trigger the wallpaper change
function changeWallpaper() {
// Get the transitionType from the settings
transitionType = Settings.data.wallpaper.transitionType
if (transitionType == "random") {
var index = Math.floor(Math.random() * allTransitions.length)
transitionType = allTransitions[index]
}
// Ensure the transition type really exists
if (transitionType !== "none" && !allTransitions.includes(transitionType)) {
transitionType = "fade"
}
//Logger.log("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType)
switch (transitionType) {
case "none":
setWallpaperImmediate(futureWallpaper)
break
case "wipe":
wipeDirection = Math.random() * 4
setWallpaperWithTransition(futureWallpaper)
break
case "disc":
discCenterX = Math.random()
discCenterY = Math.random()
setWallpaperWithTransition(futureWallpaper)
break
case "stripes":
stripesCount = Math.round(Math.random() * 20 + 4)
stripesAngle = Math.random() * 360
setWallpaperWithTransition(futureWallpaper)
break
default:
setWallpaperWithTransition(futureWallpaper)
break
}
}
}
}

View File

@@ -6,24 +6,47 @@ import qs.Commons
import qs.Services
import qs.Widgets
Loader {
active: CompositorService.isNiri
Variants {
model: Quickshell.screens
Component.onCompleted: {
if (CompositorService.isNiri) {
Logger.log("Overview", "Loading Overview component for Niri")
}
}
delegate: Loader {
required property ShellScreen modelData
sourceComponent: Variants {
model: Quickshell.screens
active: CompositorService.isNiri && CompositorService.niriOverviewActive && modelData && Settings.data.wallpaper.enabled
delegate: PanelWindow {
required property ShellScreen modelData
property string wallpaperSource: WallpaperService.currentWallpaper !== ""
&& !Settings.data.wallpaper.swww.enabled ? WallpaperService.currentWallpaper : ""
property string wallpaper: ""
sourceComponent: PanelWindow {
Component.onCompleted: {
if (modelData) {
Logger.log("Overview", "Loading Overview component for Niri on", modelData.name)
}
updateWallpaper()
}
function updateWallpaper() {
wallpaper = modelData ? WallpaperService.getWallpaper(modelData.name) : ""
}
// External state management
Connections {
target: WallpaperService
function onWallpaperChanged(screenName, path) {
if (screenName === modelData.name) {
wallpaper = path
}
}
}
Connections {
target: WallpaperService
function onIsInitializedChanged() {
if (WallpaperService.isInitialized) {
updateWallpaper()
}
}
}
visible: wallpaperSource !== "" && !Settings.data.wallpaper.swww.enabled
color: Color.transparent
screen: modelData
WlrLayershell.layer: WlrLayer.Background
@@ -39,29 +62,27 @@ Loader {
Image {
id: bgImage
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
source: wallpaperSource
cache: true
source: wallpaper
smooth: true
mipmap: false
visible: wallpaperSource !== ""
cache: false
}
MultiEffect {
id: overviewBgBlur
anchors.fill: parent
source: bgImage
autoPaddingEnabled: false
blurEnabled: true
blur: 0.48
blurMax: 128
}
// Make the overview darker
Rectangle {
anchors.fill: parent
color: Qt.rgba(Color.mSurface.r, Color.mSurface.g, Color.mSurface.b, 0.5)
color: Settings.data.colorSchemes.darkMode ? Qt.alpha(Color.mSurface, Style.opacityMedium) : Qt.alpha(Color.mOnSurface, Style.opacityMedium)
}
}
}

View File

@@ -16,23 +16,26 @@ Loader {
id: root
required property ShellScreen modelData
readonly property real scaling: ScalingService.scale(screen)
property real scaling: ScalingService.getScreenScale(screen)
screen: modelData
// Visible color
property color ringColor: Qt.rgba(Color.mSurface.r, Color.mSurface.g, Color.mSurface.b,
Settings.data.bar.backgroundOpacity)
// The amount subtracted from full size for the inner cutout
// Inner size = full size - borderWidth (per axis)
property int borderWidth: Style.borderM
// Rounded radius for the inner cutout
property int innerRadius: 20
property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Qt.rgba(0, 0, 0, 1) : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
property real cornerRadius: Style.screenRadius * scaling
property real cornerSize: Style.screenRadius * scaling
Connections {
target: ScalingService
function onScaleChanged(screenName, scale) {
if (screenName === screen.name) {
scaling = scale
}
}
}
color: Color.transparent
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "quickshell-corner"
// Do not take keyboard focus and make the surface click-through
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
@@ -43,111 +46,205 @@ Loader {
}
margins {
top: ((modelData && Settings.data.bar.monitors.includes(modelData.name))
|| (Settings.data.bar.monitors.length === 0))
&& Settings.data.bar.position === "top" ? Math.floor(Style.barHeight * scaling) : 0
bottom: ((modelData && Settings.data.bar.monitors.includes(modelData.name))
|| (Settings.data.bar.monitors.length === 0))
&& Settings.data.bar.position === "bottom" ? Math.floor(Style.barHeight * scaling) : 0
}
// Source we want to show only as a ring
Rectangle {
id: overlaySource
anchors.fill: parent
color: root.ringColor
}
// Texture for overlaySource
ShaderEffectSource {
id: overlayTexture
anchors.fill: parent
sourceItem: overlaySource
hideSource: true
live: true
visible: false
}
// Mask via Canvas: paint opaque white, then punch rounded inner hole
Canvas {
id: maskSource
anchors.fill: parent
antialiasing: true
renderTarget: Canvas.FramebufferObject
onPaint: {
const ctx = getContext("2d")
ctx.reset()
ctx.clearRect(0, 0, width, height)
// Solid white base (alpha=1)
ctx.globalCompositeOperation = "source-over"
ctx.fillStyle = "#ffffffff"
ctx.fillRect(0, 0, width, height)
// Punch hole using destination-out with rounded rect path
const x = Math.round(root.borderWidth / 2)
const y = Math.round(root.borderWidth / 2)
const w = Math.max(0, width - root.borderWidth)
const h = Math.max(0, height - root.borderWidth)
const r = Math.max(0, Math.min(root.innerRadius, Math.min(w, h) / 2))
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = "#ffffffff"
ctx.beginPath()
// rounded rectangle path using arcTo
ctx.moveTo(x + r, y)
ctx.lineTo(x + w - r, y)
ctx.arcTo(x + w, y, x + w, y + r, r)
ctx.lineTo(x + w, y + h - r)
ctx.arcTo(x + w, y + h, x + w - r, y + h, r)
ctx.lineTo(x + r, y + h)
ctx.arcTo(x, y + h, x, y + h - r, r)
ctx.lineTo(x, y + r)
ctx.arcTo(x, y, x + r, y, r)
ctx.closePath()
ctx.fill()
}
onWidthChanged: requestPaint()
onHeightChanged: requestPaint()
}
// Repaint mask when properties change
Connections {
function onBorderWidthChanged() {
maskSource.requestPaint()
}
function onRingColorChanged() {}
function onInnerRadiusChanged() {
maskSource.requestPaint()
}
target: root
}
// Texture for maskSource; hides the original
ShaderEffectSource {
id: maskTexture
anchors.fill: parent
sourceItem: maskSource
hideSource: true
live: true
visible: false
}
// Apply mask to show only the ring area
MultiEffect {
anchors.fill: parent
source: overlayTexture
maskEnabled: true
maskSource: maskTexture
maskInverted: false
maskSpreadAtMax: 0.75
// When bar is floating, corners should be at screen edges (no margins)
// When bar is not floating, respect bar margins as before
top: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "top" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
bottom: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "bottom" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
left: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "left" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
right: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "right" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
}
mask: Region {}
// Top-left concave corner
Canvas {
id: topLeftCorner
anchors.top: parent.top
anchors.left: parent.left
width: cornerSize
height: cornerSize
antialiasing: true
renderTarget: Canvas.FramebufferObject
smooth: false
onPaint: {
const ctx = getContext("2d")
if (!ctx)
return
ctx.reset()
ctx.clearRect(0, 0, width, height)
// Fill the entire area with the corner color
ctx.fillStyle = root.cornerColor
ctx.fillRect(0, 0, width, height)
// Cut out the rounded corner using destination-out
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = "#ffffff"
ctx.beginPath()
ctx.arc(width, height, root.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
}
onWidthChanged: if (available)
requestPaint()
onHeightChanged: if (available)
requestPaint()
Connections {
target: root
function onCornerColorChanged() {
if (topLeftCorner.available)
topLeftCorner.requestPaint()
}
function onCornerRadiusChanged() {
if (topLeftCorner.available)
topLeftCorner.requestPaint()
}
}
}
// Top-right concave corner
Canvas {
id: topRightCorner
anchors.top: parent.top
anchors.right: parent.right
width: cornerSize
height: cornerSize
antialiasing: true
renderTarget: Canvas.FramebufferObject
smooth: true
onPaint: {
const ctx = getContext("2d")
if (!ctx)
return
ctx.reset()
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = root.cornerColor
ctx.fillRect(0, 0, width, height)
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = "#ffffff"
ctx.beginPath()
ctx.arc(0, height, root.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
}
onWidthChanged: if (available)
requestPaint()
onHeightChanged: if (available)
requestPaint()
Connections {
target: root
function onCornerColorChanged() {
if (topRightCorner.available)
topRightCorner.requestPaint()
}
function onCornerRadiusChanged() {
if (topRightCorner.available)
topRightCorner.requestPaint()
}
}
}
// Bottom-left concave corner
Canvas {
id: bottomLeftCorner
anchors.bottom: parent.bottom
anchors.left: parent.left
width: cornerSize
height: cornerSize
antialiasing: true
renderTarget: Canvas.FramebufferObject
smooth: true
onPaint: {
const ctx = getContext("2d")
if (!ctx)
return
ctx.reset()
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = root.cornerColor
ctx.fillRect(0, 0, width, height)
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = "#ffffff"
ctx.beginPath()
ctx.arc(width, 0, root.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
}
onWidthChanged: if (available)
requestPaint()
onHeightChanged: if (available)
requestPaint()
Connections {
target: root
function onCornerColorChanged() {
if (bottomLeftCorner.available)
bottomLeftCorner.requestPaint()
}
function onCornerRadiusChanged() {
if (bottomLeftCorner.available)
bottomLeftCorner.requestPaint()
}
}
}
// Bottom-right concave corner
Canvas {
id: bottomRightCorner
anchors.bottom: parent.bottom
anchors.right: parent.right
width: cornerSize
height: cornerSize
antialiasing: true
renderTarget: Canvas.FramebufferObject
smooth: true
onPaint: {
const ctx = getContext("2d")
if (!ctx)
return
ctx.reset()
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = root.cornerColor
ctx.fillRect(0, 0, width, height)
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = "#ffffff"
ctx.beginPath()
ctx.arc(0, 0, root.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
}
onWidthChanged: if (available)
requestPaint()
onHeightChanged: if (available)
requestPaint()
Connections {
target: root
function onCornerColorChanged() {
if (bottomRightCorner.available)
bottomRightCorner.requestPaint()
}
function onCornerRadiusChanged() {
if (bottomRightCorner.available)
bottomRightCorner.requestPaint()
}
}
}
}
}
}

View File

@@ -8,119 +8,251 @@ import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.Notification
import qs.Modules.Bar.Extras
Variants {
model: Quickshell.screens
delegate: PanelWindow {
delegate: Loader {
id: root
required property ShellScreen modelData
readonly property real scaling: ScalingService.scale(screen)
screen: modelData
property real scaling: ScalingService.getScreenScale(modelData)
WlrLayershell.namespace: "noctalia-bar"
implicitHeight: Style.barHeight * scaling
color: Color.transparent
// If no bar activated in settings, then show them all
visible: modelData ? (Settings.data.bar.monitors.includes(modelData.name)
|| (Settings.data.bar.monitors.length === 0)) : false
anchors {
top: Settings.data.bar.position === "top"
bottom: Settings.data.bar.position === "bottom"
left: true
right: true
Connections {
target: ScalingService
function onScaleChanged(screenName, scale) {
if ((modelData !== null) && (screenName === modelData.name)) {
scaling = scale
}
}
}
Item {
anchors.fill: parent
clip: true
active: BarService.isVisible && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false
// Background fill
Rectangle {
id: bar
sourceComponent: PanelWindow {
screen: modelData || null
WlrLayershell.namespace: "noctalia-bar"
implicitHeight: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? screen.height : Math.round(Style.barHeight * scaling)
implicitWidth: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? Math.round(Style.barHeight * scaling) : screen.width
color: Color.transparent
anchors {
top: Settings.data.bar.position === "top" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
bottom: Settings.data.bar.position === "bottom" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
left: Settings.data.bar.position === "left" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
right: Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
}
// Floating bar margins - only apply when floating is enabled
// Also don't apply margin on the opposite side ot the bar orientation, ex: if bar is floating on top, margin is only applied on top, not bottom.
margins {
top: Settings.data.bar.floating && Settings.data.bar.position !== "bottom" ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
bottom: Settings.data.bar.floating && Settings.data.bar.position !== "top" ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
left: Settings.data.bar.floating && Settings.data.bar.position !== "right" ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
right: Settings.data.bar.floating && Settings.data.bar.position !== "left" ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
}
Component.onCompleted: {
if (modelData && modelData.name) {
BarService.registerBar(modelData.name)
}
}
Item {
anchors.fill: parent
color: Qt.rgba(Color.mSurface.r, Color.mSurface.g, Color.mSurface.b, Settings.data.bar.backgroundOpacity)
layer.enabled: true
}
clip: true
// ------------------------------
// Left Section - Dynamic Widgets
Row {
id: leftSection
// Background fill with shadow
Rectangle {
id: bar
height: parent.height
anchors.left: parent.left
anchors.leftMargin: Style.marginS * scaling
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling
anchors.fill: parent
color: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
Repeater {
model: Settings.data.bar.widgets.left
delegate: Loader {
active: true
sourceComponent: NWidgetLoader {
widgetName: modelData
widgetProps: {
"screen": screen
}
// Floating bar rounded corners
radius: Settings.data.bar.floating ? Style.radiusL : 0
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: function (mouse) {
if (mouse.button === Qt.RightButton) {
controlCenterPanel.toggle(BarService.lookupWidget("ControlCenter"))
mouse.accepted = true
}
anchors.verticalCenter: parent.verticalCenter
}
}
}
// ------------------------------
// Center Section - Dynamic Widgets
Row {
id: centerSection
Loader {
anchors.fill: parent
sourceComponent: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? verticalBarComponent : horizontalBarComponent
}
height: parent.height
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling
// For vertical bars
Component {
id: verticalBarComponent
Item {
anchors.fill: parent
Repeater {
model: Settings.data.bar.widgets.center
delegate: Loader {
active: true
sourceComponent: NWidgetLoader {
widgetName: modelData
widgetProps: {
"screen": screen
// Top section (left widgets)
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: Style.marginM * root.scaling
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.left
delegate: BarWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "left",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
}
Layout.alignment: Qt.AlignHCenter
}
}
}
// Center section (center widgets)
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.center
delegate: BarWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "center",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
}
Layout.alignment: Qt.AlignHCenter
}
}
}
// Bottom section (right widgets)
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.marginM * root.scaling
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.right
delegate: BarWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "right",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
}
Layout.alignment: Qt.AlignHCenter
}
}
}
anchors.verticalCenter: parent.verticalCenter
}
}
}
// ------------------------------
// Right Section - Dynamic Widgets
Row {
id: rightSection
// For horizontal bars
Component {
id: horizontalBarComponent
Item {
anchors.fill: parent
height: parent.height
anchors.right: bar.right
anchors.rightMargin: Style.marginS * scaling
anchors.verticalCenter: bar.verticalCenter
spacing: Style.marginS * scaling
// Left Section
RowLayout {
id: leftSection
objectName: "leftSection"
anchors.left: parent.left
anchors.leftMargin: Style.marginS * root.scaling
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.right
delegate: Loader {
active: true
sourceComponent: NWidgetLoader {
widgetName: modelData
widgetProps: {
"screen": screen
Repeater {
model: Settings.data.bar.widgets.left
delegate: BarWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "left",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
}
Layout.alignment: Qt.AlignVCenter
}
}
}
// Center Section
RowLayout {
id: centerSection
objectName: "centerSection"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.center
delegate: BarWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "center",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
}
Layout.alignment: Qt.AlignVCenter
}
}
}
// Right Section
RowLayout {
id: rightSection
objectName: "rightSection"
anchors.right: parent.right
anchors.rightMargin: Style.marginS * root.scaling
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * root.scaling
Repeater {
model: Settings.data.bar.widgets.right
delegate: BarWidgetLoader {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "right",
"sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
}
Layout.alignment: Qt.AlignVCenter
}
}
}
anchors.verticalCenter: parent.verticalCenter
}
}
}

View File

@@ -0,0 +1,181 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Bluetooth
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
ColumnLayout {
id: root
property string label: ""
property string tooltipText: ""
property var model: {
}
Layout.fillWidth: true
spacing: Style.marginM * scaling
NText {
text: root.label
pointSize: Style.fontSizeL * scaling
color: Color.mSecondary
font.weight: Style.fontWeightMedium
Layout.fillWidth: true
visible: root.model.length > 0
}
Repeater {
id: deviceList
Layout.fillWidth: true
model: root.model
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Rectangle {
id: device
readonly property bool canConnect: BluetoothService.canConnect(modelData)
readonly property bool canDisconnect: BluetoothService.canDisconnect(modelData)
readonly property bool isBusy: BluetoothService.isDeviceBusy(modelData)
function getContentColor(defaultColor = Color.mOnSurface) {
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
return Color.mPrimary
if (modelData.blocked)
return Color.mError
return defaultColor
}
Layout.fillWidth: true
Layout.preferredHeight: deviceLayout.implicitHeight + (Style.marginM * scaling * 2)
radius: Style.radiusM * scaling
color: Color.mSurface
border.width: Math.max(1, Style.borderS * scaling)
border.color: getContentColor(Color.mOutline)
RowLayout {
id: deviceLayout
anchors.fill: parent
anchors.margins: Style.marginM * scaling
spacing: Style.marginM * scaling
Layout.alignment: Qt.AlignVCenter
// One device BT icon
NIcon {
icon: BluetoothService.getDeviceIcon(modelData)
pointSize: Style.fontSizeXXL * scaling
color: getContentColor(Color.mOnSurface)
Layout.alignment: Qt.AlignVCenter
}
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXXS * scaling
// Device name
NText {
text: modelData.name || modelData.deviceName
pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightMedium
elide: Text.ElideRight
color: getContentColor(Color.mOnSurface)
Layout.fillWidth: true
}
// Status
NText {
text: BluetoothService.getStatusString(modelData)
visible: text !== ""
pointSize: Style.fontSizeXS * scaling
color: getContentColor(Color.mOnSurfaceVariant)
}
// Signal Strength
RowLayout {
visible: modelData.signalStrength !== undefined
Layout.fillWidth: true
spacing: Style.marginXS * scaling
// Device signal strength - "Unknown" when not connected
NText {
text: BluetoothService.getSignalStrength(modelData)
pointSize: Style.fontSizeXS * scaling
color: getContentColor(Color.mOnSurfaceVariant)
}
NIcon {
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
text: BluetoothService.getSignalIcon(modelData)
pointSize: Style.fontSizeXS * scaling
color: getContentColor(Color.mOnSurface)
}
NText {
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
pointSize: Style.fontSizeXS * scaling
color: getContentColor(Color.mOnSurface)
}
}
// Battery
NText {
visible: modelData.batteryAvailable
text: BluetoothService.getBattery(modelData)
pointSize: Style.fontSizeXS * scaling
color: getContentColor(Color.mOnSurfaceVariant)
}
}
// Spacer to push connect button to the right
Item {
Layout.fillWidth: true
}
// Call to action
NButton {
id: button
visible: (modelData.state !== BluetoothDeviceState.Connecting)
enabled: (canConnect || canDisconnect) && !isBusy
outlined: !button.hovered
fontSize: Style.fontSizeXS * scaling
fontWeight: Style.fontWeightMedium
backgroundColor: {
if (device.canDisconnect && !isBusy) {
return Color.mError
}
return Color.mPrimary
}
tooltipText: root.tooltipText
text: {
if (modelData.pairing) {
return "Pairing..."
}
if (modelData.blocked) {
return "Blocked"
}
if (modelData.connected) {
return "Disconnect"
}
return "Connect"
}
icon: (isBusy ? "busy" : null)
onClicked: {
if (modelData.connected) {
BluetoothService.disconnectDevice(modelData)
} else {
BluetoothService.connectDeviceWithTrust(modelData)
}
}
onRightClicked: {
BluetoothService.forgetDevice(modelData)
}
}
}
}
}
}

View File

@@ -0,0 +1,223 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Bluetooth
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
NPanel {
id: root
preferredWidth: 380
preferredHeight: 500
panelKeyboardFocus: true
panelContent: Rectangle {
color: Color.transparent
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling
// HEADER
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
NIcon {
icon: "bluetooth"
pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary
}
NText {
text: I18n.tr("bluetooth.panel.title")
pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
}
NToggle {
id: bluetoothSwitch
checked: BluetoothService.enabled
onToggled: checked => BluetoothService.setBluetoothEnabled(checked)
baseSize: Style.baseWidgetSize * 0.65 * scaling
}
NIconButton {
enabled: BluetoothService.enabled
icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "refresh"
tooltipText: I18n.tr("tooltips.refresh-devices")
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
if (BluetoothService.adapter) {
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering
}
}
}
NIconButton {
icon: "close"
tooltipText: I18n.tr("tooltips.close")
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
root.close()
}
}
}
NDivider {
Layout.fillWidth: true
}
Rectangle {
visible: !(BluetoothService.adapter && BluetoothService.adapter.enabled)
Layout.fillWidth: true
Layout.fillHeight: true
color: Color.transparent
// Center the content within this rectangle
ColumnLayout {
anchors.centerIn: parent
spacing: Style.marginM * scaling
NIcon {
icon: "bluetooth-off"
pointSize: 64 * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("bluetooth.panel.disabled")
pointSize: Style.fontSizeL * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("bluetooth.panel.enable-message")
pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
}
}
NScrollView {
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Layout.fillWidth: true
Layout.fillHeight: true
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
clip: true
contentWidth: availableWidth
ColumnLayout {
width: parent.width
spacing: Style.marginM * scaling
// Connected devices
BluetoothDevicesList {
label: I18n.tr("bluetooth.panel.connected-devices")
property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices)
return []
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected)
return BluetoothService.sortDevices(filtered)
}
model: items
visible: items.length > 0
Layout.fillWidth: true
}
// Known devices
BluetoothDevicesList {
label: I18n.tr("bluetooth.panel.known-devices")
tooltipText: I18n.tr("tooltips.connect-disconnect-devices")
property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices)
return []
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted))
return BluetoothService.sortDevices(filtered)
}
model: items
visible: items.length > 0
Layout.fillWidth: true
}
// Available devices
BluetoothDevicesList {
label: I18n.tr("bluetooth.panel.available-devices")
property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices)
return []
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted)
return BluetoothService.sortDevices(filtered)
}
model: items
visible: items.length > 0
Layout.fillWidth: true
}
// Fallback - No devices, scanning
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginM * scaling
visible: {
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) {
return false
}
var availableCount = Bluetooth.devices.values.filter(dev => {
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0)
}).length
return (availableCount === 0)
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginXS * scaling
NIcon {
icon: "refresh"
pointSize: Style.fontSizeXXL * 1.5 * scaling
color: Color.mPrimary
RotationAnimation on rotation {
running: true
loops: Animation.Infinite
from: 0
to: 360
duration: Style.animationSlow * 4
}
}
NText {
text: I18n.tr("bluetooth.panel.scanning")
pointSize: Style.fontSizeL * scaling
color: Color.mOnSurface
}
}
NText {
text: I18n.tr("bluetooth.panel.pairing-mode")
pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
}
Item {
Layout.fillHeight: true
}
}
}
}
}
}

View File

@@ -0,0 +1,270 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
NPanel {
id: root
preferredWidth: Settings.data.location.showWeekNumberInCalendar ? 320 : 300
preferredHeight: 300
// Main Column
panelContent: ColumnLayout {
id: content
anchors.fill: parent
anchors.margins: Style.marginM * scaling
spacing: Style.marginXS * scaling
readonly property int firstDayOfWeek: Qt.locale().firstDayOfWeek
// Header: Month/Year with navigation
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.marginM * scaling
Layout.rightMargin: Style.marginM * scaling
spacing: Style.marginS * scaling
NIconButton {
icon: "chevron-left"
tooltipText: I18n.tr("tooltips.previous-month")
onClicked: {
let newDate = new Date(grid.year, grid.month - 1, 1)
grid.year = newDate.getFullYear()
grid.month = newDate.getMonth()
}
}
NText {
text: grid.title
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
NIconButton {
icon: "chevron-right"
tooltipText: I18n.tr("tooltips.next-month")
onClicked: {
let newDate = new Date(grid.year, grid.month + 1, 1)
grid.year = newDate.getFullYear()
grid.month = newDate.getMonth()
}
}
}
// Divider between header and weekdays
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginS * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Columns label (respects locale's first day of week)
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.marginS * scaling // Align with grid
Layout.rightMargin: Style.marginS * scaling
Layout.bottomMargin: Style.marginM * scaling
spacing: 0
// Week header spacer or label (same width as week number column)
Item {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * scaling : 0
NText {
anchors.centerIn: parent
text: I18n.tr("calendar.panel.week")
color: Color.mOutline
pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightRegular
horizontalAlignment: Text.AlignHCenter
}
}
// Day name headers - now properly aligned with calendar grid
GridLayout {
Layout.fillWidth: true
Layout.fillHeight: true
columns: 7
rows: 1
columnSpacing: 0
rowSpacing: 0
Repeater {
model: 7
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredWidth: Style.baseWidgetSize * scaling
NText {
anchors.centerIn: parent
text: {
let dayIndex = (content.firstDayOfWeek + index) % 7
return Qt.locale().dayName(dayIndex, Locale.ShortFormat)
}
color: Color.mSecondary
pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
// Grids: days with optional week numbers
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: Style.marginS * scaling
Layout.rightMargin: Style.marginS * scaling
spacing: 0
// Week numbers column (only visible when enabled)
ColumnLayout {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * scaling : 0
Layout.fillHeight: true
spacing: 0
Repeater {
model: 6 // Maximum 6 weeks in a month view
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: Style.baseWidgetSize * scaling
NText {
anchors.centerIn: parent
color: Color.mOutline
pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
text: {
// Calculate the date shown in the first column of this row
// MonthGrid always shows 42 days (6 weeks × 7 days)
// First, find the first day of the month
let firstOfMonth = new Date(grid.year, grid.month, 1)
// Calculate how many days before the 1st to start the grid
// This depends on the locale's first day of week
let firstDayOfWeek = content.firstDayOfWeek
let firstOfMonthDayOfWeek = firstOfMonth.getDay()
// Calculate offset: how many days before the 1st should the grid start?
let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7
// MonthGrid typically shows the previous month's days to fill the first week
// If the 1st is already on the first day of week, show the previous week
if (daysBeforeFirst === 0) {
daysBeforeFirst = 7
}
// Calculate the start date of the grid
let gridStartDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst)
// Calculate the date for this specific row (week)
let rowStartDate = new Date(gridStartDate)
rowStartDate.setDate(gridStartDate.getDate() + (index * 7))
// For ISO week numbers, we need to find the Thursday of this week
// ISO 8601 week numbering: week with year's first Thursday is week 1
// The week number is determined by the Thursday
// Find the Thursday of this row's week
// If firstDayOfWeek is Monday (1), Thursday is +3 days
// If firstDayOfWeek is Sunday (0), we need to adjust
let thursday = new Date(rowStartDate)
if (firstDayOfWeek === 0) {
// Sunday start: Thursday is 4 days after Sunday
thursday.setDate(rowStartDate.getDate() + 4)
} else if (firstDayOfWeek === 1) {
// Monday start: Thursday is 3 days after Monday
thursday.setDate(rowStartDate.getDate() + 3)
} else {
// Other start days: calculate offset to Thursday
let daysToThursday = (4 - firstDayOfWeek + 7) % 7
thursday.setDate(rowStartDate.getDate() + daysToThursday)
}
return `${getISOWeekNumber(thursday)}`
}
}
}
}
}
// The actual calendar grid
MonthGrid {
id: grid
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
month: Time.date.getMonth()
year: Time.date.getFullYear()
locale: Qt.locale()
delegate: Item {
Rectangle {
width: Style.baseWidgetSize * scaling
height: Style.baseWidgetSize * scaling
radius: width / 2
color: model.today ? Color.mPrimary : Color.transparent
NText {
anchors.centerIn: parent
text: model.day
color: model.today ? Color.mOnPrimary : Color.mOnSurface
opacity: model.month === grid.month ? Style.opacityHeavy : Style.opacityLight
pointSize: Style.fontSizeM * scaling
font.weight: model.today ? Style.fontWeightBold : Style.fontWeightRegular
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
}
}
}
// ISO 8601 week number calculation
// This is locale-independent and always uses Monday as first day of week
function getISOWeekNumber(date) {
// Create a copy and set to nearest Thursday (current date + 4 - current day number)
// ISO week starts on Monday (1) to Sunday (7)
const target = new Date(date.getTime())
target.setHours(0, 0, 0, 0)
// Get day of week where Monday = 1, Sunday = 7
const dayOfWeek = target.getDay() || 7
// Set to nearest Thursday (which determines the week number)
target.setDate(target.getDate() + 4 - dayOfWeek)
// Get first day of year
const yearStart = new Date(target.getFullYear(), 0, 1)
// Calculate full weeks between yearStart and target
// Add 1 because we're counting weeks, not week differences
const weekNumber = Math.ceil(((target - yearStart) / 86400000 + 1) / 7)
return weekNumber
}
}

View File

@@ -0,0 +1,116 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
Item {
id: root
property ShellScreen screen
property string icon: ""
property string text: ""
property string suffix: ""
property string tooltipText: ""
property bool autoHide: false
property bool forceOpen: false
property bool forceClose: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
property bool compact: false
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
signal shown
signal hidden
signal entered
signal exited
signal clicked
signal rightClicked
signal middleClicked
signal wheel(int delta)
// Dynamic sizing based on loaded component
width: pillLoader.item ? pillLoader.item.width : 0
height: pillLoader.item ? pillLoader.item.height : 0
// Loader to switch between vertical and horizontal pill implementations
Loader {
id: pillLoader
sourceComponent: isVerticalBar ? verticalPillComponent : horizontalPillComponent
Component {
id: verticalPillComponent
BarPillVertical {
screen: root.screen
icon: root.icon
text: root.text
suffix: root.suffix
tooltipText: root.tooltipText
autoHide: root.autoHide
forceOpen: root.forceOpen
forceClose: root.forceClose
disableOpen: root.disableOpen
rightOpen: root.rightOpen
hovered: root.hovered
compact: root.compact
onShown: root.shown()
onHidden: root.hidden()
onEntered: root.entered()
onExited: root.exited()
onClicked: root.clicked()
onRightClicked: root.rightClicked()
onMiddleClicked: root.middleClicked()
onWheel: delta => root.wheel(delta)
}
}
Component {
id: horizontalPillComponent
BarPillHorizontal {
screen: root.screen
icon: root.icon
text: root.text
suffix: root.suffix
tooltipText: root.tooltipText
autoHide: root.autoHide
forceOpen: root.forceOpen
forceClose: root.forceClose
disableOpen: root.disableOpen
rightOpen: root.rightOpen
hovered: root.hovered
compact: root.compact
onShown: root.shown()
onHidden: root.hidden()
onEntered: root.entered()
onExited: root.exited()
onClicked: root.clicked()
onRightClicked: root.rightClicked()
onMiddleClicked: root.middleClicked()
onWheel: delta => root.wheel(delta)
}
}
}
function show() {
if (pillLoader.item && pillLoader.item.show) {
pillLoader.item.show()
}
}
function hide() {
if (pillLoader.item && pillLoader.item.hide) {
pillLoader.item.hide()
}
}
function showDelayed() {
if (pillLoader.item && pillLoader.item.showDelayed) {
pillLoader.item.showDelayed()
}
}
}

View File

@@ -1,67 +1,100 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
Item {
id: root
property ShellScreen screen
property string icon: ""
property string text: ""
property string suffix: ""
property string tooltipText: ""
property color pillColor: Color.mSurfaceVariant
property color textColor: Color.mOnSurface
property color iconCircleColor: Color.mPrimary
property color iconTextColor: Color.mSurface
property color collapsedIconColor: Color.mOnSurface
property real sizeMultiplier: 0.8
property bool autoHide: false
property bool forceOpen: false
property bool forceClose: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
property bool compact: false
// Effective shown state (true if hovered/animated open or forced)
readonly property bool effectiveShown: forceOpen || showPill
readonly property bool revealed: !forceClose && (forceOpen || showPill)
signal shown
signal hidden
signal entered
signal exited
signal clicked
signal rightClicked
signal middleClicked
signal wheel(int delta)
// Internal state
property bool showPill: false
property bool shouldAnimateHide: false
// Exposed width logic
readonly property int pillHeight: Style.baseWidgetSize * sizeMultiplier * scaling
readonly property int iconSize: Style.baseWidgetSize * sizeMultiplier * scaling
readonly property int pillPaddingHorizontal: Style.marginM * scaling
readonly property int pillOverlap: iconSize * 0.5
readonly property int maxPillWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap)
readonly property int pillHeight: Math.round(Style.capsuleHeight * scaling)
readonly property int pillPaddingHorizontal: Math.round(Style.capsuleHeight * 0.2 * scaling)
readonly property int pillOverlap: Math.round(Style.capsuleHeight * 0.5 * scaling)
readonly property int pillMaxWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap)
width: iconSize + (effectiveShown ? maxPillWidth - pillOverlap : 0)
readonly property real iconSize: Math.max(1, compact ? pillHeight * 0.65 : pillHeight * 0.48)
readonly property real textSize: Math.max(1, compact ? pillHeight * 0.45 : pillHeight * 0.33)
width: pillHeight + Math.max(0, pill.width - pillOverlap)
height: pillHeight
Connections {
target: root
function onTooltipTextChanged() {
TooltipService.updateText(root.tooltipText)
}
}
Rectangle {
id: pill
width: effectiveShown ? maxPillWidth : 1
property ShellScreen screen: root.screen
width: revealed ? pillMaxWidth : 1
height: pillHeight
x: (iconCircle.x + iconCircle.width / 2) - width
opacity: effectiveShown ? Style.opacityFull : Style.opacityNone
color: pillColor
topLeftRadius: pillHeight * 0.5
bottomLeftRadius: pillHeight * 0.5
x: rightOpen ? (iconCircle.x + iconCircle.width / 2) : // Opens right
(iconCircle.x + iconCircle.width / 2) - width // Opens left
opacity: revealed ? Style.opacityFull : Style.opacityNone
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
topLeftRadius: rightOpen ? 0 : pillHeight * 0.5
bottomLeftRadius: rightOpen ? 0 : pillHeight * 0.5
topRightRadius: rightOpen ? pillHeight * 0.5 : 0
bottomRightRadius: rightOpen ? pillHeight * 0.5 : 0
anchors.verticalCenter: parent.verticalCenter
NText {
id: textItem
anchors.centerIn: parent
text: root.text
font.pointSize: Style.fontSizeXS * scaling
anchors.verticalCenter: parent.verticalCenter
x: {
// Better text horizontal centering
var centerX = (parent.width - width) / 2
var offset = rightOpen ? Style.marginXS * scaling : -Style.marginXS * scaling
if (forceOpen) {
// If its force open, the icon disc background is the same color as the bg pill move text slightly
offset += rightOpen ? -Style.marginXXS * scaling : Style.marginXXS * scaling
}
return centerX + offset
}
text: root.text + root.suffix
family: Settings.data.ui.fontFixed
pointSize: textSize
font.weight: Style.fontWeightBold
color: textColor
visible: effectiveShown
color: forceOpen ? Color.mOnSurface : Color.mPrimary
visible: revealed
}
Behavior on width {
@@ -82,13 +115,13 @@ Item {
Rectangle {
id: iconCircle
width: iconSize
height: iconSize
width: pillHeight
height: pillHeight
radius: width * 0.5
// When forced shown, match pill background; otherwise use accent when hovered
color: forceOpen ? pillColor : (showPill ? iconCircleColor : Color.mSurfaceVariant)
color: hovered ? Color.mTertiary : Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
x: rightOpen ? 0 : (parent.width - width)
Behavior on color {
ColorAnimation {
@@ -98,11 +131,13 @@ Item {
}
NIcon {
text: root.icon
font.pointSize: Style.fontSizeM * scaling
// When forced shown, use pill text color; otherwise accent color when hovered
color: forceOpen ? textColor : (showPill ? iconTextColor : Color.mOnSurface)
anchors.centerIn: parent
icon: root.icon
pointSize: iconSize
color: hovered ? Color.mOnTertiary : Color.mOnSurface
// Center horizontally
x: (iconCircle.width - width) / 2
// Center vertically accounting for font metrics
y: (iconCircle.height - height) / 2 + (height - contentHeight) / 2
}
}
@@ -113,7 +148,7 @@ Item {
target: pill
property: "width"
from: 1
to: maxPillWidth
to: pillMaxWidth
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
@@ -153,7 +188,7 @@ Item {
NumberAnimation {
target: pill
property: "width"
from: maxPillWidth
from: pillMaxWidth
to: 1
duration: Style.animationNormal
easing.type: Easing.InCubic
@@ -173,14 +208,6 @@ Item {
}
}
NTooltip {
id: tooltip
positionAbove: Settings.data.bar.position === "bottom"
target: pill
delay: Style.tooltipDelayLong
text: root.tooltipText
}
Timer {
id: showTimer
interval: Style.pillDelay
@@ -194,10 +221,12 @@ Item {
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onEntered: {
hovered = true
root.entered()
tooltip.show()
if (disableOpen) {
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
if (disableOpen || forceClose) {
return
}
if (!forceOpen) {
@@ -205,18 +234,23 @@ Item {
}
}
onExited: {
hovered = false
root.exited()
if (!forceOpen) {
if (!forceOpen && !forceClose) {
hide()
}
tooltip.hide()
TooltipService.hide()
}
onClicked: {
root.clicked()
onClicked: function (mouse) {
if (mouse.button === Qt.LeftButton) {
root.clicked()
} else if (mouse.button === Qt.RightButton) {
root.rightClicked()
} else if (mouse.button === Qt.MiddleButton) {
root.middleClicked()
}
}
onWheel: wheel => {
root.wheel(wheel.angleDelta.y)
}
onWheel: wheel => root.wheel(wheel.angleDelta.y)
}
function show() {

View File

@@ -0,0 +1,337 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
Item {
id: root
property ShellScreen screen
property string icon: ""
property string text: ""
property string suffix: ""
property string tooltipText: ""
property bool autoHide: false
property bool forceOpen: false
property bool forceClose: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
property bool compact: false
// 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: rightOpen
readonly property bool openUpward: !rightOpen
// Effective shown state (true if animated open or forced, but not if force closed)
readonly property bool revealed: !forceClose && (forceOpen || showPill)
signal shown
signal hidden
signal entered
signal exited
signal clicked
signal rightClicked
signal middleClicked
signal wheel(int delta)
// Internal state
property bool showPill: false
property bool shouldAnimateHide: false
// Sizing logic for vertical bars
readonly property int buttonSize: Math.round(Style.capsuleHeight * scaling)
readonly property int pillHeight: buttonSize
readonly property int pillPaddingVertical: 3 * 2 * scaling // Very precise adjustment don't replace by Style.margin
readonly property int pillOverlap: buttonSize * 0.5
readonly property int maxPillWidth: buttonSize
readonly property int maxPillHeight: Math.max(1, textItem.implicitHeight + pillPaddingVertical * 4)
readonly property real iconSize: Math.max(1, compact ? pillHeight * 0.65 : pillHeight * 0.48)
readonly property real textSize: Math.max(1, compact ? pillHeight * 0.38 : pillHeight * 0.33)
// For vertical bars: width is just icon size, height includes pill space
width: buttonSize
height: revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize
Connections {
target: root
function onTooltipTextChanged() {
TooltipService.updateText(root.tooltipText)
}
}
Rectangle {
id: pill
property ShellScreen screen: root.screen
width: revealed ? maxPillWidth : 1
height: revealed ? maxPillHeight : 1
// Position based on direction - center the pill relative to the icon
x: 0
y: openUpward ? (iconCircle.y + iconCircle.height / 2 - height) : (iconCircle.y + iconCircle.height / 2)
opacity: revealed ? Style.opacityFull : Style.opacityNone
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
// Radius logic for vertical expansion - rounded on the side that connects to icon
topLeftRadius: openUpward ? buttonSize * 0.5 : 0
bottomLeftRadius: openDownward ? buttonSize * 0.5 : 0
topRightRadius: openUpward ? buttonSize * 0.5 : 0
bottomRightRadius: openDownward ? buttonSize * 0.5 : 0
anchors.horizontalCenter: parent.horizontalCenter
NText {
id: textItem
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: {
var offset = openDownward ? pillPaddingVertical * 0.75 : -pillPaddingVertical * 0.75
if (forceOpen) {
// If its force open, the icon disc background is the same color as the bg pill move text slightly
offset += rightOpen ? -Style.marginXXS * scaling : Style.marginXXS * scaling
}
return offset
}
text: root.text + root.suffix
family: Settings.data.ui.fontFixed
pointSize: textSize
font.weight: Style.fontWeightMedium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: forceOpen ? Color.mOnSurface : Color.mPrimary
visible: revealed
}
Behavior on width {
enabled: showAnim.running || hideAnim.running
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Behavior on height {
enabled: showAnim.running || hideAnim.running
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Behavior on opacity {
enabled: showAnim.running || hideAnim.running
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
}
Rectangle {
id: iconCircle
width: buttonSize
height: buttonSize
radius: width * 0.5
color: hovered ? Color.mTertiary : Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
// Icon positioning based on direction
x: 0
y: openUpward ? (parent.height - height) : 0
anchors.horizontalCenter: parent.horizontalCenter
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
easing.type: Easing.InOutQuad
}
}
NIcon {
icon: root.icon
pointSize: iconSize
color: hovered ? Color.mOnTertiary : Color.mOnSurface
// Center horizontally
x: (iconCircle.width - width) / 2
// Center vertically accounting for font metrics
y: (iconCircle.height - height) / 2 + (height - contentHeight) / 2
}
}
ParallelAnimation {
id: showAnim
running: false
NumberAnimation {
target: pill
property: "width"
from: 1
to: maxPillWidth
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
NumberAnimation {
target: pill
property: "height"
from: 1
to: maxPillHeight
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
NumberAnimation {
target: pill
property: "opacity"
from: 0
to: 1
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
onStarted: {
showPill = true
}
onStopped: {
delayedHideAnim.start()
root.shown()
}
}
SequentialAnimation {
id: delayedHideAnim
running: false
PauseAnimation {
duration: 2500
}
ScriptAction {
script: if (shouldAnimateHide) {
hideAnim.start()
}
}
}
ParallelAnimation {
id: hideAnim
running: false
NumberAnimation {
target: pill
property: "width"
from: maxPillWidth
to: 1
duration: Style.animationNormal
easing.type: Easing.InCubic
}
NumberAnimation {
target: pill
property: "height"
from: maxPillHeight
to: 1
duration: Style.animationNormal
easing.type: Easing.InCubic
}
NumberAnimation {
target: pill
property: "opacity"
from: 1
to: 0
duration: Style.animationNormal
easing.type: Easing.InCubic
}
onStopped: {
showPill = false
shouldAnimateHide = false
root.hidden()
}
}
Timer {
id: showTimer
interval: Style.pillDelay
onTriggered: {
if (!showPill) {
showAnim.start()
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onEntered: {
hovered = true
root.entered()
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
if (disableOpen || forceClose) {
return
}
if (!forceOpen) {
showDelayed()
}
}
onExited: {
hovered = false
root.exited()
if (!forceOpen && !forceClose) {
hide()
}
TooltipService.hide()
}
onClicked: function (mouse) {
if (mouse.button === Qt.LeftButton) {
root.clicked()
} else if (mouse.button === Qt.RightButton) {
root.rightClicked()
} else if (mouse.button === Qt.MiddleButton) {
root.middleClicked()
}
}
onWheel: wheel => root.wheel(wheel.angleDelta.y)
}
function show() {
if (!showPill) {
shouldAnimateHide = autoHide
showAnim.start()
} else {
hideAnim.stop()
delayedHideAnim.restart()
}
}
function hide() {
if (forceOpen) {
return
}
if (showPill) {
hideAnim.start()
}
showTimer.stop()
}
function showDelayed() {
if (!showPill) {
shouldAnimateHide = autoHide
showTimer.start()
} else {
hideAnim.stop()
delayedHideAnim.restart()
}
}
onForceOpenChanged: {
if (forceOpen) {
// Immediately lock open without animations
showAnim.stop()
hideAnim.stop()
delayedHideAnim.stop()
showPill = true
} else {
hide()
}
}
}

View File

@@ -0,0 +1,76 @@
import QtQuick
import Quickshell
import qs.Services
import qs.Commons
Item {
id: root
property string widgetId: ""
property var widgetProps: ({})
property string screenName: widgetProps.screen ? widgetProps.screen.name : ""
property string section: widgetProps.section || ""
property int sectionIndex: widgetProps.sectionWidgetIndex || 0
Connections {
target: ScalingService
function onScaleChanged(aScreenName, scale) {
if (loader.item && loader.item.screen && aScreenName === screenName) {
loader.item['scaling'] = scale
}
}
}
// Don't reserve space unless the loaded widget is really visible
implicitWidth: loader.item ? loader.item.visible ? loader.item.implicitWidth : 0 : 0
implicitHeight: loader.item ? loader.item.visible ? loader.item.implicitHeight : 0 : 0
Loader {
id: loader
anchors.fill: parent
active: widgetId !== ""
sourceComponent: {
if (!active) {
return null
}
return BarWidgetRegistry.getWidget(widgetId)
}
onLoaded: {
if (item && widgetProps) {
// Apply properties to loaded widget
for (var prop in widgetProps) {
if (item.hasOwnProperty(prop)) {
item[prop] = widgetProps[prop]
}
}
}
// Register this widget instance with BarService
if (screenName && section) {
BarService.registerWidget(screenName, section, widgetId, sectionIndex, item)
}
if (item.hasOwnProperty("onLoaded")) {
item.onLoaded()
}
//Logger.log("BarWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name)
}
Component.onDestruction: {
// Unregister when destroyed
if (screenName && section) {
BarService.unregisterWidget(screenName, section, widgetId, sectionIndex)
}
}
}
// Error handling
onWidgetIdChanged: {
if (widgetId && !BarWidgetRegistry.hasWidget(widgetId)) {
Logger.warn("BarWidgetLoader", "Widget not found in bar registry:", widgetId)
}
}
}

View File

@@ -14,13 +14,24 @@ PopupWindow {
property real anchorY
property bool isSubMenu: false
property bool isHovered: rootMouseArea.containsMouse
property ShellScreen screen
property real scaling: ScalingService.getScreenScale(screen)
Connections {
target: ScalingService
function onScaleChanged(screenName, scale) {
if ((screen != null) && (screenName === screen.name)) {
scaling = scale
}
}
}
readonly property int menuWidth: 180
implicitWidth: menuWidth * scaling
// Use the content height of the Flickable for implicit height
implicitHeight: Math.min(Screen.height * 0.9, flickable.contentHeight + (Style.marginM * 2 * scaling))
implicitHeight: Math.min(screen ? screen.height * 0.9 : Screen.height * 0.9, flickable.contentHeight + (Style.marginS * 2 * scaling))
visible: false
color: Color.transparent
anchor.item: anchorItem
@@ -147,10 +158,9 @@ PopupWindow {
NText {
id: text
Layout.fillWidth: true
color: (modelData?.enabled
?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant
color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant
text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..."
font.pointSize: Style.fontSizeS * scaling
pointSize: Style.fontSizeS * scaling
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
}
@@ -164,11 +174,11 @@ PopupWindow {
}
NIcon {
text: modelData?.hasChildren ? "menu" : ""
font.pointSize: Style.fontSizeS * scaling
icon: modelData?.hasChildren ? "menu" : ""
pointSize: Style.fontSizeS * scaling
verticalAlignment: Text.AlignVCenter
visible: modelData?.hasChildren ?? false
color: Color.mOnSurface
color: (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface)
}
}
@@ -210,9 +220,32 @@ PopupWindow {
const submenuWidth = menuWidth * scaling // Assuming a similar width as the parent
const overlap = 4 * scaling // A small overlap to bridge the mouse path
// Check if there's enough space on the right
// Determine submenu opening direction based on bar position and available space
let openLeft = false
// Check bar position first
const barPosition = Settings.data.bar.position
const globalPos = entry.mapToGlobal(0, 0)
const openLeft = (globalPos.x + entry.width + submenuWidth > Screen.width)
if (barPosition === "right") {
// Bar is on the right, prefer opening submenus to the left
openLeft = true
} else if (barPosition === "left") {
// Bar is on the left, prefer opening submenus to the right
openLeft = false
} else {
// Bar is horizontal (top/bottom) or undefined, use space-based logic
openLeft = (globalPos.x + entry.width + submenuWidth > screen.width)
// Secondary check: ensure we don't open off-screen
if (openLeft && globalPos.x - submenuWidth < 0) {
// Would open off the left edge, force right opening
openLeft = false
} else if (!openLeft && globalPos.x + entry.width + submenuWidth > screen.width) {
// Would open off the right edge, force left opening
openLeft = true
}
}
// Position with overlap
const anchorX = openLeft ? -submenuWidth + overlap : entry.width - overlap
@@ -223,7 +256,8 @@ PopupWindow {
"anchorItem": entry,
"anchorX": anchorX,
"anchorY": 0,
"isSubMenu": true
"isSubMenu": true,
"screen": screen
})
if (entry.subMenu) {

View File

@@ -0,0 +1,587 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
NPanel {
id: root
preferredWidth: 400
preferredHeight: 500
panelKeyboardFocus: true
property string passwordSsid: ""
property string passwordInput: ""
property string expandedSsid: ""
onOpened: NetworkService.scan()
panelContent: Rectangle {
color: Color.transparent
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling
// Header
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
NIcon {
icon: Settings.data.network.wifiEnabled ? "wifi" : "wifi-off"
pointSize: Style.fontSizeXXL * scaling
color: Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("wifi.panel.title")
pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
}
NToggle {
id: wifiSwitch
checked: Settings.data.network.wifiEnabled
onToggled: checked => NetworkService.setWifiEnabled(checked)
baseSize: Style.baseWidgetSize * 0.65 * scaling
}
NIconButton {
icon: "refresh"
tooltipText: I18n.tr("tooltips.refresh")
baseSize: Style.baseWidgetSize * 0.8
enabled: Settings.data.network.wifiEnabled && !NetworkService.scanning
onClicked: NetworkService.scan()
}
NIconButton {
icon: "close"
tooltipText: I18n.tr("tooltips.close")
baseSize: Style.baseWidgetSize * 0.8
onClicked: root.close()
}
}
NDivider {
Layout.fillWidth: true
}
// Error message
Rectangle {
visible: NetworkService.lastError.length > 0
Layout.fillWidth: true
Layout.preferredHeight: errorRow.implicitHeight + (Style.marginM * scaling * 2)
color: Qt.rgba(Color.mError.r, Color.mError.g, Color.mError.b, 0.1)
radius: Style.radiusS * scaling
border.width: Math.max(1, Style.borderS * scaling)
border.color: Color.mError
RowLayout {
id: errorRow
anchors.fill: parent
anchors.margins: Style.marginM * scaling
spacing: Style.marginS * scaling
NIcon {
icon: "warning"
pointSize: Style.fontSizeL * scaling
color: Color.mError
}
NText {
text: NetworkService.lastError
color: Color.mError
pointSize: Style.fontSizeS * scaling
wrapMode: Text.Wrap
Layout.fillWidth: true
}
NIconButton {
icon: "close"
baseSize: Style.baseWidgetSize * 0.6
onClicked: NetworkService.lastError = ""
}
}
}
// Main content area
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Color.transparent
// WiFi disabled state
ColumnLayout {
visible: !Settings.data.network.wifiEnabled
anchors.fill: parent
spacing: Style.marginM * scaling
Item {
Layout.fillHeight: true
}
NIcon {
icon: "wifi-off"
pointSize: 64 * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("wifi.panel.disabled")
pointSize: Style.fontSizeL * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("wifi.panel.enable-message")
pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
Item {
Layout.fillHeight: true
}
}
// Scanning state
ColumnLayout {
visible: Settings.data.network.wifiEnabled && NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
anchors.fill: parent
spacing: Style.marginL * scaling
Item {
Layout.fillHeight: true
}
NBusyIndicator {
running: true
color: Color.mPrimary
size: Style.baseWidgetSize * scaling
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("wifi.panel.searching")
pointSize: Style.fontSizeNormal * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
Item {
Layout.fillHeight: true
}
}
// Networks list container
NScrollView {
visible: Settings.data.network.wifiEnabled && (!NetworkService.scanning || Object.keys(NetworkService.networks).length > 0)
anchors.fill: parent
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
clip: true
ColumnLayout {
width: parent.width
spacing: Style.marginM * scaling
// Network list
Repeater {
model: {
if (!Settings.data.network.wifiEnabled)
return []
const nets = Object.values(NetworkService.networks)
return nets.sort((a, b) => {
if (a.connected !== b.connected)
return b.connected - a.connected
return b.signal - a.signal
})
}
Rectangle {
Layout.fillWidth: true
implicitHeight: netColumn.implicitHeight + (Style.marginM * scaling * 2)
radius: Style.radiusM * scaling
// Add opacity for operations in progress
opacity: (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1.0
color: modelData.connected ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.05) : Color.mSurface
border.width: Math.max(1, Style.borderS * scaling)
border.color: modelData.connected ? Color.mPrimary : Color.mOutline
// Smooth opacity animation
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
}
}
ColumnLayout {
id: netColumn
width: parent.width - (Style.marginM * scaling * 2)
x: Style.marginM * scaling
y: Style.marginM * scaling
spacing: Style.marginS * scaling
// Main row
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS * scaling
NIcon {
icon: NetworkService.signalIcon(modelData.signal)
pointSize: Style.fontSizeXXL * scaling
color: modelData.connected ? Color.mPrimary : Color.mOnSurface
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2 * scaling
NText {
text: modelData.ssid
pointSize: Style.fontSizeNormal * scaling
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
color: Color.mOnSurface
elide: Text.ElideRight
Layout.fillWidth: true
}
RowLayout {
spacing: Style.marginXS * scaling
NText {
text: I18n.tr("system.signal-strength", {
"signal": modelData.signal
})
pointSize: Style.fontSizeXXS * scaling
color: Color.mOnSurfaceVariant
}
NText {
text: "•"
pointSize: Style.fontSizeXXS * scaling
color: Color.mOnSurfaceVariant
}
NText {
text: NetworkService.isSecured(modelData.security) ? modelData.security : "Open"
pointSize: Style.fontSizeXXS * scaling
color: Color.mOnSurfaceVariant
}
Item {
Layout.preferredWidth: Style.marginXXS * scaling
}
// Update the status badges area (around line 237)
Rectangle {
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
color: Color.mPrimary
radius: height * 0.5
width: connectedText.implicitWidth + (Style.marginS * scaling * 2)
height: connectedText.implicitHeight + (Style.marginXXS * scaling * 2)
NText {
id: connectedText
anchors.centerIn: parent
text: I18n.tr("wifi.panel.connected")
pointSize: Style.fontSizeXXS * scaling
color: Color.mOnPrimary
}
}
Rectangle {
visible: NetworkService.disconnectingFrom === modelData.ssid
color: Color.mError
radius: height * 0.5
width: disconnectingText.implicitWidth + (Style.marginS * scaling * 2)
height: disconnectingText.implicitHeight + (Style.marginXXS * scaling * 2)
NText {
id: disconnectingText
anchors.centerIn: parent
text: I18n.tr("wifi.panel.disconnecting")
pointSize: Style.fontSizeXXS * scaling
color: Color.mOnPrimary
}
}
Rectangle {
visible: NetworkService.forgettingNetwork === modelData.ssid
color: Color.mError
radius: height * 0.5
width: forgettingText.implicitWidth + (Style.marginS * scaling * 2)
height: forgettingText.implicitHeight + (Style.marginXXS * scaling * 2)
NText {
id: forgettingText
anchors.centerIn: parent
text: I18n.tr("wifi.panel.forgetting")
pointSize: Style.fontSizeXXS * scaling
color: Color.mOnPrimary
}
}
Rectangle {
visible: modelData.cached && !modelData.connected && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
color: Color.transparent
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
radius: height * 0.5
width: savedText.implicitWidth + (Style.marginS * scaling * 2)
height: savedText.implicitHeight + (Style.marginXXS * scaling * 2)
NText {
id: savedText
anchors.centerIn: parent
text: I18n.tr("wifi.panel.saved")
pointSize: Style.fontSizeXXS * scaling
color: Color.mOnSurfaceVariant
}
}
}
}
// Action area
RowLayout {
spacing: Style.marginS * scaling
NBusyIndicator {
visible: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid
running: visible
color: Color.mPrimary
size: Style.baseWidgetSize * 0.5 * scaling
}
NIconButton {
visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
icon: "trash"
tooltipText: I18n.tr("tooltips.forget-network")
baseSize: Style.baseWidgetSize * 0.8
onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid
}
NButton {
visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && passwordSsid !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
text: {
if (modelData.existing || modelData.cached)
return I18n.tr("wifi.panel.connect")
if (!NetworkService.isSecured(modelData.security))
return I18n.tr("wifi.panel.connect")
return I18n.tr("wifi.panel.password")
}
outlined: !hovered
fontSize: Style.fontSizeXS * scaling
enabled: !NetworkService.connecting
onClicked: {
if (modelData.existing || modelData.cached || !NetworkService.isSecured(modelData.security)) {
NetworkService.connect(modelData.ssid)
} else {
passwordSsid = modelData.ssid
passwordInput = ""
expandedSsid = ""
}
}
}
NButton {
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
text: I18n.tr("wifi.panel.disconnect")
outlined: !hovered
fontSize: Style.fontSizeXS * scaling
backgroundColor: Color.mError
onClicked: NetworkService.disconnect(modelData.ssid)
}
}
}
// Password input
Rectangle {
visible: passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
Layout.fillWidth: true
height: passwordRow.implicitHeight + Style.marginS * scaling * 2
color: Color.mSurfaceVariant
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
radius: Style.radiusS * scaling
RowLayout {
id: passwordRow
anchors.fill: parent
anchors.margins: Style.marginS * scaling
spacing: Style.marginM * scaling
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Style.radiusXS * scaling
color: Color.mSurface
border.color: pwdInput.activeFocus ? Color.mSecondary : Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
TextInput {
id: pwdInput
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Style.marginS * scaling
text: passwordInput
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurface
echoMode: TextInput.Password
selectByMouse: true
focus: visible
passwordCharacter: "●"
onTextChanged: passwordInput = text
onVisibleChanged: if (visible)
forceActiveFocus()
onAccepted: {
if (text && !NetworkService.connecting) {
NetworkService.connect(passwordSsid, text)
passwordSsid = ""
passwordInput = ""
}
}
NText {
visible: parent.text.length === 0
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("wifi.panel.enter-password")
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeS * scaling
}
}
}
NButton {
text: I18n.tr("wifi.panel.connect")
fontSize: Style.fontSizeXXS * scaling
enabled: passwordInput.length > 0 && !NetworkService.connecting
outlined: true
onClicked: {
NetworkService.connect(passwordSsid, passwordInput)
passwordSsid = ""
passwordInput = ""
}
}
NIconButton {
icon: "close"
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
passwordSsid = ""
passwordInput = ""
}
}
}
}
// Forget network
Rectangle {
visible: expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
Layout.fillWidth: true
height: forgetRow.implicitHeight + Style.marginS * 2 * scaling
color: Color.mSurfaceVariant
radius: Style.radiusS * scaling
border.width: Math.max(1, Style.borderS * scaling)
border.color: Color.mOutline
RowLayout {
id: forgetRow
anchors.fill: parent
anchors.margins: Style.marginS * scaling
spacing: Style.marginM * scaling
RowLayout {
NIcon {
icon: "trash"
pointSize: Style.fontSizeL * scaling
color: Color.mError
}
NText {
text: I18n.tr("wifi.panel.forget-network")
pointSize: Style.fontSizeS * scaling
color: Color.mError
Layout.fillWidth: true
}
}
NButton {
id: forgetButton
text: I18n.tr("wifi.panel.forget")
fontSize: Style.fontSizeXXS * scaling
backgroundColor: Color.mError
outlined: forgetButton.hovered ? false : true
onClicked: {
NetworkService.forget(modelData.ssid)
expandedSsid = ""
}
}
NIconButton {
icon: "close"
baseSize: Style.baseWidgetSize * 0.8
onClicked: expandedSsid = ""
}
}
}
}
}
}
}
}
// Empty state when no networks
ColumnLayout {
visible: Settings.data.network.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
anchors.fill: parent
spacing: Style.marginL * scaling
Item {
Layout.fillHeight: true
}
NIcon {
icon: "search"
pointSize: 64 * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("wifi.panel.no-networks")
pointSize: Style.fontSizeL * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NButton {
text: I18n.tr("wifi.panel.scan-again")
icon: "refresh"
Layout.alignment: Qt.AlignHCenter
onClicked: NetworkService.scan()
}
Item {
Layout.fillHeight: true
}
}
}
}
}
}

View File

@@ -2,95 +2,146 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Commons
import qs.Services
import qs.Widgets
Row {
Item {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property bool showingFullTitle: false
property int lastWindowIndex: -1
property real scaling: 1.0
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling
visible: getTitle() !== ""
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
// Timer to hide full title after window switch
Timer {
id: fullTitleTimer
interval: 2000
repeat: false
onTriggered: {
showingFullTitle = false
}
}
// Update text when window changes
Connections {
target: CompositorService
function onActiveWindowChanged() {
// Check if window actually changed
if (CompositorService.focusedWindowIndex !== lastWindowIndex) {
lastWindowIndex = CompositorService.focusedWindowIndex
showingFullTitle = true
fullTitleTimer.restart()
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
function getTitle() {
// Use the service's focusedWindowTitle property which is updated immediately
// when WindowOpenedOrChanged events are received
return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : ""
readonly property bool hasActiveWindow: CompositorService.getFocusedWindowTitle() !== ""
readonly property string windowTitle: CompositorService.getFocusedWindowTitle() || "No active window"
readonly property string fallbackIcon: "user-desktop"
readonly property string barPosition: Settings.data.bar.position
readonly property bool compact: (Settings.data.bar.density === "compact")
// Widget settings - matching MediaMini pattern
readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon
readonly property bool autoHide: (widgetSettings.autoHide !== undefined) ? widgetSettings.autoHide : widgetMetadata.autoHide
readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : (widgetMetadata.scrollingMode !== undefined ? widgetMetadata.scrollingMode : "hover")
// Fixed width
readonly property real widgetWidth: Math.max(145, screen.width * 0.06)
implicitHeight: visible ? ((barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)) : 0
implicitWidth: visible ? ((barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)) : 0
opacity: !autoHide || hasActiveWindow ? 1.0 : 0
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
function calculatedVerticalHeight() {
return Math.round(Style.baseWidgetSize * 0.8 * scaling)
}
function getAppIcon() {
const focusedWindow = CompositorService.getFocusedWindow()
if (!focusedWindow || !focusedWindow.appId)
return ""
try {
// Try CompositorService first
const focusedWindow = CompositorService.getFocusedWindow()
if (focusedWindow && focusedWindow.appId) {
try {
const idValue = focusedWindow.appId
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase())
if (iconResult && iconResult !== "") {
return iconResult
}
} catch (iconError) {
Logger.warn("ActiveWindow", "Error getting icon from CompositorService:", iconError)
}
}
return Icons.iconForAppId(focusedWindow.appId)
if (CompositorService.isHyprland) {
// Fallback to ToplevelManager
if (ToplevelManager && ToplevelManager.activeToplevel) {
try {
const activeToplevel = ToplevelManager.activeToplevel
if (activeToplevel.appId) {
const idValue2 = activeToplevel.appId
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
const iconResult2 = ThemeIcons.iconForAppId(normalizedId2.toLowerCase())
if (iconResult2 && iconResult2 !== "") {
return iconResult2
}
}
} catch (fallbackError) {
Logger.warn("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError)
}
}
}
return ThemeIcons.iconFromName(fallbackIcon)
} catch (e) {
Logger.warn("ActiveWindow", "Error in getAppIcon:", e)
return ThemeIcons.iconFromName(fallbackIcon)
}
}
// A hidden text element to safely measure the full title width
// Hidden text element to measure full title width
NText {
id: fullTitleMetrics
visible: false
text: titleText.text
font: titleText.font
text: windowTitle
pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
}
Rectangle {
// Let the Rectangle size itself based on its content (the Row)
id: windowActiveRect
visible: root.visible
width: row.width + Style.marginM * scaling * 2
height: Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: (barPosition === "left" || barPosition === "right") ? width / 2 : Math.round(Style.radiusM * scaling)
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
id: mainContainer
anchors.fill: parent
anchors.leftMargin: Style.marginS * scaling
anchors.rightMargin: Style.marginS * scaling
anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
Row {
id: row
// Horizontal layout for top/bottom bars
RowLayout {
id: rowLayout
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginXS * scaling
spacing: Style.marginS * scaling
visible: barPosition === "top" || barPosition === "bottom"
z: 1
// Window icon
Item {
width: Style.fontSizeL * scaling * 1.2
height: Style.fontSizeL * scaling * 1.2
anchors.verticalCenter: parent.verticalCenter
visible: getTitle() !== "" && Settings.data.bar.showActiveWindowIcon
Layout.preferredWidth: Math.round(18 * scaling)
Layout.preferredHeight: Math.round(18 * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showIcon
IconImage {
id: windowIcon
@@ -102,23 +153,138 @@ Row {
}
}
NText {
id: titleText
// Title container with scrolling
Item {
id: titleContainer
Layout.preferredWidth: {
// Calculate available width based on other elements
var iconWidth = (showIcon && windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0)
var totalMargins = Style.marginXXS * scaling * 2
var availableWidth = mainContainer.width - iconWidth - totalMargins
return Math.max(20 * scaling, availableWidth)
}
Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: titleText.height
// If hovered or just switched window, show up to 400 pixels
// If not hovered show up to 150 pixels
width: (showingFullTitle || mouseArea.containsMouse) ? Math.min(fullTitleMetrics.contentWidth,
400 * scaling) : Math.min(
fullTitleMetrics.contentWidth, 150 * scaling)
text: getTitle()
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
color: Color.mSecondary
clip: true
Behavior on width {
property bool isScrolling: false
property bool isResetting: false
property real textWidth: fullTitleMetrics.contentWidth
property real containerWidth: width
property bool needsScrolling: textWidth > containerWidth
// Timer for "always" mode with delay
Timer {
id: scrollStartTimer
interval: 1000
repeat: false
onTriggered: {
if (scrollingMode === "always" && titleContainer.needsScrolling) {
titleContainer.isScrolling = true
titleContainer.isResetting = false
}
}
}
// Update scrolling state based on mode
property var updateScrollingState: function () {
if (scrollingMode === "never") {
isScrolling = false
isResetting = false
} else if (scrollingMode === "always") {
if (needsScrolling) {
if (mouseArea.containsMouse) {
isScrolling = false
isResetting = true
} else {
scrollStartTimer.restart()
}
} else {
scrollStartTimer.stop()
isScrolling = false
isResetting = false
}
} else if (scrollingMode === "hover") {
if (mouseArea.containsMouse && needsScrolling) {
isScrolling = true
isResetting = false
} else {
isScrolling = false
if (needsScrolling) {
isResetting = true
}
}
}
}
onWidthChanged: updateScrollingState()
Component.onCompleted: updateScrollingState()
// React to hover changes
Connections {
target: mouseArea
function onContainsMouseChanged() {
titleContainer.updateScrollingState()
}
}
// Scrolling content with seamless loop
Item {
id: scrollContainer
height: parent.height
width: childrenRect.width
property real scrollX: 0
x: scrollX
RowLayout {
spacing: 50 * scaling // Gap between text copies
NText {
id: titleText
text: windowTitle
pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
verticalAlignment: Text.AlignVCenter
color: Color.mOnSurface
}
// Second copy for seamless scrolling
NText {
text: windowTitle
font: titleText.font
verticalAlignment: Text.AlignVCenter
color: Color.mOnSurface
visible: titleContainer.needsScrolling && titleContainer.isScrolling
}
}
// Reset animation
NumberAnimation on scrollX {
running: titleContainer.isResetting
to: 0
duration: 300
easing.type: Easing.OutQuad
onFinished: {
titleContainer.isResetting = false
}
}
// Seamless infinite scroll
NumberAnimation on scrollX {
id: infiniteScroll
running: titleContainer.isScrolling && !titleContainer.isResetting
from: 0
to: -(titleContainer.textWidth + 50 * scaling)
duration: Math.max(4000, windowTitle.length * 100)
loops: Animation.Infinite
easing.type: Easing.Linear
}
}
Behavior on Layout.preferredWidth {
NumberAnimation {
duration: Style.animationSlow
easing.type: Easing.InOutCubic
@@ -127,12 +293,68 @@ Row {
}
}
// Vertical layout for left/right bars - icon only
Item {
id: verticalLayout
anchors.centerIn: parent
width: parent.width - Style.marginM * scaling * 2
height: parent.height - Style.marginM * scaling * 2
visible: barPosition === "left" || barPosition === "right"
z: 1
// Window icon
Item {
width: Style.baseWidgetSize * 0.5 * scaling
height: Style.baseWidgetSize * 0.5 * scaling
anchors.centerIn: parent
visible: windowTitle !== ""
IconImage {
id: windowIconVertical
anchors.fill: parent
source: getAppIcon()
asynchronous: true
smooth: true
visible: source !== ""
}
}
}
// Mouse area for hover detection
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onEntered: {
if ((windowTitle !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) {
TooltipService.show(root, windowTitle, BarService.getTooltipDirection())
}
}
onExited: {
TooltipService.hide()
}
}
}
}
Connections {
target: CompositorService
function onActiveWindowChanged() {
try {
windowIcon.source = Qt.binding(getAppIcon)
windowIconVertical.source = Qt.binding(getAppIcon)
} catch (e) {
Logger.warn("ActiveWindow", "Error in onActiveWindowChanged:", e)
}
}
function onWindowListChanged() {
try {
windowIcon.source = Qt.binding(getAppIcon)
windowIconVertical.source = Qt.binding(getAppIcon)
} catch (e) {
Logger.warn("ActiveWindow", "Error in onWindowListChanged:", e)
}
}
}

View File

@@ -1,75 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
sizeMultiplier: 0.8
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
// Enhanced icon states with better visual feedback
icon: {
if (ArchUpdaterService.busy)
return "sync"
if (ArchUpdaterService.updatePackages.length > 0) {
// Show different icons based on update count
const count = ArchUpdaterService.updatePackages.length
if (count > 50)
return "system_update_alt" // Many updates
if (count > 10)
return "system_update" // Moderate updates
return "system_update" // Few updates
}
return "task_alt"
}
// Enhanced tooltip with more information
tooltipText: {
if (ArchUpdaterService.busy)
return "Checking for updates…"
var count = ArchUpdaterService.updatePackages.length
if (count === 0)
return "System is up to date ✓"
var header = count === 1 ? "One package can be upgraded:" : (count + " packages can be upgraded:")
var list = ArchUpdaterService.updatePackages || []
var s = ""
var limit = Math.min(list.length, 8)
// Reduced to 8 for better readability
for (var i = 0; i < limit; ++i) {
var p = list[i]
s += (i ? "\n" : "") + (p.name + ": " + p.oldVersion + " → " + p.newVersion)
}
if (list.length > 8)
s += "\n… and " + (list.length - 8) + " more"
return header + "\n\n" + s + "\n\nClick to update system"
}
// Enhanced click behavior with confirmation
onClicked: {
if (ArchUpdaterService.busy)
return
if (ArchUpdaterService.updatePackages.length > 0) {
// Show confirmation dialog for updates
PanelService.getPanel("archUpdaterPanel").toggle(screen)
} else {
// Just refresh if no updates available
ArchUpdaterService.doPoll()
}
}
}

View File

@@ -5,97 +5,125 @@ import QtQuick.Layouts
import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.Bar.Extras
Item {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
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
// Test mode
readonly property bool testMode: false
readonly property int testPercent: 100
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 bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
property bool hasNotifiedLowBattery: false
implicitWidth: pill.width
implicitHeight: pill.height
NPill {
id: pill
// 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
ToastService.showWarning(I18n.tr("toast.battery.low"), I18n.tr("toast.battery.low-desc", {
"percent": Math.round(percent)
}))
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
// Reset when charging starts or when battery recovers 5% above threshold
root.hasNotifiedLowBattery = false
}
}
// Test mode
property bool testMode: false
property int testPercent: 49
property bool testCharging: false
property var battery: UPower.displayDevice
property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
// Choose icon based on charge and charging state
function batteryIcon() {
if (!isReady || !battery.isLaptopBattery)
return "battery_android_alert"
if (charging)
return "battery_android_bolt"
if (percent >= 95)
return "battery_android_full"
// Hardcoded battery symbols
if (percent >= 85)
return "battery_android_6"
if (percent >= 70)
return "battery_android_5"
if (percent >= 55)
return "battery_android_4"
if (percent >= 40)
return "battery_android_3"
if (percent >= 25)
return "battery_android_2"
if (percent >= 10)
return "battery_android_1"
if (percent >= 0)
return "battery_android_0"
// 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)
}
icon: batteryIcon()
text: (isReady && battery.isLaptopBattery) ? Math.round(percent) + "%" : "-"
textColor: charging ? Color.mPrimary : Color.mOnSurface
forceOpen: isReady && battery.isLaptopBattery && Settings.data.bar.alwaysShowBatteryPercentage
disableOpen: (!isReady || !battery.isLaptopBattery)
function onStateChanged() {
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging
// Reset notification flag when charging starts
if (isCharging) {
root.hasNotifiedLowBattery = false
}
// Also re-evaluate maybeNotify, as state might have changed
var currentPercent = UPower.displayDevice.percentage * 100
root.maybeNotify(currentPercent, isCharging)
}
}
BarPill {
id: pill
screen: root.screen
compact: (Settings.data.bar.density === "compact")
rightOpen: BarService.getPillDirection(root)
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, charging, isReady)
text: (isReady || testMode) ? Math.round(percent) : "-"
suffix: "%"
autoHide: false
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
forceClose: displayMode === "alwaysHide"
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))
tooltipText: {
let lines = []
if (testMode) {
lines.push("Time Left: " + Time.formatVagueHumanReadableDuration(12345))
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`)
return lines.join("\n")
}
if (!isReady || !battery.isLaptopBattery) {
return "No Battery Detected"
return "No battery detected."
}
if (battery.timeToEmpty > 0) {
lines.push("Time Left: " + Time.formatVagueHumanReadableDuration(battery.timeToEmpty))
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(battery.timeToEmpty)}.`)
}
if (battery.timeToFull > 0) {
lines.push("Time Until Full: " + Time.formatVagueHumanReadableDuration(battery.timeToFull))
lines.push(`Time until full: ${Time.formatVagueHumanReadableDuration(battery.timeToFull)}.`)
}
if (battery.changeRate !== undefined) {
const rate = battery.changeRate
if (rate > 0) {
lines.push(charging ? "Charging Rate: " + rate.toFixed(2) + " W" : "Discharging Rate: " + rate.toFixed(
2) + " W")
lines.push(charging ? "Charging rate: " + rate.toFixed(2) + " W." : "Discharging rate: " + rate.toFixed(2) + " W.")
} else if (rate < 0) {
lines.push("Discharging Rate: " + Math.abs(rate).toFixed(2) + " W")
lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W.")
} else {
lines.push("Estimating...")
}
} else {
lines.push(charging ? "Charging" : "Discharging")
lines.push(charging ? "Charging." : "Discharging.")
}
if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) {
lines.push("Health: " + Math.round(battery.healthPercentage) + "%")
}

View File

@@ -11,25 +11,17 @@ NIconButton {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
visible: Settings.data.network.bluetoothEnabled
sizeMultiplier: 0.8
colorBg: Color.mSurfaceVariant
baseSize: Style.capsuleHeight
compact: (Settings.data.bar.density === "compact")
colorBg: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
icon: {
// Show different icons based on connection status
if (BluetoothService.pairedDevices.length > 0) {
return "bluetooth_connected"
} else if (BluetoothService.discovering) {
return "bluetooth_searching"
} else {
return "bluetooth"
}
}
tooltipText: "Bluetooth Devices"
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(screen)
tooltipText: I18n.tr("tooltips.bluetooth-devices")
tooltipDirection: BarService.getTooltipDirection()
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
onRightClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
}

View File

@@ -1,7 +1,8 @@
import QtQuick
import Quickshell
import qs.Commons
import qs.Modules.SettingsPanel
import qs.Modules.Bar.Extras
import qs.Modules.Settings
import qs.Services
import qs.Widgets
@@ -9,7 +10,27 @@ Item {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
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
// Used to avoid opening the pill on Quickshell startup
property bool firstBrightnessReceived: false
@@ -25,8 +46,7 @@ Item {
function getIcon() {
var monitor = getMonitor()
var brightness = monitor ? monitor.brightness : 0
return brightness <= 0 ? "brightness_1" : brightness < 0.33 ? "brightness_low" : brightness
< 0.66 ? "brightness_medium" : "brightness_high"
return brightness <= 0.5 ? "brightness-low" : "brightness-high"
}
// Connection used to open the pill when brightness changes
@@ -34,44 +54,46 @@ Item {
target: getMonitor()
ignoreUnknownSignals: true
function onBrightnessUpdated() {
Logger.log("Bar-Brightness", "OnBrightnessUpdated")
var monitor = getMonitor()
if (!monitor)
return
var currentBrightness = monitor.brightness
// Ignore if this is the first time or if brightness hasn't actually changed
// Ignore if this is the first time we receive an update.
// Most likely service just kicked off.
if (!firstBrightnessReceived) {
firstBrightnessReceived = true
monitor.lastBrightness = currentBrightness
return
}
// Only show pill if brightness actually changed (not just loaded from settings)
if (Math.abs(currentBrightness - monitor.lastBrightness) > 0.1) {
pill.show()
}
monitor.lastBrightness = currentBrightness
pill.show()
hideTimerAfterChange.restart()
}
}
NPill {
Timer {
id: hideTimerAfterChange
interval: 2500
running: false
repeat: false
onTriggered: pill.hide()
}
BarPill {
id: pill
screen: root.screen
compact: (Settings.data.bar.density === "compact")
rightOpen: BarService.getPillDirection(root)
icon: getIcon()
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want
text: {
var monitor = getMonitor()
return monitor ? (Math.round(monitor.brightness * 100) + "%") : ""
return monitor ? Math.round(monitor.brightness * 100) : ""
}
suffix: text.length > 0 ? "%" : "-"
forceOpen: displayMode === "alwaysShow"
forceClose: displayMode === "alwaysHide"
tooltipText: {
var monitor = getMonitor()
if (!monitor)
return ""
return "Brightness: " + Math.round(monitor.brightness * 100) + "%\nMethod: " + monitor.method
+ "\nLeft click for advanced settings.\nScroll up/down to change brightness."
return "Brightness: " + Math.round(monitor.brightness * 100) + "%\nRight click for settings.\nScroll to modify brightness."
}
onWheel: function (angle) {
@@ -86,8 +108,15 @@ Item {
}
onClicked: {
settingsPanel.requestedTab = SettingsPanel.Tab.Brightness
settingsPanel.open(screen)
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Display
settingsPanel.open()
}
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Display
settingsPanel.open()
}
}
}

View File

@@ -1,4 +1,5 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services
@@ -8,37 +9,121 @@ Rectangle {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
implicitWidth: clock.width + Style.marginM * 2 * scaling
implicitHeight: Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
// Clock Icon with attached calendar
NClock {
id: clock
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
NTooltip {
id: tooltip
text: Time.dateString
target: clock
positionAbove: Settings.data.bar.position === "bottom"
readonly property string barPosition: Settings.data.bar.position
readonly property bool isBarVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property var now: Time.date
// Resolve settings: try user settings or defaults from BarWidgetRegistry
readonly property bool usePrimaryColor: widgetSettings.usePrimaryColor !== undefined ? widgetSettings.usePrimaryColor : widgetMetadata.usePrimaryColor
readonly property bool useCustomFont: widgetSettings.useCustomFont !== undefined ? widgetSettings.useCustomFont : widgetMetadata.useCustomFont
readonly property string customFont: widgetSettings.customFont !== undefined ? widgetSettings.customFont : widgetMetadata.customFont
readonly property string formatHorizontal: widgetSettings.formatHorizontal !== undefined ? widgetSettings.formatHorizontal : widgetMetadata.formatHorizontal
readonly property string formatVertical: widgetSettings.formatVertical !== undefined ? widgetSettings.formatVertical : widgetMetadata.formatVertical
implicitWidth: isBarVertical ? Math.round(Style.capsuleHeight * scaling) : Math.round((isBarVertical ? verticalLoader.implicitWidth : horizontalLoader.implicitWidth) + Style.marginM * 2 * scaling)
implicitHeight: isBarVertical ? Math.round(verticalLoader.implicitHeight + Style.marginS * 2 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusS * scaling)
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
id: clockContainer
anchors.centerIn: parent
// Horizontal
Loader {
id: horizontalLoader
active: !isBarVertical
anchors.centerIn: parent
sourceComponent: ColumnLayout {
anchors.centerIn: parent
spacing: Settings.data.bar.showCapsule ? -4 * scaling : -2 * scaling
Repeater {
id: repeater
model: Qt.locale().toString(now, formatHorizontal.trim()).split("\\n")
NText {
visible: text !== ""
text: modelData
family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault
pointSize: {
if (repeater.model.length == 1) {
return Style.fontSizeS * scaling
} else {
return (index == 0) ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling
}
}
font.weight: Style.fontWeightBold
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
}
}
// Vertical
Loader {
id: verticalLoader
active: isBarVertical
anchors.centerIn: parent // Now this works without layout conflicts
sourceComponent: ColumnLayout {
anchors.centerIn: parent
spacing: -2 * scaling
Repeater {
model: Qt.locale().toString(now, formatVertical.trim()).split(" ")
delegate: NText {
visible: text !== ""
text: modelData
family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault
pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightBold
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
}
}
}
MouseArea {
id: clockMouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: {
if (!PanelService.getPanel("calendarPanel")?.active) {
tooltip.show()
TooltipService.show(root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection())
}
}
onExited: {
tooltip.hide()
TooltipService.hide()
}
onClicked: {
tooltip.hide()
PanelService.getPanel("calendarPanel")?.toggle(screen)
TooltipService.hide()
PanelService.getPanel("calendarPanel")?.toggle(this)
}
}
}

View File

@@ -0,0 +1,66 @@
import QtQuick
import Quickshell
import Quickshell.Widgets
import QtQuick.Effects
import qs.Commons
import qs.Widgets
import qs.Services
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
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 || ""
// If we have a custom path or distro logo, don't use the theme icon.
icon: (customIconPath === "" && !useDistroLogo) ? customIcon : ""
tooltipText: I18n.tr("tooltips.open-control-center")
tooltipDirection: BarService.getTooltipDirection()
baseSize: Style.capsuleHeight
compact: (Settings.data.bar.density === "compact")
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mOnSurface
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mTertiary
colorBorder: Color.transparent
colorBorderHover: useDistroLogo ? Color.mTertiary : Color.transparent
onClicked: PanelService.getPanel("controlCenterPanel")?.toggle(this)
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle()
IconImage {
id: customOrDistroLogo
anchors.centerIn: parent
width: root.width * 0.8
height: width
source: {
if (customIconPath !== "")
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath
if (useDistroLogo)
return DistroLogoService.osLogo
return ""
}
visible: source !== ""
smooth: true
asynchronous: true
}
}

View File

@@ -0,0 +1,140 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.Settings
import qs.Modules.Bar.Extras
Item {
id: root
// Widget properties passed from Bar.qml
property var screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
// Use settings or defaults from BarWidgetRegistry
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec
readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec
readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
readonly property string textCommand: widgetSettings.textCommand !== undefined ? widgetSettings.textCommand : (widgetMetadata.textCommand || "")
readonly property int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000)
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
implicitWidth: pill.width
implicitHeight: pill.height
BarPill {
id: pill
screen: root.screen
rightOpen: BarService.getPillDirection(root)
icon: customIcon
text: _dynamicText
compact: (Settings.data.bar.density === "compact")
autoHide: false
forceOpen: _dynamicText !== ""
forceClose: false
disableOpen: true
tooltipText: {
if (!hasExec) {
return "Custom button, configure in settings."
} else {
var lines = []
if (leftClickExec !== "") {
lines.push(`Left click: ${leftClickExec}.`)
}
if (rightClickExec !== "") {
lines.push(`Right click: ${rightClickExec}.`)
}
if (middleClickExec !== "") {
lines.push(`Middle click: ${middleClickExec}.`)
}
return lines.join("\n")
}
}
onClicked: root.onClicked()
onRightClicked: root.onRightClicked()
onMiddleClicked: root.onMiddleClicked()
}
// Internal state for dynamic text
property string _dynamicText: ""
// Periodically run the text command (if set)
Timer {
id: refreshTimer
interval: Math.max(250, textIntervalMs)
repeat: true
running: (textCommand && textCommand.length > 0)
triggeredOnStart: true
onTriggered: {
if (!textCommand || textCommand.length === 0)
return
if (textProc.running)
return
textProc.command = ["sh", "-lc", textCommand]
textProc.running = true
}
}
Process {
id: textProc
stdout: StdioCollector {}
stderr: StdioCollector {}
onExited: (exitCode, exitStatus) => {
var out = String(stdout.text || "").trim()
if (out.indexOf("\n") !== -1) {
out = out.split("\n")[0]
}
_dynamicText = out
}
}
function onClicked() {
if (leftClickExec) {
Quickshell.execDetached(["sh", "-c", leftClickExec])
Logger.log("CustomButton", `Executing command: ${leftClickExec}`)
} else if (!hasExec) {
// No script was defined, open settings
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Bar
settingsPanel.open()
}
}
function onRightClicked() {
if (rightClickExec) {
Quickshell.execDetached(["sh", "-c", rightClickExec])
Logger.log("CustomButton", `Executing command: ${rightClickExec}`)
}
}
function onMiddleClicked() {
if (middleClickExec) {
Quickshell.execDetached(["sh", "-c", middleClickExec])
Logger.log("CustomButton", `Executing command: ${middleClickExec}`)
}
}
}

View File

@@ -0,0 +1,22 @@
import Quickshell
import qs.Commons
import qs.Widgets
import qs.Services
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
icon: "dark-mode"
tooltipText: Settings.data.colorSchemes.darkMode ? I18n.tr("tooltips.switch-to-light-mode") : I18n.tr("tooltips.switch-to-dark-mode")
tooltipDirection: BarService.getTooltipDirection()
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
colorBg: Settings.data.colorSchemes.darkMode ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
colorBorder: Color.transparent
colorBorderHover: Color.transparent
onClicked: Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode
}

View File

@@ -0,0 +1,24 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
baseSize: Style.capsuleHeight
compact: (Settings.data.bar.density === "compact")
icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
tooltipText: IdleInhibitorService.isInhibited ? I18n.tr("tooltips.disable-keep-awake") : I18n.tr("tooltips.enable-keep-awake")
tooltipDirection: BarService.getTooltipDirection()
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
onClicked: IdleInhibitorService.manualToggle()
}

View File

@@ -1,36 +1,62 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.Bar.Extras
Row {
Item {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
// Use the shared service for keyboard layout
property string currentLayout: KeyboardLayoutService.currentLayout
width: pill.width
height: pill.height
implicitWidth: pill.width
implicitHeight: pill.height
NPill {
BarPill {
id: pill
icon: "keyboard_alt"
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want
text: currentLayout
tooltipText: "Keyboard Layout: " + currentLayout
screen: root.screen
anchors.verticalCenter: parent.verticalCenter
compact: (Settings.data.bar.density === "compact")
rightOpen: BarService.getPillDirection(root)
icon: "keyboard"
autoHide: false // Important to be false so we can hover as long as we want
text: currentLayout.toUpperCase()
tooltipText: I18n.tr("tooltips.keyboard-layout", {
"layout": currentLayout.toUpperCase()
})
forceOpen: root.displayMode === "forceOpen"
forceClose: root.displayMode === "alwaysHide"
onClicked: {
// You could open keyboard settings here if needed
// For now, just show the current layout
// You could open keyboard settings here if needed.
}
}
}

View File

@@ -7,21 +7,78 @@ import qs.Commons
import qs.Services
import qs.Widgets
Row {
Item {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling
visible: MediaService.currentPlayer !== null && MediaService.canPlay
width: MediaService.currentPlayer !== null && MediaService.canPlay ? implicitWidth : 0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property string barPosition: Settings.data.bar.position
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property bool autoHide: (widgetSettings.autoHide !== undefined) ? widgetSettings.autoHide : widgetMetadata.autoHide
readonly property bool showAlbumArt: (widgetSettings.showAlbumArt !== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt
readonly property bool showVisualizer: (widgetSettings.showVisualizer !== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer
readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType !== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType
readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode
// Fixed width - no expansion
readonly property real widgetWidth: Math.max(145, screen.width * 0.06)
readonly property bool hasActivePlayer: MediaService.currentPlayer !== null && getTitle() !== ""
readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player")
readonly property string tooltipText: {
var title = getTitle()
var controls = ""
if (MediaService.canGoNext) {
controls += "Right click for next.\n"
}
if (MediaService.canGoPrevious) {
controls += "Middle click for previous."
}
if (controls !== "") {
return title + "\n\n" + controls
}
return title
}
implicitHeight: visible ? ((barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)) : 0
implicitWidth: visible ? ((barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)) : 0
opacity: !autoHide || hasActivePlayer || (!hasActivePlayer && !autoHide) ? 1.0 : 0
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
function getTitle() {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
}
function calculatedVerticalHeight() {
return Math.round(Style.baseWidgetSize * 0.8 * scaling)
}
// A hidden text element to safely measure the full title width
NText {
id: fullTitleMetrics
@@ -31,137 +88,245 @@ Row {
}
Rectangle {
// Let the Rectangle size itself based on its content (the Row)
width: row.width + Style.marginM * scaling * 2
height: Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
id: mediaMini
visible: root.visible
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: (barPosition === "left" || barPosition === "right") ? width / 2 : Math.round(Style.radiusM * scaling)
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
id: mainContainer
anchors.fill: parent
anchors.leftMargin: Style.marginS * scaling
anchors.rightMargin: Style.marginS * scaling
anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
Loader {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "linear"
&& MediaService.isPlaying
active: showVisualizer && visualizerType == "linear"
z: 0
sourceComponent: LinearSpectrum {
width: mainContainer.width - Style.marginS * scaling
height: 20 * scaling
values: CavaService.values
fillColor: Color.mOnSurfaceVariant
fillColor: Color.mPrimary
opacity: 0.4
}
Loader {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "mirrored"
&& MediaService.isPlaying
z: 0
sourceComponent: MirroredSpectrum {
width: mainContainer.width - Style.marginS * scaling
height: mainContainer.height - Style.marginS * scaling
values: CavaService.values
fillColor: Color.mOnSurfaceVariant
opacity: 0.4
}
}
Loader {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "wave"
&& MediaService.isPlaying
z: 0
sourceComponent: WaveSpectrum {
width: mainContainer.width - Style.marginS * scaling
height: mainContainer.height - Style.marginS * scaling
values: CavaService.values
fillColor: Color.mOnSurfaceVariant
opacity: 0.4
}
}
}
Row {
id: row
Loader {
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginXS * scaling
anchors.horizontalCenter: parent.horizontalCenter
active: showVisualizer && visualizerType == "mirrored"
z: 0
sourceComponent: MirroredSpectrum {
width: mainContainer.width - Style.marginS * scaling
height: mainContainer.height - Style.marginS * scaling
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.4
}
}
Loader {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
active: showVisualizer && visualizerType == "wave"
z: 0
sourceComponent: WaveSpectrum {
width: mainContainer.width - Style.marginS * scaling
height: mainContainer.height - Style.marginS * scaling
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.4
}
}
// Horizontal layout for top/bottom bars
RowLayout {
id: rowLayout
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling
visible: (barPosition === "top" || barPosition === "bottom")
z: 1 // Above the visualizer
NIcon {
id: windowIcon
text: MediaService.isPlaying ? "pause" : "play_arrow"
font.pointSize: Style.fontSizeL * scaling
icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc"
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
pointSize: Style.fontSizeL * scaling
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
visible: !Settings.data.audio.showMiniplayerAlbumArt && getTitle() !== "" && !trackArt.visible
Layout.alignment: Qt.AlignVCenter
visible: !hasActivePlayer || (!showAlbumArt && !trackArt.visible)
}
Column {
anchors.verticalCenter: parent.verticalCenter
visible: Settings.data.audio.showMiniplayerAlbumArt
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
visible: showAlbumArt && hasActivePlayer
spacing: 0
Rectangle {
width: 18 * scaling
height: 18 * scaling
radius: width * 0.5
color: Color.transparent
antialiasing: true
clip: true
Item {
Layout.preferredWidth: Math.round(18 * scaling)
Layout.preferredHeight: Math.round(18 * scaling)
NImageCircled {
id: trackArt
visible: MediaService.trackArtUrl.toString() !== ""
anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter
anchors.margins: scaling
imagePath: MediaService.trackArtUrl
fallbackIcon: MediaService.isPlaying ? "pause" : "play_arrow"
fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play"
fallbackIconSize: 10 * scaling
borderWidth: 0
border.color: Color.transparent
}
// Fallback icon when no album art available
NIcon {
id: windowIconFallback
text: MediaService.isPlaying ? "pause" : "play_arrow"
font.pointSize: Style.fontSizeL * scaling
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
visible: getTitle() !== "" && !trackArt.visible
}
}
}
NText {
id: titleText
Item {
id: titleContainer
Layout.preferredWidth: {
// Calculate available width based on other elements in the row
var iconWidth = (windowIcon.visible ? (Style.fontSizeL * scaling + Style.marginS * scaling) : 0)
var albumArtWidth = (hasActivePlayer && showAlbumArt ? (18 * scaling + Style.marginS * scaling) : 0)
var totalMargins = Style.marginXXS * scaling * 2
var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins
return Math.max(20 * scaling, availableWidth)
}
Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: titleText.height
// If hovered or just switched window, show up to 400 pixels
// If not hovered show up to 150 pixels
width: (mouseArea.containsMouse) ? Math.min(fullTitleMetrics.contentWidth,
400 * scaling) : Math.min(fullTitleMetrics.contentWidth,
150 * scaling)
text: getTitle()
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
color: Color.mTertiary
clip: true
Behavior on width {
property bool isScrolling: false
property bool isResetting: false
property real textWidth: fullTitleMetrics.contentWidth
property real containerWidth: 0
property bool needsScrolling: textWidth > containerWidth
// Timer for "always" mode with delay
Timer {
id: scrollStartTimer
interval: 1000
repeat: false
onTriggered: {
if (scrollingMode === "always" && titleContainer.needsScrolling) {
titleContainer.isScrolling = true
titleContainer.isResetting = false
}
}
}
// Update scrolling state based on mode
property var updateScrollingState: function () {
if (scrollingMode === "never") {
isScrolling = false
isResetting = false
} else if (scrollingMode === "always") {
if (needsScrolling) {
if (mouseArea.containsMouse) {
isScrolling = false
isResetting = true
} else {
scrollStartTimer.restart()
}
} else {
scrollStartTimer.stop()
isScrolling = false
isResetting = false
}
} else if (scrollingMode === "hover") {
if (mouseArea.containsMouse && needsScrolling) {
isScrolling = true
isResetting = false
} else {
isScrolling = false
if (needsScrolling) {
isResetting = true
}
}
}
}
onWidthChanged: {
containerWidth = width
updateScrollingState()
}
Component.onCompleted: {
containerWidth = width
updateScrollingState()
}
Connections {
target: mouseArea
function onContainsMouseChanged() {
titleContainer.updateScrollingState()
}
}
// Scrolling content
Item {
id: scrollContainer
height: parent.height
width: parent.width
property real scrollX: 0
x: scrollX
RowLayout {
spacing: 50 * scaling // Gap between text copies
NText {
id: titleText
text: hasActivePlayer ? getTitle() : placeholderText
pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
verticalAlignment: Text.AlignVCenter
horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
}
NText {
text: hasActivePlayer ? getTitle() : placeholderText
font: titleText.font
verticalAlignment: Text.AlignVCenter
horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
visible: hasActivePlayer && titleContainer.needsScrolling && titleContainer.isScrolling
}
}
// Reset animation
NumberAnimation on scrollX {
running: titleContainer.isResetting
to: 0
duration: 300
easing.type: Easing.OutQuad
onFinished: {
titleContainer.isResetting = false
}
}
// Seamless infinite scroll
NumberAnimation on scrollX {
id: infiniteScroll
running: titleContainer.isScrolling && !titleContainer.isResetting
from: 0
to: -(titleContainer.textWidth + 50 * scaling) // Scroll one complete text width + gap
duration: Math.max(4000, getTitle().length * 120)
loops: Animation.Infinite
easing.type: Easing.Linear
}
}
Behavior on Layout.preferredWidth {
NumberAnimation {
duration: Style.animationSlow
easing.type: Easing.InOutCubic
@@ -170,13 +335,67 @@ Row {
}
}
// Vertical layout for left/right bars - icon only
Item {
id: verticalLayout
anchors.centerIn: parent
width: parent.width - Style.marginM * scaling * 2
height: parent.height - Style.marginM * scaling * 2
visible: barPosition === "left" || barPosition === "right"
z: 1 // Above the visualizer
// Media icon
Item {
width: Style.baseWidgetSize * 0.5 * scaling
height: Style.baseWidgetSize * 0.5 * scaling
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
}
}
}
// Mouse area for hover detection
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: MediaService.playPause()
cursorShape: hasActivePlayer ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
if (!hasActivePlayer || !MediaService.currentPlayer || !MediaService.canPlay) {
return
}
if (mouse.button === Qt.LeftButton) {
MediaService.playPause()
} else if (mouse.button == Qt.RightButton) {
MediaService.next()
// Need to hide the tooltip instantly
tooltip.visible = false
} else if (mouse.button == Qt.MiddleButton) {
MediaService.previous()
// Need to hide the tooltip instantly
tooltip.visible = false
}
}
onEntered: {
var textToShow = hasActivePlayer ? tooltipText : placeholderText
if ((textToShow !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) {
TooltipService.show(root, textToShow, BarService.getTooltipDirection())
}
}
onExited: {
TooltipService.hide()
}
}
}
}

View File

@@ -0,0 +1,128 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Commons
import qs.Modules.Settings
import qs.Services
import qs.Widgets
import qs.Modules.Bar.Extras
Item {
id: root
property ShellScreen screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
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
// Used to avoid opening the pill on Quickshell startup
property bool firstInputVolumeReceived: false
property int wheelAccumulator: 0
implicitWidth: pill.width
implicitHeight: pill.height
function getIcon() {
if (AudioService.inputMuted) {
return "microphone-mute"
}
return (AudioService.inputVolume <= Number.EPSILON) ? "microphone-mute" : "microphone"
}
// Connection used to open the pill when input volume changes
Connections {
target: AudioService.source?.audio ? AudioService.source?.audio : null
function onVolumeChanged() {
// Logger.log("Bar:Microphone", "onInputVolumeChanged")
if (!firstInputVolumeReceived) {
// Ignore the first volume change
firstInputVolumeReceived = true
} else {
pill.show()
externalHideTimer.restart()
}
}
}
// Connection used to open the pill when input mute state changes
Connections {
target: AudioService.source?.audio ? AudioService.source?.audio : null
function onMutedChanged() {
// Logger.log("Bar:Microphone", "onInputMutedChanged")
if (!firstInputVolumeReceived) {
// Ignore the first mute change
firstInputVolumeReceived = true
} else {
pill.show()
externalHideTimer.restart()
}
}
}
Timer {
id: externalHideTimer
running: false
interval: 1500
onTriggered: {
pill.hide()
}
}
BarPill {
id: pill
screen: root.screen
rightOpen: BarService.getPillDirection(root)
icon: getIcon()
compact: (Settings.data.bar.density === "compact")
autoHide: false // Important to be false so we can hover as long as we want
text: Math.round(AudioService.inputVolume * 100)
suffix: "%"
forceOpen: displayMode === "alwaysShow"
forceClose: displayMode === "alwaysHide"
tooltipText: I18n.tr("tooltips.microphone-volume-at", {
"volume": Math.round(AudioService.inputVolume * 100)
})
onWheel: function (delta) {
wheelAccumulator += delta
if (wheelAccumulator >= 120) {
wheelAccumulator = 0
AudioService.setInputVolume(AudioService.inputVolume + AudioService.stepVolume)
} else if (wheelAccumulator <= -120) {
wheelAccumulator = 0
AudioService.setInputVolume(AudioService.inputVolume - AudioService.stepVolume)
}
}
onClicked: {
AudioService.setInputMuted(!AudioService.inputMuted)
}
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
settingsPanel.open()
}
onMiddleClicked: {
Quickshell.execDetached(["pwvucontrol"])
}
}
}

View File

@@ -0,0 +1,50 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Modules.Settings
import qs.Services
import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
colorBg: Settings.data.nightLight.forced ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Settings.data.nightLight.forced ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off"
tooltipText: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? I18n.tr("tooltips.night-light-forced") : I18n.tr("tooltips.night-light-enabled")) : I18n.tr("tooltips.night-light-disabled")
tooltipDirection: BarService.getTooltipDirection()
onClicked: {
// Check if wlsunset is available before enabling night light
if (!ProgramCheckerService.wlsunsetAvailable) {
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"))
return
}
if (!Settings.data.nightLight.enabled) {
Settings.data.nightLight.enabled = true
Settings.data.nightLight.forced = false
} else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
Settings.data.nightLight.forced = true
} else {
Settings.data.nightLight.enabled = false
Settings.data.nightLight.forced = false
}
}
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Display
settingsPanel.open()
}
}

View File

@@ -11,14 +11,79 @@ NIconButton {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
sizeMultiplier: 0.8
icon: "notifications"
tooltipText: "Notification History"
colorBg: Color.mSurfaceVariant
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
function lastSeenTs() {
return Settings.data.notifications?.lastSeenTs || 0
}
function computeUnreadCount() {
var since = lastSeenTs()
var count = 0
var model = NotificationService.historyList
for (var i = 0; i < model.count; i++) {
var item = model.get(i)
var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp
if (ts > since)
count++
}
return count
}
baseSize: Style.capsuleHeight
compact: (Settings.data.bar.density === "compact")
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
tooltipText: Settings.data.notifications.doNotDisturb ? I18n.tr("tooltips.open-notification-history-disable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd")
tooltipDirection: BarService.getTooltipDirection()
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
onClicked: PanelService.getPanel("notificationHistoryPanel")?.toggle(screen)
onClicked: {
var panel = PanelService.getPanel("notificationHistoryPanel")
panel?.toggle(this)
Settings.data.notifications.lastSeenTs = Time.timestamp * 1000
}
onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
Loader {
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 2 * scaling
anchors.topMargin: 1 * scaling
z: 2
active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0)
sourceComponent: Rectangle {
id: badge
readonly property int count: computeUnreadCount()
height: 8 * scaling
width: height
radius: height / 2
color: Color.mError
border.color: Color.mSurface
border.width: 1
visible: count > 0 || !hideWhenZero
}
}
}

View File

@@ -10,51 +10,20 @@ NIconButton {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property var powerProfiles: PowerProfiles
readonly property bool hasPP: powerProfiles.hasPerformanceProfile
property real scaling: 1.0
sizeMultiplier: 0.8
visible: hasPP
baseSize: Style.capsuleHeight
visible: PowerProfileService.available
function profileIcon() {
if (!hasPP)
return "balance"
if (powerProfiles.profile === PowerProfile.Performance)
return "speed"
if (powerProfiles.profile === PowerProfile.Balanced)
return "balance"
if (powerProfiles.profile === PowerProfile.PowerSaver)
return "eco"
}
function profileName() {
if (!hasPP)
return "Unknown"
if (powerProfiles.profile === PowerProfile.Performance)
return "Performance"
if (powerProfiles.profile === PowerProfile.Balanced)
return "Balanced"
if (powerProfiles.profile === PowerProfile.PowerSaver)
return "Power Saver"
}
function changeProfile() {
if (!hasPP)
return
if (powerProfiles.profile === PowerProfile.Performance)
powerProfiles.profile = PowerProfile.PowerSaver
else if (powerProfiles.profile === PowerProfile.Balanced)
powerProfiles.profile = PowerProfile.Performance
else if (powerProfiles.profile === PowerProfile.PowerSaver)
powerProfiles.profile = PowerProfile.Balanced
}
icon: root.profileIcon()
tooltipText: root.profileName()
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
icon: PowerProfileService.getIcon()
tooltipText: I18n.tr("tooltips.power-profile", {
"profile": PowerProfileService.getName()
})
tooltipDirection: BarService.getTooltipDirection()
compact: (Settings.data.bar.density === "compact")
colorBg: (PowerProfileService.profile === PowerProfile.Balanced) ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary
colorFg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnSurface : Color.mOnPrimary
colorBorder: Color.transparent
colorBorderHover: Color.transparent
onClicked: root.changeProfile()
onClicked: PowerProfileService.cycleProfile()
}

View File

@@ -0,0 +1,23 @@
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
// Screen Recording Indicator
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
icon: "camera-video"
tooltipText: ScreenRecorderService.isRecording ? I18n.tr("tooltips.click-to-stop-recording") : I18n.tr("tooltips.click-to-start-recording")
tooltipDirection: BarService.getTooltipDirection()
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
onClicked: ScreenRecorderService.toggleRecording()
}

View File

@@ -1,21 +0,0 @@
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
// Screen Recording Indicator
NIconButton {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
visible: ScreenRecorderService.isRecording
icon: "videocam"
tooltipText: "Screen Recording Active\nClick To Stop Recording"
sizeMultiplier: 0.8
colorBg: Color.mPrimary
colorFg: Color.mOnPrimary
anchors.verticalCenter: parent.verticalCenter
onClicked: ScreenRecorderService.toggleRecording()
}

View File

@@ -0,0 +1,24 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
icon: "power"
tooltipText: I18n.tr("tooltips.session-menu")
tooltipDirection: BarService.getTooltipDirection()
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mError
colorBorder: Color.transparent
colorBorderHover: Color.transparent
onClicked: PanelService.getPanel("sessionMenuPanel")?.toggle()
}

View File

@@ -1,23 +0,0 @@
import Quickshell
import qs.Commons
import qs.Widgets
import qs.Services
NIconButton {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
icon: "widgets"
tooltipText: "Open Side Panel"
sizeMultiplier: 0.8
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
anchors.verticalCenter: parent.verticalCenter
onClicked: PanelService.getPanel("sidePanel")?.toggle(screen)
}

View File

@@ -0,0 +1,40 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
Item {
id: root
// Widget properties passed from Bar.qml
property var screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
// Use settings or defaults from BarWidgetRegistry
readonly property int spacerWidth: widgetSettings.width !== undefined ? widgetSettings.width : widgetMetadata.width
// Set the width based on user settings
implicitWidth: spacerWidth * scaling
implicitHeight: Style.barHeight * scaling
width: implicitWidth
height: implicitHeight
}

View File

@@ -1,98 +1,335 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
Row {
Rectangle {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
Rectangle {
// Let the Rectangle size itself based on its content (the Row)
width: row.width + Style.marginM * scaling * 2
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
height: Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
anchors.verticalCenter: parent.verticalCenter
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp
readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage !== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage
readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent !== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage !== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
readonly property real iconSize: textSize * 1.4
readonly property real textSize: {
var base = isVertical ? width * 0.82 : height
return Math.max(1, compact ? base * 0.43 : base * 0.33)
}
readonly property int percentTextWidth: Math.ceil(percentMetrics.tightBoundingRect.width + 2)
readonly property int tempTextWidth: Math.ceil(tempMetrics.tightBoundingRect.width + 2)
readonly property int memTextWidth: Math.ceil(memMetrics.tightBoundingRect.width + 2)
TextMetrics {
id: percentMetrics
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightMedium
font.pointSize: textSize * Settings.data.ui.fontFixedScale
text: "99%" // Use the longest possible string for measurement
}
TextMetrics {
id: tempMetrics
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightMedium
font.pointSize: textSize * Settings.data.ui.fontFixedScale
text: "99°" // Use the longest possible string for measurement
}
TextMetrics {
id: memMetrics
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightMedium
font.pointSize: textSize * Settings.data.ui.fontFixedScale
text: "99.9K" // Longest value part of network speed
}
anchors.centerIn: parent
implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : Math.round(mainGrid.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: isVertical ? Math.round(mainGrid.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
GridLayout {
id: mainGrid
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? -1 : 1
columns: isVertical ? 1 : -1
rowSpacing: isVertical ? (Style.marginM * scaling) : 0
columnSpacing: isVertical ? 0 : (Style.marginM * scaling)
// CPU Usage Component
Item {
id: mainContainer
anchors.fill: parent
anchors.leftMargin: Style.marginS * scaling
anchors.rightMargin: Style.marginS * scaling
Layout.preferredWidth: isVertical ? root.width : iconSize + percentTextWidth + (Style.marginXXS * scaling)
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showCpuUsage
Row {
id: row
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling
Row {
id: cpuUsageLayout
spacing: Style.marginXS * scaling
GridLayout {
id: cpuUsageContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: Style.marginXXS * scaling
NIcon {
id: cpuUsageIcon
text: "speed"
anchors.verticalCenter: parent.verticalCenter
}
NText {
id: cpuUsageText
text: `${SystemStatService.cpuUsage}%`
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
NIcon {
icon: "cpu-usage"
pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
// CPU Temperature Component
Row {
id: cpuTempLayout
// spacing is thin here to compensate for the vertical thermometer icon
spacing: Style.marginXXS * scaling
NText {
text: `${Math.round(SystemStatService.cpuUsage)}%`
family: Settings.data.ui.fontFixed
pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : percentTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
NIcon {
text: "thermometer"
anchors.verticalCenter: parent.verticalCenter
}
// CPU Temperature Component
Item {
Layout.preferredWidth: isVertical ? root.width : (iconSize + tempTextWidth) + (Style.marginXXS * scaling)
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showCpuTemp
NText {
text: `${SystemStatService.cpuTemp}°C`
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
GridLayout {
id: cpuTempContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: Style.marginXXS * scaling
NIcon {
icon: "cpu-temperature"
pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
// Memory Usage Component
Row {
id: memoryUsageLayout
spacing: Style.marginXS * scaling
NText {
text: `${Math.round(SystemStatService.cpuTemp)}°`
family: Settings.data.ui.fontFixed
pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : tempTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
NIcon {
text: "memory"
anchors.verticalCenter: parent.verticalCenter
}
// Memory Usage Component
Item {
Layout.preferredWidth: isVertical ? root.width : iconSize + (showMemoryAsPercent ? percentTextWidth : memTextWidth) + (Style.marginXXS * scaling)
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showMemoryUsage
NText {
text: `${SystemStatService.memoryUsageGb}G`
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
GridLayout {
id: memoryContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: Style.marginXXS * scaling
NIcon {
icon: "memory"
pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
NText {
text: showMemoryAsPercent ? `${Math.round(SystemStatService.memPercent)}%` : `${SystemStatService.memGb.toFixed(1)}G`
family: Settings.data.ui.fontFixed
pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : (showMemoryAsPercent ? percentTextWidth : memTextWidth)
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
// Network Download Speed Component
Item {
Layout.preferredWidth: isVertical ? root.width : iconSize + memTextWidth + (Style.marginXXS * scaling)
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showNetworkStats
GridLayout {
id: downloadContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: Style.marginXXS * scaling
NIcon {
icon: "download-speed"
pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
NText {
text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.rxSpeed) : SystemStatService.formatSpeed(SystemStatService.rxSpeed)
family: Settings.data.ui.fontFixed
pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : memTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
// Network Upload Speed Component
Item {
Layout.preferredWidth: isVertical ? root.width : iconSize + memTextWidth + (Style.marginXXS * scaling)
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showNetworkStats
GridLayout {
id: uploadContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: Style.marginXXS * scaling
NIcon {
icon: "upload-speed"
pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
NText {
text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.txSpeed) : SystemStatService.formatSpeed(SystemStatService.txSpeed)
family: Settings.data.ui.fontFixed
pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : memTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
// Disk Usage Component (primary drive)
Item {
Layout.preferredWidth: isVertical ? root.width : iconSize + percentTextWidth + (Style.marginXXS * scaling)
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showDiskUsage
GridLayout {
id: diskContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: Style.marginXXS * scaling
NIcon {
icon: "storage"
pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
NText {
text: `${SystemStatService.diskPercent}%`
family: Settings.data.ui.fontFixed
pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : percentTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}

View File

@@ -0,0 +1,125 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
Rectangle {
id: root
property ShellScreen screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property real itemSize: compact ? Style.capsuleHeight * 0.9 * scaling : Style.capsuleHeight * 0.8 * scaling
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
// Always visible when there are toplevels
implicitWidth: isVerticalBar ? Math.round(Style.capsuleHeight * scaling) : taskbarLayout.implicitWidth + Style.marginM * scaling * 2
implicitHeight: isVerticalBar ? taskbarLayout.implicitHeight + Style.marginM * scaling * 2 : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
GridLayout {
id: taskbarLayout
anchors.fill: parent
anchors {
leftMargin: isVerticalBar ? undefined : Style.marginM * scaling
rightMargin: isVerticalBar ? undefined : Style.marginM * scaling
topMargin: compact ? 0 : isVerticalBar ? Style.marginM * scaling : undefined
bottomMargin: compact ? 0 : isVerticalBar ? Style.marginM * scaling : undefined
}
// Configure GridLayout to behave like RowLayout or ColumnLayout
rows: isVerticalBar ? -1 : 1 // -1 means unlimited
columns: isVerticalBar ? 1 : -1 // -1 means unlimited
rowSpacing: isVerticalBar ? Style.marginXXS * root.scaling : 0
columnSpacing: isVerticalBar ? 0 : Style.marginXXS * root.scaling
Repeater {
model: CompositorService.windows
delegate: Item {
id: taskbarItem
required property var modelData
property ShellScreen screen: root.screen
visible: (!widgetSettings.onlySameOutput || modelData.output == screen.name) && (!widgetSettings.onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(ws => ws.id).includes(modelData.workspaceId))
Layout.preferredWidth: root.itemSize
Layout.preferredHeight: root.itemSize
Layout.alignment: Qt.AlignCenter
IconImage {
id: appIcon
width: parent.width
height: parent.height
source: ThemeIcons.iconForAppId(taskbarItem.modelData.appId)
smooth: true
asynchronous: true
opacity: modelData.isFocused ? Style.opacityFull : 0.6
Rectangle {
anchors.bottomMargin: -2 * scaling
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
id: iconBackground
width: 4 * scaling
height: 4 * scaling
color: modelData.isFocused ? Color.mPrimary : Color.transparent
radius: width * 0.5
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: function (mouse) {
if (!taskbarItem.modelData)
return
if (mouse.button === Qt.LeftButton) {
try {
CompositorService.focusWindow(taskbarItem.modelData.id)
} catch (error) {
Logger.error("Taskbar", "Failed to activate toplevel: " + error)
}
} else if (mouse.button === Qt.RightButton) {
try {
CompositorService.closeWindow(taskbarItem.modelData.id)
} catch (error) {
Logger.error("Taskbar", "Failed to close toplevel: " + error)
}
}
}
onEntered: TooltipService.show(taskbarItem, taskbarItem.modelData.title || taskbarItem.modelData.appId || "Unknown app.", BarService.getTooltipDirection())
onExited: TooltipService.hide()
}
}
}
}
}

View File

@@ -14,27 +14,38 @@ Rectangle {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
readonly property real itemSize: 24 * scaling
property real scaling: 1.0
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property real itemSize: isVertical ? width * 0.75 : height * 0.85
function onLoaded() {
// When the widget is fully initialized with its props set the screen for the trayMenu
if (trayMenu.item) {
trayMenu.item.screen = screen
}
}
visible: SystemTray.items.values.length > 0
implicitWidth: tray.width + Style.marginM * scaling * 2
implicitHeight: Math.round(Style.capsuleHeight * scaling)
implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginS * scaling * 2)
implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginS * scaling * 2) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Layout.alignment: Qt.AlignVCenter
Row {
id: tray
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
Flow {
id: trayFlow
anchors.centerIn: parent
spacing: Style.marginS * scaling
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
Repeater {
id: repeater
model: SystemTray.items
delegate: Item {
width: itemSize
height: itemSize
@@ -42,6 +53,9 @@ Rectangle {
IconImage {
id: trayIcon
property ShellScreen screen: root.screen
anchors.centerIn: parent
width: Style.marginL * scaling
height: Style.marginL * scaling
@@ -91,7 +105,7 @@ Rectangle {
modelData.secondaryActivate && modelData.secondaryActivate()
} else if (mouse.button === Qt.RightButton) {
trayTooltip.hide()
TooltipService.hideImmediately()
// Close the menu if it was visible
if (trayPanel && trayPanel.visible) {
@@ -102,9 +116,21 @@ Rectangle {
if (modelData.hasMenu && modelData.menu && trayMenu.item) {
trayPanel.open()
// Anchor the menu to the tray icon item (parent) and position it below the icon
const menuX = (width / 2) - (trayMenu.item.width / 2)
const menuY = (Style.barHeight * scaling)
// Position menu based on bar position
let menuX, menuY
if (barPosition === "left") {
// For left bar: position menu to the right of the bar
menuX = width + Style.marginM * scaling
menuY = 0
} else if (barPosition === "right") {
// For right bar: position menu to the left of the bar
menuX = -trayMenu.item.width - Style.marginM * scaling
menuY = 0
} else {
// For horizontal bars: center horizontally and position below
menuX = (width / 2) - (trayMenu.item.width / 2)
menuY = Math.round(Style.barHeight * scaling)
}
trayMenu.item.menu = modelData.menu
trayMenu.item.showAt(parent, menuX, menuY)
} else {
@@ -112,15 +138,11 @@ Rectangle {
}
}
}
onEntered: trayTooltip.show()
onExited: trayTooltip.hide()
}
NTooltip {
id: trayTooltip
target: trayIcon
text: modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item"
positionAbove: Settings.data.bar.position === "bottom"
onEntered: {
trayPanel.close()
TooltipService.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
}
onExited: TooltipService.hide()
}
}
}
@@ -138,13 +160,14 @@ Rectangle {
function open() {
visible = true
PanelService.willOpenPanel(trayPanel)
}
function close() {
visible = false
trayMenu.item.hideMenu()
if (trayMenu.item) {
trayMenu.item.hideMenu()
}
}
// Clicking outside of the rectangle to close

View File

@@ -1,28 +1,51 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Commons
import qs.Modules.SettingsPanel
import qs.Modules.Settings
import qs.Services
import qs.Widgets
import qs.Modules.Bar.Extras
Item {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
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
// Used to avoid opening the pill on Quickshell startup
property bool firstVolumeReceived: false
property int wheelAccumulator: 0
implicitWidth: pill.width
implicitHeight: pill.height
function getIcon() {
if (AudioService.muted) {
return "volume_off"
return "volume-mute"
}
return AudioService.volume <= Number.EPSILON ? "volume_off" : (AudioService.volume < 0.33 ? "volume_down" : "volume_up")
return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high"
}
// Connection used to open the pill when volume changes
@@ -49,27 +72,42 @@ Item {
}
}
NPill {
BarPill {
id: pill
icon: getIcon()
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.volume * 100) + "%"
tooltipText: "Volume: " + Math.round(
AudioService.volume * 100) + "%\nLeft click for advanced settings.\nScroll up/down to change volume."
onWheel: function (angle) {
if (angle > 0) {
screen: root.screen
compact: (Settings.data.bar.density === "compact")
rightOpen: BarService.getPillDirection(root)
icon: getIcon()
autoHide: false // Important to be false so we can hover as long as we want
text: Math.round(AudioService.volume * 100)
suffix: "%"
forceOpen: displayMode === "alwaysShow"
forceClose: displayMode === "alwaysHide"
tooltipText: I18n.tr("tooltips.volume-at", {
"volume": Math.round(AudioService.volume * 100)
})
onWheel: function (delta) {
wheelAccumulator += delta
if (wheelAccumulator >= 120) {
wheelAccumulator = 0
AudioService.increaseVolume()
} else if (angle < 0) {
} else if (wheelAccumulator <= -120) {
wheelAccumulator = 0
AudioService.decreaseVolume()
}
}
onClicked: {
AudioService.setOutputMuted(!AudioService.muted)
}
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.AudioService
settingsPanel.open(screen)
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
settingsPanel.open()
}
onMiddleClicked: {
Quickshell.execDetached(["pwvucontrol"])
}
}
}

View File

@@ -0,0 +1,24 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
baseSize: Style.capsuleHeight
compact: (Settings.data.bar.density === "compact")
icon: "wallpaper-selector"
tooltipText: I18n.tr("tooltips.open-wallpaper-selector")
tooltipDirection: BarService.getTooltipDirection()
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this)
}

View File

@@ -11,29 +11,21 @@ NIconButton {
id: root
property ShellScreen screen
property real scaling: ScalingService.scale(screen)
property real scaling: 1.0
visible: Settings.data.network.wifiEnabled
sizeMultiplier: 0.8
Component.onCompleted: {
Logger.log("WiFi", "Widget component completed")
Logger.log("WiFi", "NetworkService available:", !!NetworkService)
if (NetworkService) {
Logger.log("WiFi", "NetworkService.networks available:", !!NetworkService.networks)
}
}
colorBg: Color.mSurfaceVariant
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
tooltipText: I18n.tr("tooltips.manage-wifi")
tooltipDirection: BarService.getTooltipDirection()
icon: {
try {
if (NetworkService.ethernet)
return "lan"
if (NetworkService.ethernetConnected) {
return "ethernet"
}
let connected = false
let signalStrength = 0
for (const net in NetworkService.networks) {
@@ -43,19 +35,12 @@ NIconButton {
break
}
}
return connected ? NetworkService.signalIcon(signalStrength) : "wifi_find"
return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off"
} catch (error) {
Logger.error("WiFi", "Error getting icon:", error)
Logger.error("Wi-Fi", "Error getting icon:", error)
return "signal_wifi_bad"
}
}
tooltipText: "Network / WiFi"
onClicked: {
try {
Logger.log("WiFi", "Button clicked, toggling panel")
PanelService.getPanel("wifiPanel")?.toggle(screen)
} catch (error) {
Logger.error("WiFi", "Error toggling panel:", error)
}
}
onClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
onRightClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
}

View File

@@ -7,12 +7,44 @@ import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services
import qs.Widgets
Item {
id: root
property ShellScreen screen: null
property real scaling: ScalingService.scale(screen)
property ShellScreen screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property real baseDimensionRatio: {
const b = compact ? 0.85 : 0.65
if (widgetSettings.labelMode === "none") {
return b * 0.75
}
return b
}
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied
property bool isDestroying: false
property bool hovered: false
@@ -22,26 +54,72 @@ Item {
property bool effectsActive: false
property color effectColor: Color.mPrimary
property int horizontalPadding: Math.round(16 * scaling)
property int spacingBetweenPills: Math.round(8 * scaling)
property int horizontalPadding: Math.round(Style.marginS * scaling)
property int spacingBetweenPills: Math.round(Style.marginXS * scaling)
// Wheel scroll handling
property int wheelAccumulatedDelta: 0
property bool wheelCooldown: false
signal workspaceChanged(int workspaceId, color accentColor)
implicitHeight: Math.round(36 * scaling)
implicitWidth: {
implicitWidth: isVertical ? Math.round(Style.barHeight * scaling) : computeWidth()
implicitHeight: isVertical ? computeHeight() : Math.round(Style.barHeight * scaling)
function getWorkspaceWidth(ws) {
const d = Style.capsuleHeight * root.baseDimensionRatio
const factor = ws.isFocused ? 2.2 : 1
return d * factor * scaling
}
function getWorkspaceHeight(ws) {
const d = Style.capsuleHeight * root.baseDimensionRatio
const factor = ws.isFocused ? 2.2 : 1
return d * factor * scaling
}
function computeWidth() {
let total = 0
for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i)
if (ws.isFocused)
total += Math.round(44 * scaling)
else if (ws.isActive)
total += Math.round(28 * scaling)
else
total += Math.round(16 * scaling)
total += getWorkspaceWidth(ws)
}
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
total += horizontalPadding * 2
return total
return Math.round(total)
}
function computeHeight() {
let total = 0
for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i)
total += getWorkspaceHeight(ws)
}
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
total += horizontalPadding * 2
return Math.round(total)
}
function getFocusedLocalIndex() {
for (var i = 0; i < localWorkspaces.count; i++) {
if (localWorkspaces.get(i).isFocused === true)
return i
}
return -1
}
function switchByOffset(offset) {
if (localWorkspaces.count === 0)
return
var current = getFocusedLocalIndex()
if (current < 0)
current = 0
var next = (current + offset) % localWorkspaces.count
if (next < 0)
next = localWorkspaces.count - 1
const ws = localWorkspaces.get(next)
if (ws && ws.idx !== undefined)
CompositorService.switchToWorkspace(ws.idx)
}
Component.onCompleted: {
@@ -53,9 +131,10 @@ Item {
}
onScreenChanged: refreshWorkspaces()
onHideUnoccupiedChanged: refreshWorkspaces()
Connections {
target: WorkspaceService
target: CompositorService
function onWorkspacesChanged() {
refreshWorkspaces()
}
@@ -64,14 +143,18 @@ Item {
function refreshWorkspaces() {
localWorkspaces.clear()
if (screen !== null) {
for (var i = 0; i < WorkspaceService.workspaces.count; i++) {
const ws = WorkspaceService.workspaces.get(i)
for (var i = 0; i < CompositorService.workspaces.count; i++) {
const ws = CompositorService.workspaces.get(i)
if (ws.output.toLowerCase() === screen.name.toLowerCase()) {
if (hideUnoccupied && !ws.isOccupied && !ws.isFocused) {
continue
}
localWorkspaces.append(ws)
}
}
}
workspaceRepeater.model = localWorkspaces
workspaceRepeaterHorizontal.model = localWorkspaces
workspaceRepeaterVertical.model = localWorkspaces
updateWorkspaceFocus()
}
@@ -103,7 +186,7 @@ Item {
property: "masterProgress"
from: 0.0
to: 1.0
duration: 1000
duration: Style.animationSlow * 2
easing.type: Easing.OutQuint
}
PropertyAction {
@@ -120,54 +203,108 @@ Item {
Rectangle {
id: workspaceBackground
width: parent.width - Style.marginS * scaling * 2
height: Math.round(Style.capsuleHeight * scaling)
width: isVertical ? Math.round(Style.capsuleHeight * scaling) : parent.width
height: isVertical ? parent.height : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
layer.enabled: true
layer.effect: MultiEffect {
shadowColor: Color.mShadow
shadowVerticalOffset: 0
shadowHorizontalOffset: 0
shadowOpacity: 0.10
}
// Debounce timer for wheel interactions
Timer {
id: wheelDebounce
interval: 150
repeat: false
onTriggered: {
root.wheelCooldown = false
root.wheelAccumulatedDelta = 0
}
}
// Scroll to switch workspaces
WheelHandler {
id: wheelHandler
target: root
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onWheel: function (event) {
if (root.wheelCooldown)
return
// Prefer vertical delta, fall back to horizontal if needed
var dy = event.angleDelta.y
var dx = event.angleDelta.x
var useDy = Math.abs(dy) >= Math.abs(dx)
var delta = useDy ? dy : dx
// One notch is typically 120
root.wheelAccumulatedDelta += delta
var step = 120
if (Math.abs(root.wheelAccumulatedDelta) >= step) {
var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1
// For vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign)
// For horizontal layout, same mapping using vertical wheel
root.switchByOffset(direction)
root.wheelCooldown = true
wheelDebounce.restart()
root.wheelAccumulatedDelta = 0
event.accepted = true
}
}
}
// Horizontal layout for top/bottom bars
Row {
id: pillRow
spacing: spacingBetweenPills
anchors.verticalCenter: workspaceBackground.verticalCenter
width: root.width - horizontalPadding * 2
x: horizontalPadding
visible: !isVertical
Repeater {
id: workspaceRepeater
id: workspaceRepeaterHorizontal
model: localWorkspaces
Item {
id: workspacePillContainer
height: Math.round(12 * scaling)
width: {
if (model.isFocused)
return Math.round(44 * scaling)
else if (model.isActive)
return Math.round(28 * scaling)
else
return Math.round(16 * scaling)
}
width: root.getWorkspaceWidth(model)
height: Style.capsuleHeight * root.baseDimensionRatio * scaling
Rectangle {
id: workspacePill
id: pill
anchors.fill: parent
radius: {
if (model.isFocused)
return Math.round(12 * scaling)
else
// half of focused height (if you want to animate this too)
return Math.round(6 * scaling)
Loader {
active: (labelMode !== "none")
sourceComponent: Component {
NText {
x: (pill.width - width) / 2
y: (pill.height - height) / 2 + (height - contentHeight) / 2
text: {
if (labelMode === "name" && model.name && model.name.length > 0) {
return model.name.substring(0, 2)
} else {
return model.idx.toString()
}
}
family: Settings.data.ui.fontFixed
pointSize: model.isFocused ? workspacePillContainer.height * 0.45 : workspacePillContainer.height * 0.42
font.capitalization: Font.AllUppercase
font.weight: Style.fontWeightBold
wrapMode: Text.Wrap
color: {
if (model.isFocused)
return Color.mOnPrimary
if (model.isUrgent)
return Color.mOnError
if (model.isActive || model.isOccupied)
return Color.mOnSecondary
return Color.mOnSecondary
}
}
}
}
radius: width * 0.5
color: {
if (model.isFocused)
return Color.mPrimary
@@ -175,10 +312,8 @@ Item {
return Color.mError
if (model.isActive || model.isOccupied)
return Color.mSecondary
if (model.isUrgent)
return Color.mError
return Color.mOutline
return Qt.alpha(Color.mSecondary, 0.3)
}
scale: model.isFocused ? 1.0 : 0.9
z: 0
@@ -188,7 +323,7 @@ Item {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
WorkspaceService.switchToWorkspace(model.idx)
CompositorService.switchToWorkspace(model.idx)
}
hoverEnabled: true
}
@@ -260,4 +395,148 @@ Item {
}
}
}
// Vertical layout for left/right bars
Column {
id: pillColumn
spacing: spacingBetweenPills
anchors.horizontalCenter: workspaceBackground.horizontalCenter
y: horizontalPadding
visible: isVertical
Repeater {
id: workspaceRepeaterVertical
model: localWorkspaces
Item {
id: workspacePillContainerVertical
width: Style.capsuleHeight * root.baseDimensionRatio * scaling
height: root.getWorkspaceHeight(model)
Rectangle {
id: pillVertical
anchors.fill: parent
Loader {
active: (labelMode !== "none")
sourceComponent: Component {
NText {
x: (pillVertical.width - width) / 2
y: (pillVertical.height - height) / 2 + (height - contentHeight) / 2
text: {
if (labelMode === "name" && model.name && model.name.length > 0) {
return model.name.substring(0, 2)
} else {
return model.idx.toString()
}
}
family: Settings.data.ui.fontFixed
pointSize: model.isFocused ? workspacePillContainerVertical.width * 0.45 : workspacePillContainerVertical.width * 0.42
font.capitalization: Font.AllUppercase
font.weight: Style.fontWeightBold
wrapMode: Text.Wrap
color: {
if (model.isFocused)
return Color.mOnPrimary
if (model.isUrgent)
return Color.mOnError
if (model.isActive || model.isOccupied)
return Color.mOnSecondary
return Color.mOnSurface
}
}
}
}
radius: width * 0.5
color: {
if (model.isFocused)
return Color.mPrimary
if (model.isUrgent)
return Color.mError
if (model.isActive || model.isOccupied)
return Color.mSecondary
return Color.mOutline
}
scale: model.isFocused ? 1.0 : 0.9
z: 0
MouseArea {
id: pillMouseAreaVertical
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
CompositorService.switchToWorkspace(model.idx)
}
hoverEnabled: true
}
// Material 3-inspired smooth animation for width, height, scale, color, opacity, and radius
Behavior on width {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
Behavior on height {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
Behavior on scale {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.InOutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutCubic
}
}
Behavior on radius {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
}
Behavior on width {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
Behavior on height {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
// Burst effect overlay for focused pill (smaller outline)
Rectangle {
id: pillBurstVertical
anchors.centerIn: workspacePillContainerVertical
width: workspacePillContainerVertical.width + 18 * root.masterProgress * scale
height: workspacePillContainerVertical.height + 18 * root.masterProgress * scale
radius: width / 2
color: Color.transparent
border.color: root.effectColor
border.width: Math.max(1, Math.round((2 + 6 * (1.0 - root.masterProgress)) * scaling))
opacity: root.effectsActive && model.isFocused ? (1.0 - root.masterProgress) * 0.7 : 0
visible: root.effectsActive && model.isFocused
z: 1
}
}
}
}
}

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