1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

Compare commits

...

405 Commits

Author SHA1 Message Date
github-actions[bot]
8b272dc2fd Update VERSION to v0.1.16 (from DMS) 2025-10-16 23:44:02 +00:00
purian23
87a919bbde Remove dupes 2025-10-16 18:07:21 -04:00
purian23
d3017e98c5 Suppress instructions after init install 2025-10-16 17:58:31 -04:00
Jaren Glenn
5758d7274e calendar: detect and parse 12-hour event start/end times (#464) 2025-10-16 16:10:05 -04:00
bbedward
0e215d69cb nm: revise enterprise flow 2025-10-16 15:53:38 -04:00
bbedward
cbaaa32ce8 matugen: prefix all internal templates with dms 2025-10-16 14:09:20 -04:00
bbedward
5c81646397 idle: remove lock before suspend, manually triggered
- depend on our dbus service telling us to lock
2025-10-16 13:52:09 -04:00
BB
30ca1fb14f Update translations/poexports/pt.json (POEditor.com) 2025-10-16 11:52:22 -04:00
bbedward
9fab49984a i18n: add portugese to export workflow 2025-10-16 11:51:52 -04:00
BB
696fa6e4f8 Update translations/poexports/ja.json (POEditor.com) 2025-10-16 11:49:03 -04:00
BB
921393e84e Update translations/poexports/ja.json (POEditor.com) 2025-10-16 11:48:43 -04:00
BB
13e894e910 Update translations/poexports/zh_CN.json (POEditor.com) 2025-10-16 11:48:39 -04:00
bbedward
7c7e8aaef3 i18n: add portugese 2025-10-16 11:48:10 -04:00
bbedward
7ea3bd9df9 animations: dynamic bar/dock animation durations 2025-10-16 11:44:05 -04:00
bbedward
7bf7d0afae lock: terminate fprint pam session when using password 2025-10-16 11:37:14 -04:00
bbedward
0d329baaca dock: restructure + icon size option + orientation improvements 2025-10-16 11:23:16 -04:00
enzi
941a87b59c fix crash in spacer (#461) 2025-10-16 10:25:29 -04:00
bbedward
a9e8ac46d8 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-16 09:58:55 -04:00
bbedward
0d3a294118 dankbar: allow layer override via env 2025-10-16 09:58:44 -04:00
purian23
7a27537632 Remove ghostty req 2025-10-16 09:55:04 -04:00
bbedward
287e778ddb clock: show seconds false by default 2025-10-16 09:52:16 -04:00
Oleksandr
ce44edb419 support for displaying seconds on a clock (#457)
Co-authored-by: Oleksandr <avktech@gmail.com>
2025-10-16 09:51:23 -04:00
bbedward
9dcd8af7a3 workspace: respect theme corner radius 2025-10-16 09:50:19 -04:00
bokicoder
76dfcd0ccb fix incorrect path in the greeter module (#459) 2025-10-16 08:20:05 -04:00
Massimo Branchini
13a188635d small fix: correct width for per-monitor dropdown (#454) 2025-10-16 08:19:30 -04:00
bbedward
cd18fd5aed theme: make portal prefer-dark sync optional 2025-10-16 00:16:34 -04:00
purian23
b277bd8014 Improve Fedora dms-greeter post install 2025-10-15 23:37:57 -04:00
purian23
daa0d368ab Update dms-greeter permissions & post install 2025-10-15 23:11:53 -04:00
purian23
2cc7777e16 DMS-Greeter spec update 2025-10-15 19:54:29 -04:00
bbedward
d276e31f7b niri: --no-preserve=mode on binds copy command 2025-10-15 18:23:53 -04:00
bbedward
7f35ba7e21 spotlight: fix context menu button click widths 2025-10-15 18:17:58 -04:00
bbedward
edd54dda84 dock: repair context menu screens & tooltips 2025-10-15 18:17:56 -04:00
purian23
a50a97314d Prefer quickshell-git 2025-10-15 18:01:05 -04:00
bbedward
4bc05e7083 idle: add lock before suspend to idle manager 2025-10-15 16:40:20 -04:00
bbedward
09a45b49a6 dock: fix show on overview logic 2025-10-15 16:14:20 -04:00
purian23
1c0b71436e Update dms-git priority 2025-10-15 16:11:50 -04:00
bbedward
24f5e9a7e6 plugins: add PluginGlobalVar
- Allow syncing vars to all widget instances, like a singleton
2025-10-15 16:08:24 -04:00
bbedward
59d123a4a1 common: dont require id in runCommand helper 2025-10-15 14:53:23 -04:00
bbedward
ed2afa03f9 revert useless changes 2025-10-15 14:52:53 -04:00
bbedward
3c531dc2ec bar: bind widget refresh to dpr changes 2025-10-15 14:43:47 -04:00
bbedward
83564bd03f media: disable layer on morphing blob
- already on the window
2025-10-15 14:38:34 -04:00
bbedward
7146d0d92d Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-15 14:23:25 -04:00
bbedward
e842d6761a theme: handle colors being deleted more gracefully 2025-10-15 14:23:02 -04:00
bbedward
17b405e9dc Also bind system tray visibility to # of items 2025-10-15 13:26:20 -04:00
bbedward
f281513a41 launcher: fix os logo on multi-monitor 2025-10-15 13:22:50 -04:00
bbedward
63b876479f Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-15 12:48:24 -04:00
bbedward
38b833c886 bar: dpr-aware canvas 2025-10-15 12:31:07 -04:00
BB
d75ea18e9f Update translations/poexports/zh_CN.json (POEditor.com) 2025-10-15 12:07:26 -04:00
bbedward
f311b20ef7 Fix zh-Hans code 2025-10-15 12:06:56 -04:00
bbedward
78f7237422 workflow: add poeditor job 2025-10-15 12:05:44 -04:00
purian23
726af3393b Copr Ghostty support 2025-10-15 11:55:13 -04:00
bbedward
c772331554 version: prefer git head over VERSION file 2025-10-15 11:36:36 -04:00
github-actions[bot]
80d257b94f Update VERSION to v0.1.15 (from DMS) 2025-10-15 15:34:20 +00:00
bbedward
e2db034959 try and fix version commit 2025-10-15 11:31:53 -04:00
bbedward
c4e88e5c05 hyprland: add keybinds cheatsheet 2025-10-15 11:18:02 -04:00
bbedward
e47e7667c6 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-15 10:33:32 -04:00
bbedward
8bb2a64663 cc: allow multiple brightness sliders 2025-10-15 10:31:55 -04:00
purian23
e056e08fc1 Prefer git build w/source 2025-10-15 10:14:34 -04:00
bbedward
342cd55bc0 loginctl: disable inhibit when loginctl integration disabled 2025-10-15 09:58:44 -04:00
bbedward
23ef19e683 bar: attempt to fix some nichhe binding issues 2025-10-15 09:16:53 -04:00
bbedward
437fd29e96 ipc: move mpris to global controller 2025-10-15 09:08:26 -04:00
bbedward
aa7a07fd99 bar: trigger repaint on opacity change 2025-10-15 09:03:31 -04:00
bbedward
5217006dec workflow: fix tag version generation 2025-10-15 09:00:58 -04:00
max72bra
ab4f6baae6 Improvement: Allow the user to perform custom power actions (#439) 2025-10-15 08:46:32 -04:00
Oleksandr
1976ea4d49 Multi batteries support (#431)
* Multi batteries support

* Multi batteries support DMS_PREFERRED_BATTERY fix

---------

Co-authored-by: Oleksandr <avktech@gmail.com>
2025-10-15 08:45:35 -04:00
bbedward
697fc4d2b7 Fix version detection 2025-10-15 08:40:12 -04:00
max72bra
38c1f7bbcb small fix: update popout column space (#441) 2025-10-15 08:16:05 -04:00
max72bra
8cbfaab807 small fix: right check on execution (#440) 2025-10-15 08:15:40 -04:00
Bruno Cesar Rocha
f4a4151632 docs: update PLUGIN docs (#443) 2025-10-15 08:14:49 -04:00
bbedward
5f810fe741 update locales 2025-10-15 00:25:48 -04:00
bbedward
adaa0caab8 fix: clip add widget listview in control center 2025-10-15 00:02:26 -04:00
bbedward
54ef14e765 ipc: Add openWithQuery and toggleWithQuery to spotlight 2025-10-14 23:56:43 -04:00
Body
d1383b5d1b remove screen dimming from dropdown menus (#435) 2025-10-14 23:44:29 -04:00
bbedward
caa703af99 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-14 23:23:42 -04:00
bbedward
90aab9f4db sounds: option to override system sound themes 2025-10-14 23:23:22 -04:00
purian23
3439030145 feat: Copr DMS-Greeter support 2025-10-14 23:11:11 -04:00
bbedward
058c7408d1 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-14 22:21:27 -04:00
purian23
a4f7fd58f6 Tag dms-git spec 2025-10-14 22:14:31 -04:00
bbedward
6f3024c90d Remove loader from Spotlight chain 2025-10-14 22:14:10 -04:00
bbedward
5f95fa5e79 Use proc helper in more places 2025-10-14 16:52:50 -04:00
bbedward
f9cb0506e9 fix session service connection 2025-10-14 15:58:10 -04:00
bbedward
2429401d0e Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-14 15:14:37 -04:00
bbedward
9ff0d7405f common: add Proc.runCommand helper 2025-10-14 15:14:20 -04:00
bbedward
5bb5cd296d config: restructure, migration system, cache data 2025-10-14 15:07:28 -04:00
purian23
273662e03e Update Copr Stable Workflow 2025-10-14 14:28:56 -04:00
bbedward
9c1a89d786 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-14 14:14:25 -04:00
bbedward
524d7ee5c0 systemupdate: use ref system to prevent executions 2025-10-14 14:14:06 -04:00
purian23
51d2bc9aae Spec update - DMS CLI Source 2025-10-14 14:12:34 -04:00
max72bra
0c8a7ff332 small detail: the timer must run on both pkgManager and updChecker (#428) 2025-10-14 14:11:33 -04:00
bbedward
309b8d9efe i18n: update terms
adjacently, fix the power menu confirm option
2025-10-14 14:06:46 -04:00
max72bra
c2f32b7bdc enhancement: let power actions confirmation optional (#427) 2025-10-14 13:59:12 -04:00
purian23
563bc7b359 Spec update 2025-10-14 13:44:05 -04:00
bbedward
94ca5a5bef set default env to prevent dGPU wakeups 2025-10-14 13:16:29 -04:00
bbedward
4464589c0f elide, dont wrap spotlight/launcher results 2025-10-14 12:58:13 -04:00
bbedward
748d4fe2ac Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-14 12:40:25 -04:00
bbedward
50b28dc8ca launcher: fix crash in plugin action execution 2025-10-14 12:39:51 -04:00
max72bra
118980a9fb bug fix: launcher settings tab -> recently used apps -> mass and single clear not saving changes (#426) 2025-10-14 12:17:26 -04:00
bbedward
8aff381676 launcher: add sort by alphabetically option
sounds: convert files to wav
2025-10-14 11:08:52 -04:00
bbedward
6d0fba1905 localization: wifi password modal strings 2025-10-14 10:57:48 -04:00
bbedward
7e885d3cee nm: enterprise options for domain and realm 2025-10-14 10:26:45 -04:00
bbedward
811daf74ff Fix spacing of night mode + auto wallpaper sections 2025-10-14 10:05:04 -04:00
bbedward
ee755b8bd6 lock: fprintd support 2025-10-14 09:18:57 -04:00
Moraxyc Xu
1019eb925a nix: add system sound support option (#422) 2025-10-14 08:18:51 -04:00
max72bra
061bb50b88 system updater: separated update finder from pkg manager (#419) 2025-10-14 08:18:34 -04:00
bbedward
eb7e665c86 Fix delegate on lock 2025-10-14 08:15:29 -04:00
bbedward
80301d1aab fix warning 2025-10-14 08:01:14 -04:00
bbedward
ea56fb5840 sounds: make qt6-multimedia optional 2025-10-14 07:58:16 -04:00
purian23
692b45c4f0 Finalize Copr Workflow 2025-10-14 01:28:58 -04:00
purian23
8d53a8826e Fix package dependencies for Ubuntu build environment 2025-10-14 00:46:15 -04:00
purian23
64ea115303 Copr Workflow & Spec 2025-10-14 00:25:10 -04:00
bbedward
b5e29cf50c Incorporate some system sounds 2025-10-13 22:38:13 -04:00
bbedward
381df1e949 fix bindings in Theme & Colors settings pane 2025-10-13 22:12:32 -04:00
bbedward
13a81eda6f calendar: fix binding loop 2025-10-13 22:05:12 -04:00
bbedward
a48c39642a Fix outdated oSD trigger 2025-10-13 22:02:34 -04:00
bbedward
3be3e622bc re-work mouse handling of dannkbar 2025-10-13 21:34:53 -04:00
bbedward
07fe2ca407 Restore correct workflow 2025-10-13 20:22:18 -04:00
bbedward
9b96dae744 Sync lock/unlock events with loginctl 2025-10-13 19:46:18 -04:00
bbedward
0e3d3d1a40 fix hyprland svg 2025-10-13 19:31:28 -04:00
Bruno Cesar Rocha
cb3274fb0c fix: get desktop entry back (#417) 2025-10-13 19:25:27 -04:00
bbedward
a3ada5b2bb Restructure lock screen 2025-10-13 17:44:09 -04:00
bbedward
3e167a2c52 Update localizations 2025-10-13 17:05:45 -04:00
bbedward
38b3ad2b31 niri: re-gen layout kdl on dbar spacing change 2025-10-13 17:03:08 -04:00
bbedward
56e5cd13b7 Add popup gaps override + math min 4 logic to match existing code 2025-10-13 16:57:18 -04:00
bbedward
d63c0fc6f0 Simplify font picking and elide contents 2025-10-13 14:58:24 -04:00
bbedward
6814b140fc bar: more repaint triggers + repaint debounce 2025-10-13 14:38:03 -04:00
max72bra
5f7e478118 Enhancement: custom system updater command (#414)
* enhancement: system updater custom command and custom additional terminal parameters

* removed console log

* minor: tabs
2025-10-13 14:25:25 -04:00
Bruno Cesar Rocha
7317024da5 feat: Launcher Plugin Component (#408)
Load launcher items from plugin.
2025-10-13 14:24:41 -04:00
Body
9b9fbabc3f tweak popout margins for consistency (#399) 2025-10-13 14:17:02 -04:00
bbedward
3c5a23799f Flip light/dark mode and icon themes properly 2025-10-13 14:16:36 -04:00
bbedward
3bfdc6163c Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-13 13:54:54 -04:00
purian23
cf2f74a38d Stock workflow release 2025-10-12 15:23:00 -04:00
bbedward
2a7f52c67e Update readme 2025-10-12 07:46:43 -04:00
Body
50fde1e308 Add niri overview toggle on launcher button rightclick. (#394) 2025-10-12 07:38:55 -04:00
max72bra
46fd0ae413 Hyprland: filter all spacial workspaces (#398)
Filters all workspaces with IDs less than 0, regardless of their name (which is user-defined)
2025-10-12 07:38:39 -04:00
bokicoder
65e32dc429 Fix keyboard focus issue in clipboard (#391) 2025-10-12 07:38:15 -04:00
Body
413675dfc1 Close ControlCenter popout on Settings open. (#396) 2025-10-12 07:34:06 -04:00
purian23
a17343f40e Enable Copr stable builds 2025-10-12 01:08:10 -04:00
bokicoder
5d023804c1 update flake.lock (#390) 2025-10-12 00:12:41 -04:00
bbedward
fa07a846b9 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-11 23:48:36 -04:00
bbedward
5df46b605e Call lockerReady to releasse inhibitor whenlock screen is drawn 2025-10-11 23:48:13 -04:00
bokicoder
77cf371a21 update flake.lock (#388) 2025-10-11 23:42:55 -04:00
bbedward
89802dd040 Presever user configs 2025-10-11 23:08:11 -04:00
bbedward
59b95e9dd6 Bundle distro package with dms releases 2025-10-11 22:21:55 -04:00
bbedward
b836db5252 Update copr-related build opts 2025-10-11 21:59:08 -04:00
purian23
4dc4b15925 Revert "Update spec to SRPM by default"
This reverts commit 5c3062e699.
2025-10-11 18:19:21 -04:00
purian23
5c3062e699 Update spec to SRPM by default 2025-10-11 17:52:26 -04:00
bbedward
3a7777c643 api: unify dms API clients
- Single subscribe socket
- Single req/callback socket
- Remove PrepareForSleep handling
- Dependency on dms API version enforcement
- Remove gdbus from portal and session
2025-10-11 14:37:18 -04:00
bbedward
71543c35d6 Re-organize settings 2025-10-11 13:04:26 -04:00
bbedward
f4cf66dc01 Update terms 2025-10-11 11:37:09 -04:00
bbedward
7870dff0fd matugen: opt to run user templates 2025-10-11 11:30:19 -04:00
bbedward
9fc9c1ed19 Update issue template 2025-10-11 10:39:34 -04:00
bbedward
4d0151350f Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-11 10:30:19 -04:00
bbedward
dff10c8d13 Update issue templates 2025-10-11 10:30:09 -04:00
purian23
362bcb9294 More cli - better spec 2025-10-11 02:14:48 -04:00
purian23
351b4f8a94 Remove CLI ref 2025-10-11 01:59:59 -04:00
bbedward
90955eb0a1 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-10 20:07:19 -04:00
bbedward
62669747ad lock/greeter: show full power menu 2025-10-10 20:07:01 -04:00
bokicoder
466d00c666 Modify the greeter module to use the dms-greeter wrapper (#373) 2025-10-10 19:20:17 -04:00
bbedward
63845ff875 Update vid 2025-10-10 18:53:52 -04:00
bbedward
d013748a51 plugins: fix variant syncing 2025-10-10 18:45:43 -04:00
bbedward
474af3bc07 Remove braindead mouse area arbitrary rules 2025-10-10 18:21:35 -04:00
bbedward
8e09e155fa greeter: update readme 2025-10-10 17:47:02 -04:00
bbedward
7f9f4f96b9 Update greeter readme 2025-10-10 17:44:06 -04:00
bbedward
cd488a8623 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-10 15:01:10 -04:00
bbedward
080b7a28b1 Fix force focus 2025-10-10 15:00:57 -04:00
purian23
6949ed0ebd No inline package comments 2025-10-10 14:57:32 -04:00
purian23
8465fa45bb Adjust inline spec comments 2025-10-10 14:49:34 -04:00
purian23
40835ffc89 Update copr dms spec 2025-10-10 14:20:47 -04:00
bbedward
01a42ff330 Fix key forward targets 2025-10-10 13:51:17 -04:00
bbedward
ba49654a64 Fix launcher context menu sizing 2025-10-10 13:42:55 -04:00
bbedward
bc6577fe18 Fix icon theme overflow 2025-10-10 12:47:22 -04:00
bbedward
4ca3f0da67 Fix tab/backtab in launchers 2025-10-10 12:40:02 -04:00
bbedward
7f2086488b Fix power menu focus activation 2025-10-10 12:29:14 -04:00
bbedward
3014fd8095 Fractional scaling fixes + bar border settings 2025-10-10 12:25:00 -04:00
bbedward
27885c8ac3 Update readme and about page 2025-10-10 00:05:49 -04:00
bbedward
d6be0509ac Put release bin in bin/ folder 2025-10-09 23:32:20 -04:00
bbedward
1c85f5e857 Make release assets more sane and understandable 2025-10-09 23:28:10 -04:00
bbedward
abe5515aca Expose ensureVisible to PluginSettings 2025-10-09 23:07:42 -04:00
bbedward
6fba975490 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-09 20:38:26 -04:00
bbedward
2de6798f45 Clean up variants from bar whhen removed from plugin data 2025-10-09 20:37:14 -04:00
purian23
04fdfa2a35 simplify Fedora Spec 2025-10-09 18:52:27 -04:00
bbedward
8f3085290d Update wrapper for consistency 2025-10-09 17:59:16 -04:00
bbedward
0839fe45f5 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-09 17:54:07 -04:00
bbedward
18f4795fda add dms-greeter wrapper 2025-10-09 17:53:56 -04:00
Parthiv Seetharaman
55d9fa622a Re-introduce default settings option and fix dms-cli build (#366)
* Reapply "Add default configuration option to home-manager (#356)"

This reverts commit bc23109f99.

* fix multiple xdg.configFile definitions error

* update dms-cli flake to fix build
2025-10-09 16:11:35 -04:00
bbedward
7dc723c764 Middle clock to close window on dock 2025-10-09 15:16:22 -04:00
bbedward
5a63205972 Fix light mode binding 2025-10-09 15:08:21 -04:00
bbedward
a4ceeafb1e Add ability to disable loginctl lock integration 2025-10-09 14:58:16 -04:00
bbedward
242e05cc0e Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-09 14:47:18 -04:00
bbedward
065dddbe6e Fix lock before suspend 2025-10-09 14:44:55 -04:00
purian23
fa6825252b prep Fedora Copr support 2025-10-09 14:42:09 -04:00
bbedward
b06e48a444 FolderListModel filters 2025-10-09 14:30:43 -04:00
bbedward
97dbd40f07 remove unused fileURL property 2025-10-09 14:17:29 -04:00
bbedward
bc23109f99 Revert "Add default configuration option to home-manager (#356)"
This reverts commit 67a4e3074e.
2025-10-09 13:53:27 -04:00
bbedward
ecb9675e9c Try more plugin loading things 2025-10-09 13:51:43 -04:00
bbedward
e1f9b9e7a4 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-09 13:37:46 -04:00
bbedward
067b485bb3 Bind perms directly to availablePlugins map 2025-10-09 13:37:34 -04:00
bokicoder
67a4e3074e Add default configuration option to home-manager (#356) 2025-10-09 13:23:44 -04:00
bokicoder
010bc4e8c3 fix systemd startup (#364) 2025-10-09 13:23:33 -04:00
bbedward
9de5e3253e Re-do plugin scanning 2025-10-09 13:22:33 -04:00
bbedward
e32622ac48 Some lockscreen restructure 2025-10-09 11:30:02 -04:00
bbedward
5e2371c2cb Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-09 09:01:39 -04:00
bbedward
a6ce26ee87 i18n: update loading + add zh_CN 2025-10-09 09:01:20 -04:00
Parthiv Seetharaman
2a72c126f1 Add nix home-manager option for adding plugins (#354)
* add plugins nix hm option

* add nix hm plugins option usage to readme
2025-10-09 08:34:05 -04:00
bbedward
36e1a5d379 Update greeter docs 2025-10-08 23:37:45 -04:00
bbedward
c12eafa1db Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-08 23:18:07 -04:00
bbedward
9e26d8755c Add os keyboard to greeter 2025-10-08 23:17:52 -04:00
Lukas Krejci
90bd30e351 add support for system updates on fedora (#353) 2025-10-08 22:19:40 -04:00
bbedward
3fb5d5c4f3 i18n: don't load en json - move animated terms to properties 2025-10-08 20:31:41 -04:00
bbedward
9f3685e4d5 clip spotlight result listview 2025-10-08 18:58:14 -04:00
bbedward
6565988952 Cleanup some of the backwards compat crap 2025-10-08 18:28:46 -04:00
bbedward
10b7a0875b Crop CircularImages not fit 2025-10-08 18:16:16 -04:00
bbedward
bb4b9f1a58 Only ignore left/right keys in grid launcher mode 2025-10-08 17:55:24 -04:00
bbedward
981e527560 Remove artifacts of search fields overriding key inputs 2025-10-08 17:36:12 -04:00
bbedward
80b2ee719c Restore ipc target 2025-10-08 17:31:26 -04:00
bbedward
6103d6196f override cachyos logo 2025-10-08 16:35:49 -04:00
bbedward
ed1a5bfded i18n: Add japanese + i18n service 2025-10-08 16:25:06 -04:00
bbedward
3909ce3350 Remove minimum menu size constraint 2025-10-08 15:23:09 -04:00
bbedward
d64cd0b8a4 Fix launcher button mask 2025-10-08 15:12:47 -04:00
bbedward
676aa9f93f Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-08 14:51:59 -04:00
bbedward
ebd48e2556 Many more logo customization options 2025-10-08 14:51:38 -04:00
bokicoder
ad5871aae4 Update flake.lock (#347) 2025-10-08 14:15:38 -04:00
bbedward
e327b1ca5b Add proxy service for legacy nmcli version 2025-10-08 14:00:51 -04:00
bbedward
ad43ca11eb Insta fallback 2025-10-08 13:41:37 -04:00
omar
41ba76e2e2 Change lock activation to use lockLoader (#346)
Fixes broken lock button
2025-10-08 13:40:27 -04:00
bbedward
15e773434e Portal/Loginctl fallbacks for DMS_SOCKET unavail 2025-10-08 13:37:03 -04:00
bbedward
ed118f4e7a Dropbox icon workaround 2025-10-08 12:54:06 -04:00
bbedward
3c420f2e30 Remove useless getter 2025-10-08 12:33:00 -04:00
bbedward
4e271d4f0e Fix search when appusage is unavailable 2025-10-08 12:07:46 -04:00
bbedward
27f9b3cd0b native NetworkManager + all native dbus bindings via dms
- Scrap janky NetworkService in favor of, dms' native NM integration
  socket
- Scrap all gdbus usage in favor of native dbus bindings in dms
  (loginctl, freedesktop)

It means that - some features won't work if running without dms wrapper.

But the trade off is certainly worth it, in the long-run for efficiency
improvements.
2025-10-08 12:03:50 -04:00
bbedward
1ed4abd347 Attempts to improve startup time 2025-10-08 10:02:54 -04:00
bbedward
f71dd1ed54 No DankIcon in notifications 2025-10-08 09:05:33 -04:00
bokicoder
19828d3b06 fix preStart script for greeter (#339) 2025-10-08 08:12:41 -04:00
bokicoder
d242e729f0 fix default-settings.json and default-session.json (#341) 2025-10-08 08:11:55 -04:00
Mirza Esaaf Shuja
8cd0d5faa5 fix: use correct hyprland config template for greeter (#338) 2025-10-07 20:48:27 -04:00
bbedward
a741d892a9 Remove MultiEffect opacity 2025-10-07 16:01:05 -04:00
bbedward
9add3361e0 DankModal pixel snapping 2025-10-07 15:32:21 -04:00
bbedward
4aac70ab5f pass config_dir as an argument to matugen-worker 2025-10-07 13:46:41 -04:00
bbedward
980aec714a Fix typo in workspace mousearea 2025-10-07 13:39:53 -04:00
bbedward
af8ee5af0f Switch to dispatch-style release workflow 2025-10-07 12:50:09 -04:00
bbedward
32d9aa0cf2 Separate bar font scale 2025-10-07 08:29:48 -04:00
bbedward
7e49631912 Migrate plugin settings to separate file 2025-10-07 08:22:21 -04:00
bbedward
43970d34aa Fix keyboard navi in file browser 2025-10-07 00:14:56 -04:00
bbedward
abb3c40697 Thread font loading 2025-10-06 23:51:49 -04:00
bbedward
2757a41102 Add invert on mode change for launcher logo 2025-10-06 23:24:39 -04:00
bbedward
1f8bddaa5e Fix dank bar on overview clicks 2025-10-06 22:35:30 -04:00
bbedward
4c3b7ca60f Support prime-run 2025-10-06 20:50:36 -04:00
bbedward
d9d83e5767 spotlight: remove categories 2025-10-06 20:42:21 -04:00
bbedward
b4ebde47be Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-06 20:40:20 -04:00
bbedward
ef9f76190d Implement sizing mechanism for bar widget content 2025-10-06 20:40:02 -04:00
Nek
71b96efca0 Allow user's matugen config to be ran by dms (#328)
* Allow user's matugen config to be ran by dms

matugen-workers.sh now also loads the configs and templates from `~/.config/matugen/dms` and handles them the same way dms does its own matugen configs.

* Do not auto create user's matugen directories
2025-10-06 20:33:44 -04:00
bbedward
7158e09b0e Customizable launcher logo 2025-10-06 20:08:21 -04:00
bbedward
8ef125bed2 Fix toast width 2025-10-06 19:42:22 -04:00
bbedward
0b11fb2fd5 Fix variant plugins in center section 2025-10-06 17:57:01 -04:00
bbedward
3871f3cf3d Aggresive focus restoration 2025-10-06 17:52:48 -04:00
bbedward
7c5d1ec0f6 launcher + dock menu improvements 2025-10-06 17:46:19 -04:00
bbedward
b507b08e34 Re-org niri service & handle reconnects to socket 2025-10-06 16:46:05 -04:00
bbedward
1b06090f72 Remove terms that shouldnt be localized 2025-10-06 16:06:19 -04:00
bbedward
5460c20ac3 Localization framework 2025-10-06 16:00:50 -04:00
bbedward
2ccec607a0 Hide third party plugins, by default 2025-10-06 15:36:33 -04:00
bbedward
8a99fcf188 Set exec working directory to home when not defined 2025-10-06 15:28:58 -04:00
bbedward
1e2489ca76 Add support for plugin registry + install + management 2025-10-06 15:26:44 -04:00
bbedward
89793d2d62 plugins: support for multiple widgets per-plugin (variants) 2025-10-06 12:33:58 -04:00
bbedward
11a1af89f4 Allow removing force-padding on monitor widgets + plugin load fixes 2025-10-06 11:32:25 -04:00
bbedward
e24ddb804d Filter out scratch_term ws on hyprland 2025-10-06 09:38:10 -04:00
bbedward
3524d365be Add Desktop Actions to launcher + dock 2025-10-06 09:35:50 -04:00
bbedward
2b3b9d037c re-add transitions 2025-10-06 09:10:58 -04:00
Bruno Cesar Rocha
5140cd9d7f fix: use correct icon on CPU temp on bar (#327) 2025-10-06 09:05:44 -04:00
Parthiv Seetharaman
2df9437b39 add nixos support for greeter (#298)
* add nixos support for greeter

* fix greeter config file access

* fix wallpaper perms and allow for adding extra compositor config

* fix greeter config files ownership

* set default for compositor.extraConfig

* update option docs about copying instead of symlinking

* explain configHome in doc further

* add nixos option to redirect greeter logs

* prevent possible errors in greetd preStart
2025-10-06 08:42:36 -04:00
purian23
db440b8a14 feat: Add a 1px border to Dankbar w/edge detection
-  No edge gap (spacing = 0 AND corner radius = 0): Draws 1px border only on the exposed edge (bottom for top bar, top for bottom bar, right for left bar, left for right bar)
- Has edge gap (spacing > 0 OR corner radius > 0): Draws 1px border around all sides
2025-10-05 23:56:40 -04:00
purian23
c3dd70bc99 fix: Color selection state 2025-10-05 23:21:00 -04:00
purian23
223e783bbc Update Notepad theme corner radius to match user settings 2025-10-05 22:43:49 -04:00
purian23
ca086dbf16 ColorPicker visual feedback upon invaid hex code 2025-10-05 22:35:27 -04:00
purian23
523422cf6c feat: Rebuilt DankColorPicker w/Color Sampling
- Fully custom built ColorDialog
- Replaces previous Wallpaper background color tool
- Requires hyprpicker for color sampling, thanks @Vaxry
2025-10-05 22:18:26 -04:00
bbedward
2dc310dcbc Add VPN widget for control center 2025-10-05 21:28:52 -04:00
bbedward
c092cd2921 plugins: support control center plugins 2025-10-05 21:09:29 -04:00
bbedward
2b14ef76c9 resolve dms-colors dir dynamically in matugen-worker 2025-10-05 19:22:17 -04:00
bbedward
fbbf10078f Fix focus issues with add widget dialog 2025-10-05 19:17:11 -04:00
Abhinav Chalise
2315d423c4 remove signal strength from ssidName (#323) 2025-10-05 19:01:03 -04:00
bbedward
9a43465ebf Fix initial light/dark mode state in cc 2025-10-05 15:28:46 -04:00
bbedward
fc1444763d Update dms-colors matugen 2025-10-05 15:07:08 -04:00
purian23
804bf879ed Default to Zenity Color picker
- QT 6.9.3 removed the previous color picker we used
- Zenity is default in gnome based distros
- Kcolorchooser can be installed separately and will be preferred over Zenity
2025-10-04 22:59:02 -04:00
purian23
ad44f09421 feat: Display persistent OSD percentage option 2025-10-04 22:02:25 -04:00
purian23
df2469468b Refactor Notepad search 2025-10-04 14:38:30 -04:00
bbedward
f8f4fe11eb Explicit null 2025-10-04 12:10:03 -04:00
bbedward
039a370add Explicit battery override 2025-10-04 12:07:59 -04:00
bbedward
6feebc086d Update readme 2025-10-04 09:47:28 -04:00
bbedward
bc335c7d72 Remove arbitrary height limit on notification settings 2025-10-04 08:46:35 -04:00
bbedward
a6dd7254b2 Clip system tab flickable 2025-10-04 08:40:17 -04:00
bbedward
7b1026c624 Include env to override battery device 2025-10-04 08:23:49 -04:00
bbedward
4758393cc1 Fix dock hide with padding 2025-10-04 08:17:41 -04:00
bbedward
52373a3a7d Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-04 08:12:51 -04:00
bbedward
c30f9a2841 use wayland idle-inhibit when available 2025-10-04 08:12:18 -04:00
Abhinav Chalise
44d6f8f15c Fix toggle function to return correct theme mode (#309) 2025-10-04 07:36:23 -04:00
purian23
5ada12f989 feat: Add Notepad search function 2025-10-04 02:25:42 -04:00
bbedward
d213045168 default instead of prefer-light 2025-10-04 01:18:43 -04:00
bbedward
d83478239e plugins: add pillClickAction + PopoutService 2025-10-04 01:12:17 -04:00
purian23
3869955357 Update Notepad entry text color 2025-10-04 00:50:47 -04:00
bbedward
345d37edf8 Shift urgent workspace color 2025-10-04 00:11:49 -04:00
bbedward
2788ef28cf Update README 2025-10-03 23:21:46 -04:00
bbedward
0d5c1bb3df Add "daemon" type of plugins 2025-10-03 22:55:07 -04:00
bbedward
c3d505cdad better proc usage 2025-10-03 19:35:13 -04:00
bbedward
90854e1dd4 version in about tab and ci 2025-10-03 19:31:07 -04:00
bbedward
f96e3b04be Disable powermenu bg on cc 2025-10-03 18:29:06 -04:00
bbedward
44449e26a0 Handle urgent workspaces 2025-10-03 18:17:24 -04:00
bbedward
ddc88fd360 Fix positioning of power menu 2025-10-03 17:21:43 -04:00
bbedward
fedec450cb Keep the modal, but relatively positioned 2025-10-03 17:16:34 -04:00
bbedward
04ea742830 Reapply "Always use power menu modal"
This reverts commit 5a5c860cef.
2025-10-03 17:14:43 -04:00
bbedward
5a5c860cef Revert "Always use power menu modal"
This reverts commit 55d06a43f8.

mm, not sure how I feel about it
2025-10-03 17:14:08 -04:00
bbedward
55d06a43f8 Always use power menu modal 2025-10-03 16:54:10 -04:00
bbedward
71eecd6e7b Revert "betterbird/thunderbird matugen template"
This reverts commit 6f3019f84b.
2025-10-03 16:20:21 -04:00
bbedward
6f3019f84b betterbird/thunderbird matugen template 2025-10-03 16:16:47 -04:00
bbedward
e95d3126b2 Modal/Popout layout alterations 2025-10-03 15:15:44 -04:00
bbedward
5da265bf0b Instruct fonts to be global (makes sense for greeter) 2025-10-03 13:52:39 -04:00
bbedward
2ce9c43b8c disable layer debug opt 2025-10-03 11:21:43 -04:00
bbedward
740b2f206c Alter loading behavior 2025-10-03 10:08:05 -04:00
bbedward
af622bc7e7 Also concat local ones 2025-10-03 08:51:41 -04:00
bbedward
7816b50b27 Fallback greeter directories 2025-10-03 08:42:55 -04:00
bbedward
4cb7a909f7 Set default instead of prefer-light 2025-10-03 08:39:02 -04:00
bbedward
0fac88e171 namespace for notepad 2025-10-03 08:30:43 -04:00
bbedward
b4ab9d9650 XDG_DATA_DIRS for greeter 2025-10-02 22:10:46 -04:00
bbedward
731db13c14 Fix bindings in center section & icon sizes 2025-10-02 21:41:24 -04:00
bbedward
414a1ad4d2 Tweak colors a bit 2025-10-02 20:15:20 -04:00
Bruno Cesar Rocha
16055fe96e fix: Plugin settings not loading existing settings (#294)
I notice the plugin settings tab was not loading the
existing plugin settings from the settings file.

It was properly writting to the file, but not reloading
on initialization.

added a loadValue() function so PluginSettings can call when the
pluginService is injected. Added isLoading state flag to prevent
the onItemsChanged from saving back settings when loading is happening.
Added null wise checks for properties that may be not ready yet
when loading.
2025-10-02 14:24:28 -04:00
bbedward
6140c398f0 Remove useless rounding 2025-10-02 14:14:16 -04:00
bbedward
bd02923616 plugin readme update 2025-10-02 14:01:20 -04:00
bbedward
6021815fd3 Fix media player when on right edge 2025-10-02 13:49:53 -04:00
bbedward
8c4aba5479 Center add widget in cc 2025-10-02 13:45:52 -04:00
bbedward
2428b22171 Score usage data into app search 2025-10-02 13:42:05 -04:00
bbedward
a3d30211f6 Fix missing screen info 2025-10-02 12:57:43 -04:00
bbedward
730300d211 de-dupe the pills 2025-10-02 12:48:21 -04:00
bbedward
aaca31276b shift clock and date weights in vertical mode 2025-10-02 12:44:21 -04:00
bbedward
53fb927e36 niri: color and layout config generation 2025-10-02 12:34:17 -04:00
bbedward
fb5aa0313e cleanup debug logs, fix center section plugins 2025-10-02 12:24:45 -04:00
bbedward
9b41eecbf1 Fix reactivity, different settings structure, etc, etc. 2025-10-02 12:13:49 -04:00
bbedward
ae461b1caf Merge branch 'master' of github.com:bbedward/DankMaterialShell into wip/plugins 2025-10-02 00:22:32 -04:00
bbedward
57e36d6710 Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-10-02 00:13:43 -04:00
bbedward
a7c4f09c5b always blockLoading on fileview 2025-10-02 00:13:29 -04:00
bbedward
554ef16e49 moar 2025-10-01 23:37:03 -04:00
bbedward
082321f860 de-dupe env 2025-10-01 22:13:03 -04:00
bbedward
df4f7b8c9e Set XDG_SESSION_TYPE in greeter
Always wayland, which is probably fine for our use case. Fixes gnome
2025-10-01 22:10:29 -04:00
bbedward
3f1742f074 lock+greeter: show keyboard layout widget, spacing adjustments 2025-10-01 21:08:55 -04:00
bbedward
4560d5c2d5 Fix overview auto hide bar 2025-10-01 18:16:27 -04:00
bbedward
0ca12d275c Abstract away plugin dev a little more 2025-10-01 17:47:39 -04:00
bbedward
df9e834309 Some consistent styling for plugins 2025-10-01 14:04:17 -04:00
bbedward
ab1c0bb129 Merge branch 'master' of github.com:bbedward/DankMaterialShell into wip/plugins 2025-10-01 13:38:49 -04:00
bbedward
5070e4c950 Cleanup imports 2025-10-01 13:09:43 -04:00
bbedward
c13526ccad re-enable layer 2025-10-01 13:05:28 -04:00
bbedward
46e16a6c69 Greetd: Add a greeter 2025-10-01 13:04:48 -04:00
Bruno Cesar Rocha
53983933dc feat: Plugin System (#276)
* feat: Plugin System

* fix: merge conflicts
2025-10-01 11:28:10 -04:00
purian23
09f3ca39a1 feat: Top/Left/Right/Bottom Dock positioning 2025-09-30 21:56:58 -04:00
bbedward
42b4c91f35 update readme 2025-09-30 18:10:57 -04:00
bbedward
37120776be configurable animation speeds 2025-09-30 17:30:03 -04:00
bbedward
d67bcb66c9 Keep SpotlightModal always loaded.
Tbh, probably minimal impact on memory - makes app launching snappier.
2025-09-30 17:07:57 -04:00
bbedward
acf6e72f35 emacs/vim style navigation everywhere 2025-09-30 16:54:50 -04:00
Alex Birdsall
0964b271f5 Add emacs-/readline- and vim-style list navigation to app launcher (#278)
* Add emacs- and vim-style nav keys to appLauncher

* Minor typo fix in contributing docs
2025-09-30 16:46:37 -04:00
bbedward
1f7998fc44 Disable scale from DankModal 2025-09-30 16:45:30 -04:00
bbedward
7ac59f7bd0 Revert antialiasing addition 2025-09-30 16:43:04 -04:00
xdenotte
bf238640af fix(popouts): disable layer compositing and scale to keep text sharp at 1.25 (#277) 2025-09-30 16:42:39 -04:00
bbedward
6e638cadb7 slightly thinner workspace pills + more forgiving mousearea 2025-09-30 15:49:13 -04:00
bbedward
ab0759f441 antialiasing: true on rectangles 2025-09-30 13:28:07 -04:00
bbedward
123ec5c78a Flip text to native rendering 2025-09-30 13:17:09 -04:00
bbedward
d262c67832 Fix VPN tooltip 2025-09-30 13:06:21 -04:00
bbedward
fcb9a2838d Disable VPN icon rotation 2025-09-30 12:59:10 -04:00
bbedward
82d1672741 search field focus only 2025-09-30 12:36:58 -04:00
bbedward
657a8b6094 Per light-mode/dark-mode wallpaper option 2025-09-30 12:29:08 -04:00
bbedward
165f7e0720 fix exit anim 2025-09-30 11:05:15 -04:00
bbedward
91aba84c29 Tie modal loader to shouldBeVisible 2025-09-30 11:01:51 -04:00
bbedward
02c4ac1bc7 fix bar radius 2025-09-30 10:56:18 -04:00
bbedward
c529959027 Tie volume OSD to dbus event 2025-09-30 10:11:35 -04:00
bbedward
e875d1a5d7 meta: Vertical Bar, Notification Popup Position Options, ++
- CC Color picker widget
- Tooltips in more places
- Attempt to improve niri screen transitiosn
2025-09-30 09:51:18 -04:00
bbedward
d280505b9f Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-09-29 16:50:24 -04:00
bbedward
fdd9d0000b Dont do dumb mono font filtering 2025-09-29 16:50:13 -04:00
purian23
74c793eedf fix: Notepad refactor w/Dual displays 2025-09-29 14:38:58 -04:00
purian23
bd8976c620 feat: Docks refactor - Top/Bottom options 2025-09-28 23:55:59 -04:00
purian23
a3e2563f9b Smoother notepad closure animation 2025-09-28 22:41:15 -04:00
bbedward
a310e3d8fa Fix contrast of monochrome theme 2025-09-28 13:26:18 -04:00
purian23
ae9da35af1 fix: Dank Bar per/Display options 2025-09-27 21:52:06 -04:00
bbedward
b45837ff5c Tab and backtab keyboard navigation on supported content 2025-09-27 09:08:41 -04:00
Parthiv Seetharaman
3dd9ab8a29 add option to always show dock in overview (#257) 2025-09-27 08:55:21 -04:00
bbedward
5ebd2f6b8e Smaller top bar auto hide mask 2025-09-27 08:53:28 -04:00
purian23
977043ac92 feat: Long Live the DankBar > Top/Bottom positioning 2025-09-26 23:45:19 -04:00
bbedward
4c6182b79c Allow 0 spacing 2025-09-26 13:49:00 -04:00
bbedward
01125e092c Don't allow popouts to go off screen 2025-09-26 13:47:40 -04:00
bbedward
99ef447a52 Restore bluetooth audio codec functionality 2025-09-26 12:37:10 -04:00
purian23
4722ff9b97 Merge pull request #255 from sezaru/fix_nix_documentation
doc: Fixes DMS input URL for nix flake
2025-09-26 12:21:26 -04:00
Eduardo B. A.
23a19df79a doc: Fixes DMS input url for nix flake 2025-09-26 13:10:10 -03:00
Eduardo B. A.
97b86c6faa Optimize Nix Flake for Niri / Hyprland (#242)
* feat: Make niri stuff optional

* doc: Add some documentation about the flake

* Simplify modules.

* Overlay pkgs and simplify modules.

* Fix niri config levels.

* Fix documentation.

* Just pass the packages to the module

---------

Co-authored-by: Eduardo Barreto Alexandre <blibs@blobs.com>
Co-authored-by: Luis González <5774777+luis-agm@users.noreply.github.com>
2025-09-26 10:44:41 -04:00
bbedward
e6296f20b9 default opaque 2025-09-26 00:25:15 -04:00
bbedward
0ee9dcc255 Revise catpuccin palettes 2025-09-26 00:16:34 -04:00
bbedward
c7b4e2c49d wrap shaders in a loader 2025-09-25 23:33:57 -04:00
bbedward
066d1847e4 cc: fixes to edit mode 2025-09-25 21:37:32 -04:00
purian23
3155bf24c1 Revert "refactor: Add Drag & Drop to Control Center"
This reverts commit a207ed74ff.
2025-09-25 20:59:22 -04:00
purian23
92bb5b90aa Merge branch 'master' of github.com:AvengeMedia/DankMaterialShell 2025-09-25 19:32:42 -04:00
purian23
a207ed74ff refactor: Add Drag & Drop to Control Center 2025-09-25 19:29:36 -04:00
bbedward
9d224113c4 use a matugen template for dms-colors.json 2025-09-25 18:27:25 -04:00
bbedward
934f4b2210 ControlCenter styling consistency fixes 2025-09-25 18:19:07 -04:00
bbedward
a6a41d4de1 Re-work theme approach to watch a file from matugen-worker 2025-09-25 16:49:57 -04:00
bbedward
185ee20f2d Fix idle monitor creation 2025-09-25 16:30:46 -04:00
bbedward
778b960130 Fix hibernate manual option 2025-09-25 16:11:18 -04:00
bbedward
184938d10a Merge branch 'master' of github.com:bbedward/DankMaterialShell 2025-09-25 15:59:21 -04:00
bbedward
25565af5f9 Some styling consistencies 2025-09-25 15:57:56 -04:00
purian23
0bd9a4f860 fix Control Center IPC via Escape key 2025-09-25 14:52:21 -04:00
purian23
fe64dd1dea Add IPC for Control Center 2025-09-25 14:23:45 -04:00
bbedward
a4a59fd586 Theme consistency overhaul:
- Add surface shift option, start from surface by default (previously
  was surfaceContainer). Old colors can be attained by changing it back
to "container" in theme colors
- Remove borders on surface elements, mostly
- Fix popup distances
- Use surfaceContainer/sch by default on widgets
2025-09-25 12:46:37 -04:00
349 changed files with 52982 additions and 14491 deletions

View File

@@ -26,6 +26,14 @@ assignees: ""
- [ ] Hyprland - [ ] Hyprland
- [ ] Other (specify) - [ ] Other (specify)
## Distribution
<!-- Arch, Fedora, Debian, etc. -->
## dms version
<!-- Output of dms version command -->
## Description ## Description
<!-- Brief description of the issue --> <!-- Brief description of the issue -->
@@ -45,6 +53,14 @@ assignees: ""
## Error Messages/Logs ## Error Messages/Logs
<!-- Please include any error messages, stack traces, or relevant logs --> <!-- Please include any error messages, stack traces, or relevant logs -->
<!-- you can get a log file with the following steps:
dms kill
mkdir ~/dms_logs
nohup dms run > ~/dms_logs/dms-$(date +%s).txt 2>&1 &
Then trigger your issue, and share the contents of ~/dms_logs/dms-<timestamp>.txt
-->
``` ```
Paste error messages or logs here Paste error messages or logs here

View File

@@ -12,6 +12,14 @@ assignees: ""
- [ ] Hyprland - [ ] Hyprland
- [ ] other - [ ] other
## Distribution
<!-- Arch, Fedora, Debian, etc. -->
## dms version
<!-- Output of dms version command -->
## Description ## Description
<!-- Brief description of the support needed --> <!-- Brief description of the support needed -->

272
.github/workflows/copr-release.yml vendored Normal file
View File

@@ -0,0 +1,272 @@
name: DMS Copr Stable Release
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
required: false
default: ''
jobs:
build-and-upload:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Determine version
id: version
run: |
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
echo "Using manual version: $VERSION"
elif [ "${{ github.event_name }}" = "release" ]; then
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
echo "Using release version: $VERSION"
else
# Fallback to latest release
VERSION=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | jq -r '.tag_name' | sed 's/^v//')
echo "Using latest release version: $VERSION"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "✅ Building DMS stable version: $VERSION"
- name: Setup build environment
run: |
sudo apt-get update
sudo apt-get install -y rpm wget curl jq gzip
mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
echo "✅ RPM build environment ready"
- name: Download release assets
run: |
VERSION="${{ steps.version.outputs.version }}"
cd ~/rpmbuild/SOURCES
echo "📦 Downloading DMS release assets for v${VERSION}..."
# Download DMS QML source
wget "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-qml.tar.gz" || {
echo "❌ Failed to download dms-qml.tar.gz for v${VERSION}"
exit 1
}
# Download dms-cli (always use latest)
echo "📦 Downloading latest dms-cli..."
wget "https://github.com/AvengeMedia/danklinux/releases/latest/download/dms-distropkg-amd64.gz" || {
echo "❌ Failed to download dms-cli"
exit 1
}
# Download dgop (always use latest)
echo "📦 Downloading latest dgop..."
wget "https://github.com/AvengeMedia/dgop/releases/latest/download/dgop-linux-amd64.gz" || {
echo "❌ Failed to download dgop"
exit 1
}
echo "✅ All sources downloaded"
ls -lh
- name: Generate stable spec file
run: |
VERSION="${{ steps.version.outputs.version }}"
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
# Spec for DMS stable releases - Generated by GitHub Actions
%global debug_package %{nil}
%global version VERSION_PLACEHOLDER
%global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors
Name: dms
Version: %{version}
Release: 1%{?dist}
Summary: %{pkg_summary}
License: GPL-3.0-only
URL: https://github.com/AvengeMedia/DankMaterialShell
Source0: dms-qml.tar.gz
Source1: dms-distropkg-amd64.gz
Source2: dgop-linux-amd64.gz
BuildRequires: gzip
Requires: (quickshell or quickshell-git)
Requires: dms-cli
Requires: dgop
Requires: fira-code-fonts
Requires: material-symbols-fonts
Requires: rsms-inter-fonts
Recommends: brightnessctl
Recommends: cava
Recommends: cliphist
Recommends: hyprpicker
Recommends: matugen
Recommends: wl-clipboard
Recommends: gammastep
Recommends: NetworkManager
Recommends: qt6-qtmultimedia
Suggests: qt6ct
%description
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
and optimized for the niri and hyprland compositors. Features notifications,
app launcher, wallpaper customization, and fully customizable with plugins.
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
process monitoring, notification center, clipboard history, dock, control center,
lock screen, and comprehensive plugin system.
%package -n dms-cli
Summary: DankMaterialShell CLI tool
License: GPL-3.0-only
URL: https://github.com/AvengeMedia/danklinux
%description -n dms-cli
Command-line interface for DankMaterialShell configuration and management.
Provides native DBus bindings, NetworkManager integration, and system utilities.
%package -n dgop
Summary: Stateless CPU/GPU monitor for DankMaterialShell
License: MIT
URL: https://github.com/AvengeMedia/dgop
Provides: dgop
%description -n dgop
DGOP is a stateless system monitoring tool that provides CPU, GPU, memory, and
network statistics. Designed for integration with DankMaterialShell but can be
used standalone. This package always includes the latest stable dgop release.
%prep
%setup -q -c -n dms-qml
gunzip -c %{SOURCE1} > %{_builddir}/dms-cli
chmod +x %{_builddir}/dms-cli
gunzip -c %{SOURCE2} > %{_builddir}/dgop
chmod +x %{_builddir}/dgop
%build
%install
install -Dm755 %{_builddir}/dms-cli %{buildroot}%{_bindir}/dms
install -Dm755 %{_builddir}/dgop %{buildroot}%{_bindir}/dgop
install -dm755 %{buildroot}%{_sysconfdir}/xdg/quickshell/dms
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.git*
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/.github
rm -f %{buildroot}%{_sysconfdir}/xdg/quickshell/dms/*.spec
%files
%license LICENSE
%doc README.md CONTRIBUTING.md
%{_sysconfdir}/xdg/quickshell/dms/
%files -n dms-cli
%{_bindir}/dms
%files -n dgop
%{_bindir}/dgop
%changelog
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
- Stable release VERSION_PLACEHOLDER
- Built from GitHub release
- Includes latest dms-cli and dgop binaries
SPECEOF
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
echo "✅ Spec file generated for v${VERSION}"
echo ""
echo "=== Spec file preview ==="
head -40 ~/rpmbuild/SPECS/dms.spec
- name: Build SRPM
id: build
run: |
cd ~/rpmbuild/SPECS
echo "🔨 Building SRPM..."
rpmbuild -bs dms.spec
SRPM=$(ls ~/rpmbuild/SRPMS/*.src.rpm | tail -n 1)
SRPM_NAME=$(basename "$SRPM")
echo "srpm_path=$SRPM" >> $GITHUB_OUTPUT
echo "srpm_name=$SRPM_NAME" >> $GITHUB_OUTPUT
echo "✅ SRPM built: $SRPM_NAME"
echo ""
echo "=== SRPM Info ==="
rpm -qpi "$SRPM"
- name: Upload SRPM artifact
uses: actions/upload-artifact@v4
with:
name: dms-stable-srpm-${{ steps.version.outputs.version }}
path: ${{ steps.build.outputs.srpm_path }}
retention-days: 90
- name: Install Copr CLI
run: |
sudo apt-get install -y python3-pip
pip3 install copr-cli
mkdir -p ~/.config
cat > ~/.config/copr << EOF
[copr-cli]
login = ${{ secrets.COPR_LOGIN }}
username = avengemedia
token = ${{ secrets.COPR_TOKEN }}
copr_url = https://copr.fedorainfracloud.org
EOF
chmod 600 ~/.config/copr
echo "✅ Copr CLI configured"
- name: Upload to Copr
run: |
SRPM="${{ steps.build.outputs.srpm_path }}"
VERSION="${{ steps.version.outputs.version }}"
echo "🚀 Uploading SRPM to avengemedia/dms..."
echo " SRPM: $(basename $SRPM)"
echo " Version: $VERSION"
BUILD_OUTPUT=$(copr-cli build avengemedia/dms "$SRPM" --nowait 2>&1)
echo "$BUILD_OUTPUT"
BUILD_ID=$(echo "$BUILD_OUTPUT" | grep -oP 'Build was added to.*\K[0-9]+' || echo "unknown")
if [ "$BUILD_ID" != "unknown" ]; then
echo "✅ Build submitted successfully!"
echo "🔗 https://copr.fedorainfracloud.org/coprs/avengemedia/dms/build/$BUILD_ID/"
else
echo "⚠️ Could not extract build ID, but upload may have succeeded"
fi
- name: Build summary
if: always()
run: |
echo "### 🎉 DMS Stable Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "- **SRPM:** ${{ steps.build.outputs.srpm_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Stable release has been built and uploaded to Copr!" >> $GITHUB_STEP_SUMMARY

115
.github/workflows/poeditor-export.yml vendored Normal file
View File

@@ -0,0 +1,115 @@
name: POEditor Diff & Sync
on:
push:
branches: [ master ]
workflow_dispatch: {}
concurrency:
group: poeditor-sync
cancel-in-progress: false
jobs:
diff-and-trigger:
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- name: ja
po_lang: "ja"
repo_file: "translations/poexports/ja.json"
webhook_secret: "POEDITOR_WEBHOOK_JA"
- name: zh_hans
po_lang: "zh-Hans"
repo_file: "translations/poexports/zh_CN.json"
webhook_secret: "POEDITOR_WEBHOOK_ZH_HANS"
- name: pt
po_lang: "pt-br"
repo_file: "translations/poexports/pt.json"
webhook_secret: "POEDITOR_WEBHOOK_PT"
steps:
- uses: actions/checkout@v4
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Export from POEditor (key/value JSON) and compare
id: diffcheck
env:
API_TOKEN: ${{ secrets.POEDITOR_API_TOKEN }}
PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }}
PO_LANG: ${{ matrix.po_lang }}
REPO_FILE: ${{ matrix.repo_file }}
run: |
set -euo pipefail
# 1) Request an export URL for key/value json
echo "::group::POEditor export request"
RESP=$(curl -sS -X POST https://api.poeditor.com/v2/projects/export \
-d api_token="$API_TOKEN" \
-d id="$PROJECT_ID" \
-d language="$PO_LANG" \
-d type="key_value_json")
echo "$RESP" | jq -r '.'
STATUS=$(echo "$RESP" | jq -r '.response.status')
if [[ "$STATUS" != "success" ]]; then
echo "POEditor export request failed: $RESP" >&2
exit 1
fi
URL=$(echo "$RESP" | jq -r '.result.url')
if [[ -z "$URL" || "$URL" == "null" ]]; then
echo "No export URL returned from POEditor." >&2
exit 1
fi
echo "::endgroup::"
# 2) Download exported content
curl -sS -L "$URL" -o /tmp/po_export.json
# 3) Normalize JSON (sorted keys) for stable diff
jq -S . /tmp/po_export.json > /tmp/po_export.norm.json
# 4) Normalize repo file (or empty {}) and diff
if [[ -f "$REPO_FILE" ]]; then
jq -S . "$REPO_FILE" > /tmp/repo.norm.json || echo "{}" > /tmp/repo.norm.json
else
echo "{}" > /tmp/repo.norm.json
fi
# 5) Set output changed=true|false
if diff -q /tmp/po_export.norm.json /tmp/repo.norm.json >/dev/null; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No changes for $PO_LANG"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Detected changes for $PO_LANG"
fi
- name: Trigger POEditor webhook for this language, if changed
if: steps.diffcheck.outputs.changed == 'true'
env:
WEBHOOK_URL: ${{ secrets[matrix.webhook_secret] }}
run: |
set -euo pipefail
if [[ -z "${WEBHOOK_URL:-}" ]]; then
echo "Missing webhook secret for this language: ${{ matrix.webhook_secret }}" >&2
exit 1
fi
echo "Calling POEditor export webhook for ${{ matrix.name }}"
for attempt in 1 2 3; do
code=$(curl -sS -o /tmp/resp.txt -w '%{http_code}' -X POST "$WEBHOOK_URL" || true)
if [[ "$code" == "200" || "$code" == "204" ]]; then
echo "Webhook OK ($code)"
exit 0
fi
echo "Attempt $attempt failed ($code) → $(cat /tmp/resp.txt)"
sleep $((attempt*3))
done
echo "Webhook failed after retries." >&2
exit 1

View File

@@ -1,59 +1,219 @@
name: Create Release # Release from a dispatch event from the danklinux repo
name: Create Release from DMS
on: on:
push: repository_dispatch:
tags: types: [dms_release]
- 'v*'
permissions:
contents: write
concurrency: concurrency:
group: release-${{ github.ref_name }} group: release-${{ github.event.client_payload.tag }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
create_release: create_release_from_dms:
name: 📦 Create GitHub Release
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
env:
TAG: ${{ github.event.client_payload.tag }}
DMS_REPO: ${{ github.event.client_payload.dms_repo }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 # Fetch full history for changelog generation fetch-depth: 0
- name: Ensure VERSION and tag
run: |
set -euxo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
echo "${TAG}" > VERSION
git add -A VERSION
if ! git diff --cached --quiet; then
git commit -m "Update VERSION to ${TAG} (from DMS)"
fi
git tag -f "${TAG}"
git push origin HEAD
git push -f origin "${TAG}"
# Generate changelog
- name: Generate Changelog - name: Generate Changelog
id: changelog id: changelog
run: | run: |
# Get the previous tag set -e
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then if [ -z "$PREVIOUS_TAG" ]; then
echo "No previous tag found, using all commits"
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50) CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" | head -50)
else else
echo "Generating changelog from $PREVIOUS_TAG to HEAD" CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${TAG}")
CHANGELOG=$(git log --oneline --pretty=format:"- %s (%h)" $PREVIOUS_TAG..HEAD)
fi fi
# Create the changelog with proper formatting cat > RELEASE_BODY.md << 'EOF'
cat > CHANGELOG.md << EOF ## Assets
### Complete Packages
- **`dms-full-amd64.tar.gz`** - Complete package for x86_64 systems (CLI binaries + QML source + installation guide)
- **`dms-full-arm64.tar.gz`** - Complete package for ARM64 systems (CLI binaries + QML source + installation guide)
### Individual Components
- **`dms-cli-amd64.gz`** - DMS CLI binary for x86_64 systems
- **`dms-cli-arm64.gz`** - DMS CLI binary for ARM64 systems
- **`dms-distropkg-amd64.gz`** - DMS CLI binary built with distro_package tag for AMD64 systems
- **`dms-distropkg-arm64.gz`** - DMS CLI binary built with distro_package tag for ARM64 systems
- **`dms-qml.tar.gz`** - QML source code only
### Checksums
- **`*.sha256`** - SHA256 checksums for verifying download integrity
**Installation:** Extract the `dms-full-*.tar.gz` package for your architecture and follow the `INSTALL.md` instructions inside.
---
EOF
cat >> RELEASE_BODY.md << EOF
## What's Changed ## What's Changed
$CHANGELOG $CHANGELOG
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${{ github.ref_name }} **Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${TAG}
EOF EOF
# Set output for use in release step
echo "changelog<<EOF" >> $GITHUB_OUTPUT echo "changelog<<EOF" >> $GITHUB_OUTPUT
cat CHANGELOG.md >> $GITHUB_OUTPUT cat RELEASE_BODY.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
# Create GitHub Release - name: Create/Update DankMaterialShell Release
- name: Create GitHub Release uses: softprops/action-gh-release@v2
uses: comnoco/create-release-action@v2.0.5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ github.ref_name }} tag_name: ${{ env.TAG }}
release_name: Release ${{ github.ref_name }} name: Release ${{ env.TAG }}
body: ${{ steps.changelog.outputs.changelog }} body: ${{ steps.changelog.outputs.changelog }}
draft: false draft: false
prerelease: ${{ contains(github.ref_name, '-') }} prerelease: ${{ contains(env.TAG, '-') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download and prepare release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euxo pipefail
mkdir -p _release_assets
# Download DMS CLI binaries from the danklinux repo
gh release download "${TAG}" -R "${DMS_REPO}" --dir ./_dms_assets
# Rename CLI binaries to dms-cli-* format and copy distropkg binaries
for file in _dms_assets/dms-*.gz*; do
if [ -f "$file" ]; then
basename=$(basename "$file")
if [[ "$basename" == dms-distropkg-* ]]; then
cp "$file" "_release_assets/$basename"
else
newname=$(echo "$basename" | sed 's/^dms-/dms-cli-/')
cp "$file" "_release_assets/$newname"
fi
fi
done
# Create QML source package (exclude .git, .github, build artifacts)
tar --exclude='.git' \
--exclude='.github' \
--exclude='_dms_assets' \
--exclude='_release_assets' \
--exclude='*.tar.gz' \
-czf _release_assets/dms-qml.tar.gz .
# Generate checksum for QML package
(cd _release_assets && sha256sum dms-qml.tar.gz > dms-qml.tar.gz.sha256)
# Create full packages for each architecture
for arch in amd64 arm64; do
mkdir -p _temp_full/dms
mkdir -p _temp_full/bin
# Extract QML source to temp directory
tar -xzf _release_assets/dms-qml.tar.gz -C _temp_full/dms
# Copy CLI binary if it exists
if [ -f "_dms_assets/dms-${arch}.gz" ]; then
gunzip -c "_dms_assets/dms-${arch}.gz" > _temp_full/bin/dms
chmod +x _temp_full/bin/dms
fi
# Copy distropkg binary if it exists
if [ -f "_dms_assets/dms-distropkg-${arch}.gz" ]; then
gunzip -c "_dms_assets/dms-distropkg-${arch}.gz" > _temp_full/bin/dms-distropkg
chmod +x _temp_full/bin/dms-distropkg
fi
# Create INSTALL.md
cat > _temp_full/INSTALL.md << 'EOF'
# DankMaterialShell Installation
## Requirements
- Wayland compositor (niri or Hyprland recommended)
- Quickshell framework
- Qt6
## Installation Steps
1. **Install quickshell assets:**
```bash
mkdir -p ~/.config/quickshell
cp -r dms ~/.config/quickshell/
```
2. **Install the DMS CLI binaries:**
```bash
sudo install -m 755 bin/dms /usr/local/bin/dms
# or install to a local directory:
mkdir -p ~/.local/bin
install -m 755 bin/dms ~/.local/bin/dms
```
3. **Start the shell:**
```bash
dms run
# or directly with quickshell (will lack some dbus integrations & plugin management):
quickshell -p ~/.config/quickshell/dms
```
## Configuration
- Settings are stored in `~/.config/DankMaterialShell/settings.json`
- Plugins go in `~/.config/DankMaterialShell/plugins/`
- See the documentation in the `dms/` directory for more details
## Troubleshooting
- Run with verbose output: `quickshell -v -p ~/.config/quickshell/dms`
- Check logs in `~/.local/state/DankMaterialShell/`
- Ensure all dependencies are installed
EOF
# Create the full package
(cd _temp_full && tar -czf "../_release_assets/dms-full-${arch}.tar.gz" .)
# Generate checksum
(cd _release_assets && sha256sum "dms-full-${arch}.tar.gz" > "dms-full-${arch}.tar.gz.sha256")
# Cleanup
rm -rf _temp_full
done
- name: Attach all assets to release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG }}
files: _release_assets/**
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

227
CLAUDE.md
View File

@@ -63,6 +63,9 @@ quickshell -p shell.qml
# Or use the shorthand # Or use the shorthand
qs -p . qs -p .
# Run with verbose output for debugging
qs -v -p shell.qml
# Code formatting and linting # Code formatting and linting
qmlfmt -t 4 -i 4 -b 250 -w /path/to/file.qml # Format a QML file (requires qmlfmt, do not use qmlformat) qmlfmt -t 4 -i 4 -b 250 -w /path/to/file.qml # Format a QML file (requires qmlfmt, do not use qmlformat)
qmllint **/*.qml # Lint all QML files for syntax errors qmllint **/*.qml # Lint all QML files for syntax errors
@@ -89,6 +92,7 @@ shell.qml # Main entry point (minimal orchestration)
│ ├── DisplayService.qml │ ├── DisplayService.qml
│ ├── NotificationService.qml │ ├── NotificationService.qml
│ ├── WeatherService.qml │ ├── WeatherService.qml
│ ├── PluginService.qml
│ └── [14 more services] │ └── [14 more services]
├── Modules/ # UI components (93 files) ├── Modules/ # UI components (93 files)
│ ├── TopBar/ # Panel components (13 files) │ ├── TopBar/ # Panel components (13 files)
@@ -104,15 +108,21 @@ shell.qml # Main entry point (minimal orchestration)
│ ├── SettingsModal.qml │ ├── SettingsModal.qml
│ ├── ClipboardHistoryModal.qml │ ├── ClipboardHistoryModal.qml
│ ├── ProcessListModal.qml │ ├── ProcessListModal.qml
│ ├── PluginSettingsModal.qml
│ └── [7 more modals] │ └── [7 more modals]
── Widgets/ # Reusable UI controls (19 files) ── Widgets/ # Reusable UI controls (19 files)
├── DankIcon.qml ├── DankIcon.qml
├── DankSlider.qml ├── DankSlider.qml
├── DankToggle.qml ├── DankToggle.qml
├── DankTabBar.qml ├── DankTabBar.qml
├── DankGridView.qml ├── DankGridView.qml
├── DankListView.qml ├── DankListView.qml
└── [13 more widgets] └── [13 more widgets]
└── plugins/ # External plugins directory ($CONFIGPATH/DankMaterialShell/plugins/)
└── PluginName/ # Example Plugin structure
├── plugin.json # Plugin manifest
├── PluginNameWidget.qml # Widget component
└── PluginNameSettings.qml # Settings UI
``` ```
### Component Organization ### Component Organization
@@ -163,6 +173,12 @@ shell.qml # Main entry point (minimal orchestration)
- **DankLocationSearch**: Location picker with search - **DankLocationSearch**: Location picker with search
- **SystemLogo**: Animated system branding component - **SystemLogo**: Animated system branding component
7. **Plugins/** - External plugin system (`$CONFIGPATH/DankMaterialShell/plugins/`)
- **PluginService**: Discovers, loads, and manages plugin lifecycle
- **Dynamic Loading**: Plugins loaded at runtime from external directory
- **DankBar Integration**: Plugin widgets rendered alongside built-in widgets
- **Settings System**: Per-plugin settings with persistence
### Key Architectural Patterns ### Key Architectural Patterns
1. **Singleton Services Pattern**: 1. **Singleton Services Pattern**:
@@ -430,6 +446,200 @@ When modifying the shell:
} }
``` ```
### Creating Plugins
Plugins are external, dynamically-loaded components that extend DankMaterialShell functionality. Plugins are stored in `~/.config/DankMaterialShell/plugins/` and have their settings isolated from core DMS settings.
**Plugin Types:**
- **Widget plugins** (`"type": "widget"` or omit type field): Display UI components in DankBar
- **Daemon plugins** (`"type": "daemon"`): Run invisibly in the background without UI
#### Widget Plugins
1. **Create plugin directory**:
```bash
mkdir -p ~/.config/DankMaterialShell/plugins/YourPlugin
```
2. **Create manifest** (`plugin.json`):
```json
{
"id": "yourPlugin",
"name": "Your Plugin",
"description": "Widget description",
"version": "1.0.0",
"author": "Your Name",
"icon": "extension",
"type": "widget",
"component": "./YourWidget.qml",
"settings": "./YourSettings.qml",
"permissions": ["settings_read", "settings_write"]
}
```
3. **Create widget component** (`YourWidget.qml`):
```qml
import QtQuick
import qs.Services
Rectangle {
id: root
property bool compactMode: false
property string section: "center"
property real widgetHeight: 30
property var pluginService: null
width: content.implicitWidth + 16
height: widgetHeight
radius: 8
color: "#20FFFFFF"
Component.onCompleted: {
if (pluginService) {
var data = pluginService.loadPluginData("yourPlugin", "key", defaultValue)
}
}
}
```
4. **Create settings component** (`YourSettings.qml`):
```qml
import QtQuick
import QtQuick.Controls
FocusScope {
id: root
property var pluginService: null
implicitHeight: settingsColumn.implicitHeight
height: implicitHeight
Column {
id: settingsColumn
anchors.fill: parent
anchors.margins: 16
spacing: 12
Text {
text: "Your Plugin Settings"
font.pixelSize: 18
font.weight: Font.Bold
}
// Your settings UI here
}
function saveSettings(key, value) {
if (pluginService) {
pluginService.savePluginData("yourPlugin", key, value)
}
}
function loadSettings(key, defaultValue) {
if (pluginService) {
return pluginService.loadPluginData("yourPlugin", key, defaultValue)
}
return defaultValue
}
}
```
5. **Enable plugin**:
- Open Settings → Plugins
- Click "Scan for Plugins"
- Toggle plugin to enable
- Add plugin ID to DankBar widget list
#### Daemon Plugins
Daemon plugins run invisibly in the background without any UI components. They're useful for monitoring system events, background tasks, or data synchronization.
1. **Create plugin directory**:
```bash
mkdir -p ~/.config/DankMaterialShell/plugins/YourDaemon
```
2. **Create manifest** (`plugin.json`):
```json
{
"id": "yourDaemon",
"name": "Your Daemon",
"description": "Background daemon description",
"version": "1.0.0",
"author": "Your Name",
"icon": "settings_applications",
"type": "daemon",
"component": "./YourDaemon.qml",
"permissions": ["settings_read", "settings_write"]
}
```
3. **Create daemon component** (`YourDaemon.qml`):
```qml
import QtQuick
import qs.Common
import qs.Services
Item {
id: root
property var pluginService: null
Connections {
target: SessionData
function onWallpaperPathChanged() {
console.log("Wallpaper changed:", SessionData.wallpaperPath)
if (pluginService) {
pluginService.savePluginData("yourDaemon", "lastEvent", Date.now())
}
}
}
Component.onCompleted: {
console.log("Daemon started")
}
}
```
4. **Enable daemon**:
- Open Settings → Plugins
- Click "Scan for Plugins"
- Toggle daemon to enable
- Daemon runs automatically in background
**Example**: See `PLUGINS/WallpaperWatcherDaemon/` for a complete daemon plugin that monitors wallpaper changes
**Plugin Directory Structure:**
```
~/.config/DankMaterialShell/
├── settings.json # Core DMS settings + plugin settings
│ └── pluginSettings: {
│ └── yourPlugin: {
│ ├── enabled: true,
│ └── customData: {...}
│ }
│ }
└── plugins/ # Plugin files directory
└── YourPlugin/ # Plugin directory (matches manifest ID)
├── plugin.json # Plugin manifest
├── YourWidget.qml # Widget component
└── YourSettings.qml # Settings UI (optional)
```
**Key Plugin APIs:**
- `pluginService.loadPluginData(pluginId, key, default)` - Load persistent data
- `pluginService.savePluginData(pluginId, key, value)` - Save persistent data
- `PluginService.enablePlugin(pluginId)` - Load plugin
- `PluginService.disablePlugin(pluginId)` - Unload plugin
**Important Notes:**
- Plugin settings are automatically injected by the PluginService via `item.pluginService = PluginService`
- Settings are stored in the main settings.json but namespaced under `pluginSettings.{pluginId}`
- Plugin directories must match the plugin ID in the manifest
- Use the injected `pluginService` property in both widget and settings components
### Debugging Common Issues ### Debugging Common Issues
1. **Import errors**: Check import paths 1. **Import errors**: Check import paths
@@ -454,6 +664,7 @@ When modifying the shell:
- **Function Discovery**: Use grep/search tools to find existing utility functions before implementing new ones - **Function Discovery**: Use grep/search tools to find existing utility functions before implementing new ones
- **Modern QML Patterns**: Leverage new widgets like DankTextField, DankDropdown, CachingImage - **Modern QML Patterns**: Leverage new widgets like DankTextField, DankDropdown, CachingImage
- **Structured Organization**: Follow the established Services/Modules/Widgets/Modals separation - **Structured Organization**: Follow the established Services/Modules/Widgets/Modals separation
- **Plugin System**: For user extensions, create plugins instead of modifying core modules - see docs/PLUGINS.md
### Common Widget Patterns ### Common Widget Patterns

View File

@@ -1,6 +1,6 @@
# Contributing # Contributing
Contributions are welcome and encourages. Contributions are welcome and encouraged.
## Formatting ## Formatting

View File

@@ -72,10 +72,6 @@ Singleton {
saveSettings() saveSettings()
} }
function getAppUsageRanking() {
return appUsageRanking
}
function getRankedApps() { function getRankedApps() {
var apps = [] var apps = []
for (var appId in appUsageRanking) { for (var appId in appUsageRanking) {

122
Common/CacheData.qml Normal file
View File

@@ -0,0 +1,122 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
readonly property int cacheConfigVersion: 1
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericCacheLocation)
readonly property string _stateDir: Paths.strip(_stateUrl)
property bool _loading: false
property string wallpaperLastPath: ""
property string profileLastPath: ""
Component.onCompleted: {
if (!isGreeterMode) {
loadCache()
}
}
function loadCache() {
_loading = true
parseCache(cacheFile.text())
_loading = false
}
function parseCache(content) {
_loading = true
try {
if (content && content.trim()) {
const cache = JSON.parse(content)
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : ""
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : ""
if (cache.configVersion === undefined) {
migrateFromUndefinedToV1(cache)
cleanupUnusedKeys()
saveCache()
}
}
} catch (e) {
console.warn("CacheData: Failed to parse cache:", e.message)
} finally {
_loading = false
}
}
function saveCache() {
if (_loading)
return
cacheFile.setText(JSON.stringify({
"wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath,
"configVersion": cacheConfigVersion
}, null, 2))
}
function migrateFromUndefinedToV1(cache) {
console.log("CacheData: Migrating configuration from undefined to version 1")
}
function cleanupUnusedKeys() {
const validKeys = [
"wallpaperLastPath",
"profileLastPath",
"configVersion"
]
try {
const content = cacheFile.text()
if (!content || !content.trim()) return
const cache = JSON.parse(content)
let needsSave = false
for (const key in cache) {
if (!validKeys.includes(key)) {
console.log("CacheData: Removing unused key:", key)
delete cache[key]
needsSave = true
}
}
if (needsSave) {
cacheFile.setText(JSON.stringify(cache, null, 2))
}
} catch (e) {
console.warn("CacheData: Failed to cleanup unused keys:", e.message)
}
}
FileView {
id: cacheFile
path: isGreeterMode ? "" : _stateDir + "/DankMaterialShell/cache.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: !isGreeterMode
onLoaded: {
if (!isGreeterMode) {
parseCache(cacheFile.text())
}
}
onLoadFailed: error => {
if (!isGreeterMode) {
console.log("CacheData: No cache file found, starting fresh")
}
}
}
}

62
Common/DankSocket.qml Normal file
View File

@@ -0,0 +1,62 @@
import QtQuick
import Quickshell.Io
Item {
id: root
property alias path: socket.path
property alias parser: socket.parser
property bool connected: false
property int reconnectBaseMs: 400
property int reconnectMaxMs: 15000
property int _reconnectAttempt: 0
signal connectionStateChanged()
onConnectedChanged: {
socket.connected = connected
}
Socket {
id: socket
onConnectionStateChanged: {
root.connectionStateChanged()
if (connected) {
root._reconnectAttempt = 0
return
}
if (root.connected) {
root._scheduleReconnect()
}
}
}
Timer {
id: reconnectTimer
interval: 0
repeat: false
onTriggered: {
socket.connected = false
Qt.callLater(() => socket.connected = true)
}
}
function send(data) {
const json = typeof data === "string" ? data : JSON.stringify(data)
const message = json.endsWith("\n") ? json : json + "\n"
socket.write(message)
socket.flush()
}
function _scheduleReconnect() {
const pow = Math.min(_reconnectAttempt, 10)
const base = Math.min(reconnectBaseMs * Math.pow(2, pow), reconnectMaxMs)
const jitter = Math.floor(Math.random() * Math.floor(base / 4))
reconnectTimer.interval = base + jitter
reconnectTimer.restart()
_reconnectAttempt++
}
}

53
Common/Facts.qml Normal file
View File

@@ -0,0 +1,53 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
readonly property var facts: [
"A photon takes 100,000 to 200,000 years bouncing through the Sun's dense core, then races to Earth in just 8 minutes 20 seconds.",
"A teaspoon of neutron star matter would weigh a billion metric tons here on Earth.",
"Right now, 100 trillion solar neutrinos are passing through your body every second.",
"The Sun converts 4 million metric tons of matter into pure energy every second—enough to power Earth for 500,000 years.",
"The universe still glows with leftover heat from the Big Bang—just 2.7 degrees above absolute zero.",
"There's a nebula out there that's actually colder than empty space itself.",
"We've detected black holes crashing together by measuring spacetime stretch by less than 1/10,000th the width of a proton.",
"Fast radio bursts can release more energy in 5 milliseconds than our Sun produces in 3 days.",
"Our galaxy might be crawling with billions of rogue planets drifting alone in the dark.",
"Distant galaxies can move away from us faster than light because space itself is stretching.",
"The edge of what we can see is 46.5 billion light-years away, even though the universe is only 13.8 billion years old.",
"The universe is mostly invisible: 5% regular matter, 27% dark matter, 68% dark energy.",
"A day on Venus lasts longer than its entire year around the Sun.",
"On Mercury, the time between sunrises is 176 Earth days long.",
"In about 4.5 billion years, our galaxy will smash into Andromeda.",
"Most of the gold in your jewelry was forged when neutron stars collided somewhere in space.",
"PSR J1748-2446ad, the fastest spinning star, rotates 716 times per second—its equator moves at 24% the speed of light.",
"Cosmic rays create particles that shouldn't make it to Earth's surface, but time dilation lets them sneak through.",
"Jupiter's magnetic field is so huge that if we could see it, it would look bigger than the Moon in our sky.",
"Interstellar space is so empty it's like a cube 32 kilometers wide containing just a single grain of sand.",
"Voyager 1 is 24 billion kilometers away but won't leave the Sun's gravitational influence for another 30,000 years.",
"Counting to a billion at one number per second would take over 31 years.",
"Space is so vast, even speeding at light-speed, you'd never return past the cosmic horizon.",
"Astronauts on the ISS age about 0.01 seconds less each year than people on Earth.",
"Sagittarius B2, a dust cloud near our galaxy's center, contains ethyl formate—the compound that gives raspberries their flavor and rum its smell.",
"Beyond 16 billion light-years, the cosmic event horizon marks where space expands too fast for light to ever reach us again.",
"Even at light-speed, you'd never catch up to most galaxies—space expands faster.",
"Only around 5% of galaxies are ever reachable—even at light-speed.",
"If the Sun vanished, we'd still orbit it for 8 minutes before drifting away.",
"If a planet 65 million light-years away looked at Earth now, it'd see dinosaurs.",
"Our oldest radio signals will reach the Milky Way's center in 26,000 years.",
"Every atom in your body heavier than hydrogen was forged in the nuclear furnace of a dying star.",
"The Moon moves 3.8 centimeters farther from Earth every year.",
"The universe creates 275 million new stars every single day.",
"Jupiter's Great Red Spot is a storm twice the size of Earth that has been raging for at least 350 years.",
"If you watched someone fall into a black hole, they'd appear frozen at the event horizon forever—time effectively stops from your perspective.",
"The Boötes Supervoid is a cosmic desert 1.8 billion light-years across with 60% fewer galaxies than it should have."
]
function getRandomFact() {
return facts[Math.floor(Math.random() * facts.length)]
}
}

113
Common/I18n.qml Normal file
View File

@@ -0,0 +1,113 @@
import QtQuick
import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
readonly property string _rawLocale: Qt.locale().name
readonly property string _lang: _rawLocale.split(/[_-]/)[0]
readonly property var _candidates: {
const fullUnderscore = _rawLocale;
const fullHyphen = _rawLocale.replace("_", "-");
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
}
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
property string currentLocale: "en"
property var translations: ({})
property bool translationsLoaded: false
property url _selectedPath: ""
FolderListModel {
id: dir
folder: root.translationsFolder
nameFilters: ["*.json"]
showDirs: false
showDotAndDotDot: false
onStatusChanged: if (status === FolderListModel.Ready) root._pickTranslation()
}
FileView {
id: translationLoader
path: root._selectedPath
onLoaded: {
try {
root.translations = JSON.parse(text())
root.translationsLoaded = true
console.log(`I18n: Loaded translations for '${root.currentLocale}' ` +
`(${Object.keys(root.translations).length} contexts)`)
} catch (e) {
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e,
"- falling back to English")
root._fallbackToEnglish()
}
}
onLoadFailed: (error) => {
console.warn(`I18n: Failed to load '${root.currentLocale}' (${error}), ` +
"falling back to English")
root._fallbackToEnglish()
}
}
function _pickTranslation() {
const present = new Set()
for (let i = 0; i < dir.count; i++) {
const name = dir.get(i, "fileName") // e.g. "zh_CN.json"
if (name && name.endsWith(".json")) {
present.add(name.slice(0, -5))
}
}
for (let i = 0; i < _candidates.length; i++) {
const cand = _candidates[i]
if (present.has(cand)) {
_useLocale(cand, dir.folder + "/" + cand + ".json")
return
}
}
_fallbackToEnglish()
}
function _useLocale(localeTag, fileUrl) {
currentLocale = localeTag
_selectedPath = fileUrl
translationsLoaded = false
translations = ({})
console.log(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
}
function _fallbackToEnglish() {
currentLocale = "en"
_selectedPath = ""
translationsLoaded = false
translations = ({})
console.warn("I18n: Falling back to built-in English strings")
}
function tr(term, context) {
if (!translationsLoaded || !translations) return term
const ctx = context || term
if (translations[ctx] && translations[ctx][term]) return translations[ctx][term]
for (const c in translations) {
if (translations[c] && translations[c][term]) return translations[c][term]
}
return term
}
function trContext(context, term) {
if (!translationsLoaded || !translations) return term
if (translations[context] && translations[context][term]) return translations[context][term]
return term
}
}

View File

@@ -38,6 +38,10 @@ Singleton {
return stringify(path).replace("file://", "") return stringify(path).replace("file://", "")
} }
function toFileUrl(path: string): string {
return path.startsWith("file://") ? path : "file://" + path
}
function mkdir(path: url): void { function mkdir(path: url): void {
Quickshell.execDetached(["mkdir", "-p", strip(path)]) Quickshell.execDetached(["mkdir", "-p", strip(path)])
} }

70
Common/Proc.qml Normal file
View File

@@ -0,0 +1,70 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property int defaultDebounceMs: 50
property var _procDebouncers: ({}) // id -> { timer, command, callback, waitMs }
function runCommand(id, command, callback, debounceMs) {
const wait = (typeof debounceMs === "number" && debounceMs >= 0) ? debounceMs : defaultDebounceMs
let procId = id ? id : Math.random()
if (!_procDebouncers[procId]) {
const t = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root)
t.triggered.connect(function() { _launchProc(procId) })
_procDebouncers[procId] = { timer: t, command: command, callback: callback, waitMs: wait }
} else {
_procDebouncers[procId].command = command
_procDebouncers[procId].callback = callback
_procDebouncers[procId].waitMs = wait
}
const entry = _procDebouncers[procId]
entry.timer.interval = entry.waitMs
entry.timer.restart()
}
function _launchProc(id) {
const entry = _procDebouncers[id]
if (!entry) return
const proc = Qt.createQmlObject('import Quickshell.Io; Process { running: false }', root)
const out = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc)
const err = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc)
proc.stdout = out
proc.stderr = err
proc.command = entry.command
let capturedOut = ""
let exitSeen = false
let exitCodeValue = -1
out.streamFinished.connect(function() {
capturedOut = out.text || ""
maybeComplete()
})
proc.exited.connect(function(code) {
exitSeen = true
exitCodeValue = code
maybeComplete()
})
function maybeComplete() {
if (!exitSeen) return
if (typeof entry.callback === "function") {
try { entry.callback(capturedOut, exitCodeValue) } catch (e) { console.warn("runCommand callback error:", e) }
}
try { proc.destroy() } catch (_) {}
}
proc.running = true
}
}

View File

@@ -9,24 +9,40 @@ import qs.Common
import qs.Services import qs.Services
Singleton { Singleton {
id: root id: root
readonly property int sessionConfigVersion: 1
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
property bool hasTriedDefaultSession: false
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
readonly property string _stateDir: Paths.strip(_stateUrl)
property bool isLightMode: false property bool isLightMode: false
property bool doNotDisturb: false
property string wallpaperPath: "" property string wallpaperPath: ""
property string wallpaperLastPath: ""
property string profileLastPath: ""
property bool perMonitorWallpaper: false property bool perMonitorWallpaper: false
property var monitorWallpapers: ({}) property var monitorWallpapers: ({})
property bool doNotDisturb: false property bool perModeWallpaper: false
property string wallpaperPathLight: ""
property string wallpaperPathDark: ""
property var monitorWallpapersLight: ({})
property var monitorWallpapersDark: ({})
property string wallpaperTransition: "fade"
readonly property var availableWallpaperTransitions: ["none", "fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"]
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
property bool wallpaperCyclingEnabled: false
property string wallpaperCyclingMode: "interval"
property int wallpaperCyclingInterval: 300
property string wallpaperCyclingTime: "06:00"
property var monitorCyclingSettings: ({})
property bool nightModeEnabled: false property bool nightModeEnabled: false
property int nightModeTemperature: 4500 property int nightModeTemperature: 4500
property bool nightModeAutoEnabled: false property bool nightModeAutoEnabled: false
property string nightModeAutoMode: "time" property string nightModeAutoMode: "time"
property bool hasTriedDefaultSession: false
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
readonly property string _stateDir: Paths.strip(_stateUrl)
property int nightModeStartHour: 18 property int nightModeStartHour: 18
property int nightModeStartMinute: 0 property int nightModeStartMinute: 0
property int nightModeEndHour: 6 property int nightModeEndHour: 6
@@ -34,44 +50,31 @@ Singleton {
property real latitude: 0.0 property real latitude: 0.0
property real longitude: 0.0 property real longitude: 0.0
property string nightModeLocationProvider: "" property string nightModeLocationProvider: ""
property var pinnedApps: [] property var pinnedApps: []
property var recentColors: []
property bool showThirdPartyPlugins: false
property string launchPrefix: ""
property string lastBrightnessDevice: ""
property int selectedGpuIndex: 0 property int selectedGpuIndex: 0
property bool nvidiaGpuTempEnabled: false property bool nvidiaGpuTempEnabled: false
property bool nonNvidiaGpuTempEnabled: false property bool nonNvidiaGpuTempEnabled: false
property var enabledGpuPciIds: [] property var enabledGpuPciIds: []
property bool wallpaperCyclingEnabled: false
property string wallpaperCyclingMode: "interval" // "interval" or "time"
property int wallpaperCyclingInterval: 300 // seconds (5 minutes)
property string wallpaperCyclingTime: "06:00" // HH:mm format
property var monitorCyclingSettings: ({})
property string lastBrightnessDevice: ""
property string launchPrefix: ""
property string wallpaperTransition: "fade"
readonly property var availableWallpaperTransitions: ["none", "fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"]
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
// Power management settings - AC Power
property int acMonitorTimeout: 0 // Never
property int acLockTimeout: 0 // Never
property int acSuspendTimeout: 0 // Never
property int acHibernateTimeout: 0 // Never
// Power management settings - Battery
property int batteryMonitorTimeout: 0 // Never
property int batteryLockTimeout: 0 // Never
property int batterySuspendTimeout: 0 // Never
property int batteryHibernateTimeout: 0 // Never
property bool lockBeforeSuspend: false
Component.onCompleted: { Component.onCompleted: {
if (!isGreeterMode) {
loadSettings() loadSettings()
} }
}
function loadSettings() { function loadSettings() {
if (isGreeterMode) {
parseSettings(greeterSessionFile.text())
} else {
parseSettings(settingsFile.text()) parseSettings(settingsFile.text())
} }
}
function parseSettings(content) { function parseSettings(content) {
try { try {
@@ -79,16 +82,18 @@ Singleton {
var settings = JSON.parse(content) var settings = JSON.parse(content)
isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false
wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : "" wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : ""
wallpaperLastPath = settings.wallpaperLastPath !== undefined ? settings.wallpaperLastPath : ""
profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : ""
perMonitorWallpaper = settings.perMonitorWallpaper !== undefined ? settings.perMonitorWallpaper : false perMonitorWallpaper = settings.perMonitorWallpaper !== undefined ? settings.perMonitorWallpaper : false
monitorWallpapers = settings.monitorWallpapers !== undefined ? settings.monitorWallpapers : {} monitorWallpapers = settings.monitorWallpapers !== undefined ? settings.monitorWallpapers : {}
perModeWallpaper = settings.perModeWallpaper !== undefined ? settings.perModeWallpaper : false
wallpaperPathLight = settings.wallpaperPathLight !== undefined ? settings.wallpaperPathLight : ""
wallpaperPathDark = settings.wallpaperPathDark !== undefined ? settings.wallpaperPathDark : ""
monitorWallpapersLight = settings.monitorWallpapersLight !== undefined ? settings.monitorWallpapersLight : {}
monitorWallpapersDark = settings.monitorWallpapersDark !== undefined ? settings.monitorWallpapersDark : {}
doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500 nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500
nightModeAutoEnabled = settings.nightModeAutoEnabled !== undefined ? settings.nightModeAutoEnabled : false nightModeAutoEnabled = settings.nightModeAutoEnabled !== undefined ? settings.nightModeAutoEnabled : false
nightModeAutoMode = settings.nightModeAutoMode !== undefined ? settings.nightModeAutoMode : "time" nightModeAutoMode = settings.nightModeAutoMode !== undefined ? settings.nightModeAutoMode : "time"
// Handle legacy time format
if (settings.nightModeStartTime !== undefined) { if (settings.nightModeStartTime !== undefined) {
const parts = settings.nightModeStartTime.split(":") const parts = settings.nightModeStartTime.split(":")
nightModeStartHour = parseInt(parts[0]) || 18 nightModeStartHour = parseInt(parts[0]) || 18
@@ -122,35 +127,39 @@ Singleton {
launchPrefix = settings.launchPrefix !== undefined ? settings.launchPrefix : "" launchPrefix = settings.launchPrefix !== undefined ? settings.launchPrefix : ""
wallpaperTransition = settings.wallpaperTransition !== undefined ? settings.wallpaperTransition : "fade" wallpaperTransition = settings.wallpaperTransition !== undefined ? settings.wallpaperTransition : "fade"
includedTransitions = settings.includedTransitions !== undefined ? settings.includedTransitions : availableWallpaperTransitions.filter(t => t !== "none") includedTransitions = settings.includedTransitions !== undefined ? settings.includedTransitions : availableWallpaperTransitions.filter(t => t !== "none")
recentColors = settings.recentColors !== undefined ? settings.recentColors : []
showThirdPartyPlugins = settings.showThirdPartyPlugins !== undefined ? settings.showThirdPartyPlugins : false
acMonitorTimeout = settings.acMonitorTimeout !== undefined ? settings.acMonitorTimeout : 0 if (settings.configVersion === undefined) {
acLockTimeout = settings.acLockTimeout !== undefined ? settings.acLockTimeout : 0 migrateFromUndefinedToV1(settings)
acSuspendTimeout = settings.acSuspendTimeout !== undefined ? settings.acSuspendTimeout : 0 saveSettings()
acHibernateTimeout = settings.acHibernateTimeout !== undefined ? settings.acHibernateTimeout : 0 } else if (settings.configVersion === sessionConfigVersion) {
batteryMonitorTimeout = settings.batteryMonitorTimeout !== undefined ? settings.batteryMonitorTimeout : 0 cleanupUnusedKeys()
batteryLockTimeout = settings.batteryLockTimeout !== undefined ? settings.batteryLockTimeout : 0 }
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
// Generate system themes but don't override user's theme choice if (!isGreeterMode) {
if (typeof Theme !== "undefined") { if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme() Theme.generateSystemThemesFromCurrentTheme()
} }
} }
}
} catch (e) { } catch (e) {
} }
} }
function saveSettings() { function saveSettings() {
if (isGreeterMode) return
settingsFile.setText(JSON.stringify({ settingsFile.setText(JSON.stringify({
"isLightMode": isLightMode, "isLightMode": isLightMode,
"wallpaperPath": wallpaperPath, "wallpaperPath": wallpaperPath,
"wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath,
"perMonitorWallpaper": perMonitorWallpaper, "perMonitorWallpaper": perMonitorWallpaper,
"monitorWallpapers": monitorWallpapers, "monitorWallpapers": monitorWallpapers,
"perModeWallpaper": perModeWallpaper,
"wallpaperPathLight": wallpaperPathLight,
"wallpaperPathDark": wallpaperPathDark,
"monitorWallpapersLight": monitorWallpapersLight,
"monitorWallpapersDark": monitorWallpapersDark,
"doNotDisturb": doNotDisturb, "doNotDisturb": doNotDisturb,
"nightModeEnabled": nightModeEnabled, "nightModeEnabled": nightModeEnabled,
"nightModeTemperature": nightModeTemperature, "nightModeTemperature": nightModeTemperature,
@@ -177,20 +186,101 @@ Singleton {
"launchPrefix": launchPrefix, "launchPrefix": launchPrefix,
"wallpaperTransition": wallpaperTransition, "wallpaperTransition": wallpaperTransition,
"includedTransitions": includedTransitions, "includedTransitions": includedTransitions,
"acMonitorTimeout": acMonitorTimeout, "recentColors": recentColors,
"acLockTimeout": acLockTimeout, "showThirdPartyPlugins": showThirdPartyPlugins,
"acSuspendTimeout": acSuspendTimeout, "configVersion": sessionConfigVersion
"acHibernateTimeout": acHibernateTimeout,
"batteryMonitorTimeout": batteryMonitorTimeout,
"batteryLockTimeout": batteryLockTimeout,
"batterySuspendTimeout": batterySuspendTimeout,
"batteryHibernateTimeout": batteryHibernateTimeout,
"lockBeforeSuspend": lockBeforeSuspend
}, null, 2)) }, null, 2))
} }
function migrateFromUndefinedToV1(settings) {
console.log("SessionData: Migrating configuration from undefined to version 1")
if (typeof SettingsData !== "undefined") {
if (settings.acMonitorTimeout !== undefined) {
SettingsData.setAcMonitorTimeout(settings.acMonitorTimeout)
}
if (settings.acLockTimeout !== undefined) {
SettingsData.setAcLockTimeout(settings.acLockTimeout)
}
if (settings.acSuspendTimeout !== undefined) {
SettingsData.setAcSuspendTimeout(settings.acSuspendTimeout)
}
if (settings.acHibernateTimeout !== undefined) {
SettingsData.setAcHibernateTimeout(settings.acHibernateTimeout)
}
if (settings.batteryMonitorTimeout !== undefined) {
SettingsData.setBatteryMonitorTimeout(settings.batteryMonitorTimeout)
}
if (settings.batteryLockTimeout !== undefined) {
SettingsData.setBatteryLockTimeout(settings.batteryLockTimeout)
}
if (settings.batterySuspendTimeout !== undefined) {
SettingsData.setBatterySuspendTimeout(settings.batterySuspendTimeout)
}
if (settings.batteryHibernateTimeout !== undefined) {
SettingsData.setBatteryHibernateTimeout(settings.batteryHibernateTimeout)
}
if (settings.lockBeforeSuspend !== undefined) {
SettingsData.setLockBeforeSuspend(settings.lockBeforeSuspend)
}
if (settings.loginctlLockIntegration !== undefined) {
SettingsData.setLoginctlLockIntegration(settings.loginctlLockIntegration)
}
if (settings.launchPrefix !== undefined) {
SettingsData.setLaunchPrefix(settings.launchPrefix)
}
}
if (typeof CacheData !== "undefined") {
if (settings.wallpaperLastPath !== undefined) {
CacheData.wallpaperLastPath = settings.wallpaperLastPath
}
if (settings.profileLastPath !== undefined) {
CacheData.profileLastPath = settings.profileLastPath
}
CacheData.saveCache()
}
}
function cleanupUnusedKeys() {
const validKeys = [
"isLightMode", "wallpaperPath", "perMonitorWallpaper", "monitorWallpapers", "perModeWallpaper",
"wallpaperPathLight", "wallpaperPathDark", "monitorWallpapersLight",
"monitorWallpapersDark", "doNotDisturb", "nightModeEnabled",
"nightModeTemperature", "nightModeAutoEnabled", "nightModeAutoMode",
"nightModeStartHour", "nightModeStartMinute", "nightModeEndHour",
"nightModeEndMinute", "latitude", "longitude", "nightModeLocationProvider",
"pinnedApps", "selectedGpuIndex", "nvidiaGpuTempEnabled",
"nonNvidiaGpuTempEnabled", "enabledGpuPciIds", "wallpaperCyclingEnabled",
"wallpaperCyclingMode", "wallpaperCyclingInterval", "wallpaperCyclingTime",
"monitorCyclingSettings", "lastBrightnessDevice", "wallpaperTransition",
"includedTransitions", "recentColors", "showThirdPartyPlugins", "configVersion"
]
try {
const content = settingsFile.text()
if (!content || !content.trim()) return
const settings = JSON.parse(content)
let needsSave = false
for (const key in settings) {
if (!validKeys.includes(key)) {
console.log("SessionData: Removing unused key:", key)
delete settings[key]
needsSave = true
}
}
if (needsSave) {
settingsFile.setText(JSON.stringify(settings, null, 2))
}
} catch (e) {
console.warn("SessionData: Failed to cleanup unused keys:", e.message)
}
}
function setLightMode(lightMode) { function setLightMode(lightMode) {
isLightMode = lightMode isLightMode = lightMode
syncWallpaperForCurrentMode()
saveSettings() saveSettings()
} }
@@ -199,6 +289,212 @@ Singleton {
saveSettings() saveSettings()
} }
function setWallpaperPath(path) {
wallpaperPath = path
saveSettings()
}
function setWallpaper(imagePath) {
wallpaperPath = imagePath
if (perModeWallpaper) {
if (isLightMode) {
wallpaperPathLight = imagePath
} else {
wallpaperPathDark = imagePath
}
}
saveSettings()
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
}
function setWallpaperColor(color) {
wallpaperPath = color
if (perModeWallpaper) {
if (isLightMode) {
wallpaperPathLight = color
} else {
wallpaperPathDark = color
}
}
saveSettings()
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
}
function clearWallpaper() {
wallpaperPath = ""
saveSettings()
if (typeof Theme !== "undefined") {
if (typeof SettingsData !== "undefined" && SettingsData.theme) {
Theme.switchTheme(SettingsData.theme)
} else {
Theme.switchTheme("blue")
}
}
}
function setPerMonitorWallpaper(enabled) {
perMonitorWallpaper = enabled
if (enabled && perModeWallpaper) {
syncWallpaperForCurrentMode()
}
saveSettings()
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
}
function setPerModeWallpaper(enabled) {
if (enabled && wallpaperCyclingEnabled) {
setWallpaperCyclingEnabled(false)
}
if (enabled && perMonitorWallpaper) {
var monitorCyclingAny = false
for (var key in monitorCyclingSettings) {
if (monitorCyclingSettings[key].enabled) {
monitorCyclingAny = true
break
}
}
if (monitorCyclingAny) {
var newSettings = Object.assign({}, monitorCyclingSettings)
for (var screenName in newSettings) {
newSettings[screenName].enabled = false
}
monitorCyclingSettings = newSettings
}
}
perModeWallpaper = enabled
if (enabled) {
if (perMonitorWallpaper) {
monitorWallpapersLight = Object.assign({}, monitorWallpapers)
monitorWallpapersDark = Object.assign({}, monitorWallpapers)
} else {
wallpaperPathLight = wallpaperPath
wallpaperPathDark = wallpaperPath
}
} else {
syncWallpaperForCurrentMode()
}
saveSettings()
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
}
function setMonitorWallpaper(screenName, path) {
var newMonitorWallpapers = Object.assign({}, monitorWallpapers)
if (path && path !== "") {
newMonitorWallpapers[screenName] = path
} else {
delete newMonitorWallpapers[screenName]
}
monitorWallpapers = newMonitorWallpapers
if (perModeWallpaper) {
if (isLightMode) {
var newLight = Object.assign({}, monitorWallpapersLight)
if (path && path !== "") {
newLight[screenName] = path
} else {
delete newLight[screenName]
}
monitorWallpapersLight = newLight
} else {
var newDark = Object.assign({}, monitorWallpapersDark)
if (path && path !== "") {
newDark[screenName] = path
} else {
delete newDark[screenName]
}
monitorWallpapersDark = newDark
}
}
saveSettings()
if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined") {
var screens = Quickshell.screens
if (screens.length > 0 && screenName === screens[0].name) {
Theme.generateSystemThemesFromCurrentTheme()
}
}
}
function setWallpaperTransition(transition) {
wallpaperTransition = transition
saveSettings()
}
function setWallpaperCyclingEnabled(enabled) {
wallpaperCyclingEnabled = enabled
saveSettings()
}
function setWallpaperCyclingMode(mode) {
wallpaperCyclingMode = mode
saveSettings()
}
function setWallpaperCyclingInterval(interval) {
wallpaperCyclingInterval = interval
saveSettings()
}
function setWallpaperCyclingTime(time) {
wallpaperCyclingTime = time
saveSettings()
}
function setMonitorCyclingEnabled(screenName, enabled) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].enabled = enabled
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingMode(screenName, mode) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].mode = mode
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingInterval(screenName, interval) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].interval = interval
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingTime(screenName, time) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].time = time
monitorCyclingSettings = newSettings
saveSettings()
}
function setNightModeEnabled(enabled) { function setNightModeEnabled(enabled) {
nightModeEnabled = enabled nightModeEnabled = enabled
saveSettings() saveSettings()
@@ -257,58 +553,6 @@ Singleton {
saveSettings() saveSettings()
} }
function setWallpaperPath(path) {
wallpaperPath = path
saveSettings()
}
function setWallpaper(imagePath) {
wallpaperPath = imagePath
saveSettings()
if (typeof Theme !== "undefined") {
if (Theme.currentTheme === Theme.dynamic) {
Theme.extractColors()
}
Theme.generateSystemThemesFromCurrentTheme()
}
}
function setWallpaperColor(color) {
wallpaperPath = color
saveSettings()
if (typeof Theme !== "undefined") {
if (Theme.currentTheme === Theme.dynamic) {
Theme.extractColors()
}
Theme.generateSystemThemesFromCurrentTheme()
}
}
function clearWallpaper() {
wallpaperPath = ""
saveSettings()
if (typeof Theme !== "undefined") {
if (typeof SettingsData !== "undefined" && SettingsData.theme) {
Theme.switchTheme(SettingsData.theme)
} else {
Theme.switchTheme("blue")
}
}
}
function setWallpaperLastPath(path) {
wallpaperLastPath = path
saveSettings()
}
function setProfileLastPath(path) {
profileLastPath = path
saveSettings()
}
function setPinnedApps(apps) { function setPinnedApps(apps) {
pinnedApps = apps pinnedApps = apps
saveSettings() saveSettings()
@@ -335,6 +579,31 @@ Singleton {
return appId && pinnedApps.indexOf(appId) !== -1 return appId && pinnedApps.indexOf(appId) !== -1
} }
function addRecentColor(color) {
const colorStr = color.toString()
let recent = recentColors.slice()
recent = recent.filter(c => c !== colorStr)
recent.unshift(colorStr)
if (recent.length > 5) recent = recent.slice(0, 5)
recentColors = recent
saveSettings()
}
function setShowThirdPartyPlugins(enabled) {
showThirdPartyPlugins = enabled
saveSettings()
}
function setLaunchPrefix(prefix) {
launchPrefix = prefix
saveSettings()
}
function setLastBrightnessDevice(device) {
lastBrightnessDevice = device
saveSettings()
}
function setSelectedGpuIndex(index) { function setSelectedGpuIndex(index) {
selectedGpuIndex = index selectedGpuIndex = index
saveSettings() saveSettings()
@@ -355,24 +624,22 @@ Singleton {
saveSettings() saveSettings()
} }
function setWallpaperCyclingEnabled(enabled) { function syncWallpaperForCurrentMode() {
wallpaperCyclingEnabled = enabled if (!perModeWallpaper) return
saveSettings()
if (perMonitorWallpaper) {
monitorWallpapers = isLightMode ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark)
return
} }
function setWallpaperCyclingMode(mode) { wallpaperPath = isLightMode ? wallpaperPathLight : wallpaperPathDark
wallpaperCyclingMode = mode
saveSettings()
} }
function setWallpaperCyclingInterval(interval) { function getMonitorWallpaper(screenName) {
wallpaperCyclingInterval = interval if (!perMonitorWallpaper) {
saveSettings() return wallpaperPath
} }
return monitorWallpapers[screenName] || wallpaperPath
function setWallpaperCyclingTime(time) {
wallpaperCyclingTime = time
saveSettings()
} }
function getMonitorCyclingSettings(screenName) { function getMonitorCyclingSettings(screenName) {
@@ -384,172 +651,51 @@ Singleton {
} }
} }
function setMonitorCyclingEnabled(screenName, enabled) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].enabled = enabled
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingMode(screenName, mode) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].mode = mode
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingInterval(screenName, interval) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].interval = interval
monitorCyclingSettings = newSettings
saveSettings()
}
function setMonitorCyclingTime(screenName, time) {
var newSettings = Object.assign({}, monitorCyclingSettings)
if (!newSettings[screenName]) {
newSettings[screenName] = { enabled: false, mode: "interval", interval: 300, time: "06:00" }
}
newSettings[screenName].time = time
monitorCyclingSettings = newSettings
saveSettings()
}
function setPerMonitorWallpaper(enabled) {
perMonitorWallpaper = enabled
saveSettings()
// Refresh dynamic theming when per-monitor mode changes
if (typeof Theme !== "undefined") {
if (Theme.currentTheme === Theme.dynamic) {
Theme.extractColors()
}
Theme.generateSystemThemesFromCurrentTheme()
}
}
function setMonitorWallpaper(screenName, path) {
var newMonitorWallpapers = Object.assign({}, monitorWallpapers)
if (path && path !== "") {
newMonitorWallpapers[screenName] = path
} else {
delete newMonitorWallpapers[screenName]
}
monitorWallpapers = newMonitorWallpapers
saveSettings()
// Trigger dynamic theming if this is the first monitor and dynamic theming is enabled
if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined") {
var screens = Quickshell.screens
if (screens.length > 0 && screenName === screens[0].name) {
if (Theme.currentTheme === Theme.dynamic) {
Theme.extractColors()
}
Theme.generateSystemThemesFromCurrentTheme()
}
}
}
function getMonitorWallpaper(screenName) {
if (!perMonitorWallpaper) {
return wallpaperPath
}
return monitorWallpapers[screenName] || wallpaperPath
}
function setLastBrightnessDevice(device) {
lastBrightnessDevice = device
saveSettings()
}
function setLaunchPrefix(prefix) {
launchPrefix = prefix
saveSettings()
}
function setWallpaperTransition(transition) {
wallpaperTransition = transition
saveSettings()
}
function setAcMonitorTimeout(timeout) {
acMonitorTimeout = timeout
saveSettings()
}
function setAcLockTimeout(timeout) {
acLockTimeout = timeout
saveSettings()
}
function setAcSuspendTimeout(timeout) {
acSuspendTimeout = timeout
saveSettings()
}
function setBatteryMonitorTimeout(timeout) {
batteryMonitorTimeout = timeout
saveSettings()
}
function setBatteryLockTimeout(timeout) {
batteryLockTimeout = timeout
saveSettings()
}
function setBatterySuspendTimeout(timeout) {
batterySuspendTimeout = timeout
saveSettings()
}
function setAcHibernateTimeout(timeout) {
acHibernateTimeout = timeout
saveSettings()
}
function setBatteryHibernateTimeout(timeout) {
batteryHibernateTimeout = timeout
saveSettings()
}
function setLockBeforeSuspend(enabled) {
lockBeforeSuspend = enabled
saveSettings()
}
FileView { FileView {
id: settingsFile id: settingsFile
path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json" path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
blockLoading: true blockLoading: isGreeterMode
blockWrites: true blockWrites: true
watchChanges: true watchChanges: !isGreeterMode
onLoaded: { onLoaded: {
if (!isGreeterMode) {
parseSettings(settingsFile.text()) parseSettings(settingsFile.text())
hasTriedDefaultSession = false hasTriedDefaultSession = false
} }
}
onLoadFailed: error => { onLoadFailed: error => {
if (!hasTriedDefaultSession) { if (!isGreeterMode && !hasTriedDefaultSession) {
hasTriedDefaultSession = true hasTriedDefaultSession = true
defaultSessionCheckProcess.running = true defaultSessionCheckProcess.running = true
} }
} }
} }
FileView {
id: greeterSessionFile
path: {
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
return greetCfgDir + "/session.json"
}
preload: isGreeterMode
blockLoading: false
blockWrites: true
watchChanges: false
printErrors: true
onLoaded: {
if (isGreeterMode) {
parseSettings(greeterSessionFile.text())
}
}
}
Process { Process {
id: defaultSessionCheckProcess id: defaultSessionCheckProcess
command: ["sh", "-c", "CONFIG_DIR=\"" + _stateDir command: ["sh", "-c", "CONFIG_DIR=\"" + _stateDir
+ "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-session.json\" ] && [ ! -f \"$CONFIG_DIR/session.json\" ]; then cp \"$CONFIG_DIR/default-session.json\" \"$CONFIG_DIR/session.json\" && echo 'copied'; else echo 'not_found'; fi"] + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-session.json\" ] && [ ! -f \"$CONFIG_DIR/session.json\" ]; then cp --no-preserve=mode \"$CONFIG_DIR/default-session.json\" \"$CONFIG_DIR/session.json\" && echo 'copied'; else echo 'not_found'; fi"]
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {

File diff suppressed because it is too large Load Diff

View File

@@ -2,99 +2,101 @@
// Separated from Theme.qml to keep that file clean // Separated from Theme.qml to keep that file clean
const CatppuccinMocha = { const CatppuccinMocha = {
surface: "#45475a", surface: "#313244",
surfaceText: "#cdd6f4", surfaceText: "#cdd6f4",
surfaceVariant: "#45475a", surfaceVariant: "#313244",
surfaceVariantText: "#a6adc8", surfaceVariantText: "#a6adc8",
background: "#1e1e2e", background: "#1e1e2e",
backgroundText: "#cdd6f4", backgroundText: "#cdd6f4",
outline: "#6c7086", outline: "#6c7086",
surfaceContainer: "#313244", surfaceContainer: "#45475a",
surfaceContainerHigh: "#585b70" surfaceContainerHigh: "#585b70",
surfaceContainerHighest: "#6c7086"
} }
const CatppuccinLatte = { const CatppuccinLatte = {
surface: "#bcc0cc", surface: "#e6e9ef",
surfaceText: "#4c4f69", surfaceText: "#4c4f69",
surfaceVariant: "#bcc0cc", surfaceVariant: "#e6e9ef",
surfaceVariantText: "#6c6f85", surfaceVariantText: "#6c6f85",
background: "#eff1f5", background: "#eff1f5",
backgroundText: "#4c4f69", backgroundText: "#4c4f69",
outline: "#9ca0b0", outline: "#9ca0b0",
surfaceContainer: "#ccd0da", surfaceContainer: "#dce0e8",
surfaceContainerHigh: "#acb0be" surfaceContainerHigh: "#ccd0da",
surfaceContainerHighest: "#bcc0cc"
} }
const CatppuccinVariants = { const CatppuccinVariants = {
"cat-rosewater": { "cat-rosewater": {
name: "Rosewater", name: "Rosewater",
dark: { primary: "#f5e0dc", secondary: "#f2cdcd", primaryText: "#1e1e2e", primaryContainer: "#8b6b5e", surfaceTint: "#f5e0dc" }, dark: { primary: "#f5e0dc", secondary: "#f2cdcd", primaryText: "#1e1e2e", primaryContainer: "#7d5d56", surfaceTint: "#f5e0dc" },
light: { primary: "#dc8a78", secondary: "#dd7878", primaryText: "#ffffff", primaryContainer: "#f4d2ca", surfaceTint: "#dc8a78" } light: { primary: "#dc8a78", secondary: "#dd7878", primaryText: "#ffffff", primaryContainer: "#f6e7e3", surfaceTint: "#dc8a78" }
}, },
"cat-flamingo": { "cat-flamingo": {
name: "Flamingo", name: "Flamingo",
dark: { primary: "#f2cdcd", secondary: "#f5e0dc", primaryText: "#1e1e2e", primaryContainer: "#885d62", surfaceTint: "#f2cdcd" }, dark: { primary: "#f2cdcd", secondary: "#f5e0dc", primaryText: "#1e1e2e", primaryContainer: "#7a555a", surfaceTint: "#f2cdcd" },
light: { primary: "#dd7878", secondary: "#dc8a78", primaryText: "#ffffff", primaryContainer: "#f4caca", surfaceTint: "#dd7878" } light: { primary: "#dd7878", secondary: "#dc8a78", primaryText: "#ffffff", primaryContainer: "#f6e5e5", surfaceTint: "#dd7878" }
}, },
"cat-pink": { "cat-pink": {
name: "Pink", name: "Pink",
dark: { primary: "#f5c2e7", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#8b537a", surfaceTint: "#f5c2e7" }, dark: { primary: "#f5c2e7", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#7a3f69", surfaceTint: "#f5c2e7" },
light: { primary: "#ea76cb", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#f7c9e7", surfaceTint: "#ea76cb" } light: { primary: "#ea76cb", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#f7d7ee", surfaceTint: "#ea76cb" }
}, },
"cat-mauve": { "cat-mauve": {
name: "Mauve", name: "Mauve",
dark: { primary: "#cba6f7", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#61378a", surfaceTint: "#cba6f7" }, dark: { primary: "#cba6f7", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#55307f", surfaceTint: "#cba6f7" },
light: { primary: "#8839ef", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#e4d3ff", surfaceTint: "#8839ef" } light: { primary: "#8839ef", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#eadcff", surfaceTint: "#8839ef" }
}, },
"cat-red": { "cat-red": {
name: "Red", name: "Red",
dark: { primary: "#f38ba8", secondary: "#eba0ac", primaryText: "#1e1e2e", primaryContainer: "#891c3b", surfaceTint: "#f38ba8" }, dark: { primary: "#f38ba8", secondary: "#eba0ac", primaryText: "#1e1e2e", primaryContainer: "#6f2438", surfaceTint: "#f38ba8" },
light: { primary: "#d20f39", secondary: "#e64553", primaryText: "#ffffff", primaryContainer: "#f1b8c4", surfaceTint: "#d20f39" } light: { primary: "#d20f39", secondary: "#e64553", primaryText: "#ffffff", primaryContainer: "#f6d0d6", surfaceTint: "#d20f39" }
}, },
"cat-maroon": { "cat-maroon": {
name: "Maroon", name: "Maroon",
dark: { primary: "#eba0ac", secondary: "#f38ba8", primaryText: "#1e1e2e", primaryContainer: "#81313f", surfaceTint: "#eba0ac" }, dark: { primary: "#eba0ac", secondary: "#f38ba8", primaryText: "#1e1e2e", primaryContainer: "#6d3641", surfaceTint: "#eba0ac" },
light: { primary: "#e64553", secondary: "#d20f39", primaryText: "#ffffff", primaryContainer: "#f4c3c8", surfaceTint: "#e64553" } light: { primary: "#e64553", secondary: "#d20f39", primaryText: "#ffffff", primaryContainer: "#f7d8dc", surfaceTint: "#e64553" }
}, },
"cat-peach": { "cat-peach": {
name: "Peach", name: "Peach",
dark: { primary: "#fab387", secondary: "#f9e2af", primaryText: "#1e1e2e", primaryContainer: "#90441a", surfaceTint: "#fab387" }, dark: { primary: "#fab387", secondary: "#f9e2af", primaryText: "#1e1e2e", primaryContainer: "#734226", surfaceTint: "#fab387" },
light: { primary: "#fe640b", secondary: "#df8e1d", primaryText: "#ffffff", primaryContainer: "#ffddcc", surfaceTint: "#fe640b" } light: { primary: "#fe640b", secondary: "#df8e1d", primaryText: "#ffffff", primaryContainer: "#ffe4d5", surfaceTint: "#fe640b" }
}, },
"cat-yellow": { "cat-yellow": {
name: "Yellow", name: "Yellow",
dark: { primary: "#f9e2af", secondary: "#a6e3a1", primaryText: "#1e1e2e", primaryContainer: "#8f7342", surfaceTint: "#f9e2af" }, dark: { primary: "#f9e2af", secondary: "#a6e3a1", primaryText: "#1e1e2e", primaryContainer: "#6e5a2f", surfaceTint: "#f9e2af" },
light: { primary: "#df8e1d", secondary: "#40a02b", primaryText: "#ffffff", primaryContainer: "#fff3cc", surfaceTint: "#df8e1d" } light: { primary: "#df8e1d", secondary: "#40a02b", primaryText: "#ffffff", primaryContainer: "#fff6d6", surfaceTint: "#df8e1d" }
}, },
"cat-green": { "cat-green": {
name: "Green", name: "Green",
dark: { primary: "#a6e3a1", secondary: "#94e2d5", primaryText: "#1e1e2e", primaryContainer: "#3c7534", surfaceTint: "#a6e3a1" }, dark: { primary: "#a6e3a1", secondary: "#94e2d5", primaryText: "#1e1e2e", primaryContainer: "#2f5f36", surfaceTint: "#a6e3a1" },
light: { primary: "#40a02b", secondary: "#179299", primaryText: "#ffffff", primaryContainer: "#d4f5d4", surfaceTint: "#40a02b" } light: { primary: "#40a02b", secondary: "#179299", primaryText: "#ffffff", primaryContainer: "#dff4e0", surfaceTint: "#40a02b" }
}, },
"cat-teal": { "cat-teal": {
name: "Teal", name: "Teal",
dark: { primary: "#94e2d5", secondary: "#89dceb", primaryText: "#1e1e2e", primaryContainer: "#2a7468", surfaceTint: "#94e2d5" }, dark: { primary: "#94e2d5", secondary: "#89dceb", primaryText: "#1e1e2e", primaryContainer: "#2e5e59", surfaceTint: "#94e2d5" },
light: { primary: "#179299", secondary: "#04a5e5", primaryText: "#ffffff", primaryContainer: "#ccf2f2", surfaceTint: "#179299" } light: { primary: "#179299", secondary: "#04a5e5", primaryText: "#ffffff", primaryContainer: "#daf3f1", surfaceTint: "#179299" }
}, },
"cat-sky": { "cat-sky": {
name: "Sky", name: "Sky",
dark: { primary: "#89dceb", secondary: "#74c7ec", primaryText: "#1e1e2e", primaryContainer: "#196e7e", surfaceTint: "#89dceb" }, dark: { primary: "#89dceb", secondary: "#74c7ec", primaryText: "#1e1e2e", primaryContainer: "#24586a", surfaceTint: "#89dceb" },
light: { primary: "#04a5e5", secondary: "#209fb5", primaryText: "#ffffff", primaryContainer: "#ccebff", surfaceTint: "#04a5e5" } light: { primary: "#04a5e5", secondary: "#209fb5", primaryText: "#ffffff", primaryContainer: "#dbf1fb", surfaceTint: "#04a5e5" }
}, },
"cat-sapphire": { "cat-sapphire": {
name: "Sapphire", name: "Sapphire",
dark: { primary: "#74c7ec", secondary: "#89b4fa", primaryText: "#1e1e2e", primaryContainer: "#0a597f", surfaceTint: "#74c7ec" }, dark: { primary: "#74c7ec", secondary: "#89b4fa", primaryText: "#1e1e2e", primaryContainer: "#1f4d6f", surfaceTint: "#74c7ec" },
light: { primary: "#209fb5", secondary: "#1e66f5", primaryText: "#ffffff", primaryContainer: "#d0f0f5", surfaceTint: "#209fb5" } light: { primary: "#209fb5", secondary: "#1e66f5", primaryText: "#ffffff", primaryContainer: "#def3f8", surfaceTint: "#209fb5" }
}, },
"cat-blue": { "cat-blue": {
name: "Blue", name: "Blue",
dark: { primary: "#89b4fa", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#19468d", surfaceTint: "#89b4fa" }, dark: { primary: "#89b4fa", secondary: "#b4befe", primaryText: "#1e1e2e", primaryContainer: "#243f75", surfaceTint: "#89b4fa" },
light: { primary: "#1e66f5", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#ccd9ff", surfaceTint: "#1e66f5" } light: { primary: "#1e66f5", secondary: "#7287fd", primaryText: "#ffffff", primaryContainer: "#e0e9ff", surfaceTint: "#1e66f5" }
}, },
"cat-lavender": { "cat-lavender": {
name: "Lavender", name: "Lavender",
dark: { primary: "#b4befe", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#4a5091", surfaceTint: "#b4befe" }, dark: { primary: "#b4befe", secondary: "#cba6f7", primaryText: "#1e1e2e", primaryContainer: "#3f4481", surfaceTint: "#b4befe" },
light: { primary: "#7287fd", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#dde1ff", surfaceTint: "#7287fd" } light: { primary: "#7287fd", secondary: "#8839ef", primaryText: "#ffffff", primaryContainer: "#e5e8ff", surfaceTint: "#7287fd" }
} }
} }
@@ -118,16 +120,17 @@ const StockThemes = {
primaryText: "#000000", primaryText: "#000000",
primaryContainer: "#0d47a1", primaryContainer: "#0d47a1",
secondary: "#8ab4f8", secondary: "#8ab4f8",
surface: "#1a1c1e", surface: "#101418",
surfaceText: "#e3e8ef", surfaceText: "#e0e2e8",
surfaceVariant: "#44464f", surfaceVariant: "#42474e",
surfaceVariantText: "#c4c7c5", surfaceVariantText: "#c2c7cf",
surfaceTint: "#8ab4f8", surfaceTint: "#8ab4f8",
background: "#1a1c1e", background: "#101418",
backgroundText: "#e3e8ef", backgroundText: "#e0e2e8",
outline: "#8e918f", outline: "#8c9199",
surfaceContainer: "#1e2023", surfaceContainer: "#1d2024",
surfaceContainerHigh: "#292b2f" surfaceContainerHigh: "#272a2f",
surfaceContainerHighest: "#32353a"
}, },
purple: { purple: {
name: "Purple", name: "Purple",
@@ -135,16 +138,17 @@ const StockThemes = {
primaryText: "#381E72", primaryText: "#381E72",
primaryContainer: "#4F378B", primaryContainer: "#4F378B",
secondary: "#CCC2DC", secondary: "#CCC2DC",
surface: "#10121E", surface: "#141218",
surfaceText: "#E6E0E9", surfaceText: "#e6e0e9",
surfaceVariant: "#49454F", surfaceVariant: "#49454e",
surfaceVariantText: "#CAC4D0", surfaceVariantText: "#cac4cf",
surfaceTint: "#D0BCFF", surfaceTint: "#D0BCFF",
background: "#10121E", background: "#141218",
backgroundText: "#E6E0E9", backgroundText: "#e6e0e9",
outline: "#938F99", outline: "#948f99",
surfaceContainer: "#1D1B20", surfaceContainer: "#211f24",
surfaceContainerHigh: "#2B2930" surfaceContainerHigh: "#2b292f",
surfaceContainerHighest: "#36343a"
}, },
green: { green: {
name: "Green", name: "Green",
@@ -152,16 +156,17 @@ const StockThemes = {
primaryText: "#000000", primaryText: "#000000",
primaryContainer: "#1b5e20", primaryContainer: "#1b5e20",
secondary: "#81c995", secondary: "#81c995",
surface: "#0f1411", surface: "#10140f",
surfaceText: "#e1f5e3", surfaceText: "#e0e4db",
surfaceVariant: "#404943", surfaceVariant: "#424940",
surfaceVariantText: "#c1cbc4", surfaceVariantText: "#c2c9bd",
surfaceTint: "#81c995", surfaceTint: "#81c995",
background: "#0f1411", background: "#10140f",
backgroundText: "#e1f5e3", backgroundText: "#e0e4db",
outline: "#8b938c", outline: "#8c9388",
surfaceContainer: "#1a1f1b", surfaceContainer: "#1d211b",
surfaceContainerHigh: "#252a26" surfaceContainerHigh: "#272b25",
surfaceContainerHighest: "#323630"
}, },
orange: { orange: {
name: "Orange", name: "Orange",
@@ -169,16 +174,17 @@ const StockThemes = {
primaryText: "#000000", primaryText: "#000000",
primaryContainer: "#3e2723", primaryContainer: "#3e2723",
secondary: "#ffb74d", secondary: "#ffb74d",
surface: "#1c1410", surface: "#1a120e",
surfaceText: "#f5f1ea", surfaceText: "#f0dfd8",
surfaceVariant: "#4a453a", surfaceVariant: "#52443d",
surfaceVariantText: "#cbc5b8", surfaceVariantText: "#d7c2b9",
surfaceTint: "#ffb74d", surfaceTint: "#ffb74d",
background: "#1c1410", background: "#1a120e",
backgroundText: "#f5f1ea", backgroundText: "#f0dfd8",
outline: "#958f84", outline: "#a08d85",
surfaceContainer: "#211e17", surfaceContainer: "#271e1a",
surfaceContainerHigh: "#2c291f" surfaceContainerHigh: "#322824",
surfaceContainerHighest: "#3d332e"
}, },
red: { red: {
name: "Red", name: "Red",
@@ -186,16 +192,17 @@ const StockThemes = {
primaryText: "#000000", primaryText: "#000000",
primaryContainer: "#4a0e0e", primaryContainer: "#4a0e0e",
secondary: "#f28b82", secondary: "#f28b82",
surface: "#1c1011", surface: "#1a1110",
surfaceText: "#f5e8ea", surfaceText: "#f1dedc",
surfaceVariant: "#4a3f41", surfaceVariant: "#534341",
surfaceVariantText: "#cbc2c4", surfaceVariantText: "#d8c2be",
surfaceTint: "#f28b82", surfaceTint: "#f28b82",
background: "#1c1011", background: "#1a1110",
backgroundText: "#f5e8ea", backgroundText: "#f1dedc",
outline: "#958b8d", outline: "#a08c89",
surfaceContainer: "#211b1c", surfaceContainer: "#271d1c",
surfaceContainerHigh: "#2c2426" surfaceContainerHigh: "#322826",
surfaceContainerHighest: "#3d3231"
}, },
cyan: { cyan: {
name: "Cyan", name: "Cyan",
@@ -203,16 +210,17 @@ const StockThemes = {
primaryText: "#000000", primaryText: "#000000",
primaryContainer: "#004d5c", primaryContainer: "#004d5c",
secondary: "#4dd0e1", secondary: "#4dd0e1",
surface: "#0f1617", surface: "#0e1416",
surfaceText: "#e8f4f5", surfaceText: "#dee3e5",
surfaceVariant: "#3f474a", surfaceVariant: "#3f484a",
surfaceVariantText: "#c2c9cb", surfaceVariantText: "#bfc8ca",
surfaceTint: "#4dd0e1", surfaceTint: "#4dd0e1",
background: "#0f1617", background: "#0e1416",
backgroundText: "#e8f4f5", backgroundText: "#dee3e5",
outline: "#8c9194", outline: "#899295",
surfaceContainer: "#1a1f20", surfaceContainer: "#1b2122",
surfaceContainerHigh: "#252b2c" surfaceContainerHigh: "#252b2c",
surfaceContainerHighest: "#303637"
}, },
pink: { pink: {
name: "Pink", name: "Pink",
@@ -220,16 +228,17 @@ const StockThemes = {
primaryText: "#000000", primaryText: "#000000",
primaryContainer: "#4a0e2f", primaryContainer: "#4a0e2f",
secondary: "#f8bbd9", secondary: "#f8bbd9",
surface: "#1a1014", surface: "#191112",
surfaceText: "#f3e8ee", surfaceText: "#f0dee0",
surfaceVariant: "#483f45", surfaceVariant: "#524345",
surfaceVariantText: "#c9c2c7", surfaceVariantText: "#d6c2c3",
surfaceTint: "#f8bbd9", surfaceTint: "#f8bbd9",
background: "#1a1014", background: "#191112",
backgroundText: "#f3e8ee", backgroundText: "#f0dee0",
outline: "#938a90", outline: "#9f8c8e",
surfaceContainer: "#1f1b1e", surfaceContainer: "#261d1e",
surfaceContainerHigh: "#2a2428" surfaceContainerHigh: "#312829",
surfaceContainerHighest: "#3c3233"
}, },
amber: { amber: {
name: "Amber", name: "Amber",
@@ -237,16 +246,17 @@ const StockThemes = {
primaryText: "#000000", primaryText: "#000000",
primaryContainer: "#4a3c00", primaryContainer: "#4a3c00",
secondary: "#ffd54f", secondary: "#ffd54f",
surface: "#1a1710", surface: "#17130b",
surfaceText: "#f3f0e8", surfaceText: "#ebe1d4",
surfaceVariant: "#49453a", surfaceVariant: "#4d4639",
surfaceVariantText: "#cac5b8", surfaceVariantText: "#d0c5b4",
surfaceTint: "#ffd54f", surfaceTint: "#ffd54f",
background: "#1a1710", background: "#17130b",
backgroundText: "#f3f0e8", backgroundText: "#ebe1d4",
outline: "#949084", outline: "#998f80",
surfaceContainer: "#1f1e17", surfaceContainer: "#231f17",
surfaceContainerHigh: "#2a281f" surfaceContainerHigh: "#2e2921",
surfaceContainerHighest: "#39342b"
}, },
coral: { coral: {
name: "Coral", name: "Coral",
@@ -255,15 +265,16 @@ const StockThemes = {
primaryContainer: "#8c1d18", primaryContainer: "#8c1d18",
secondary: "#f9dedc", secondary: "#f9dedc",
surface: "#1a1110", surface: "#1a1110",
surfaceText: "#f1e8e7", surfaceText: "#f1dedc",
surfaceVariant: "#4a4142", surfaceVariant: "#534341",
surfaceVariantText: "#cdc2c1", surfaceVariantText: "#d8c2bf",
surfaceTint: "#ffb4ab", surfaceTint: "#ffb4ab",
background: "#1a1110", background: "#1a1110",
backgroundText: "#f1e8e7", backgroundText: "#f1dedc",
outline: "#968b8a", outline: "#a08c8a",
surfaceContainer: "#201a19", surfaceContainer: "#271d1c",
surfaceContainerHigh: "#2b2221" surfaceContainerHigh: "#322826",
surfaceContainerHighest: "#3d3231"
}, },
monochrome: { monochrome: {
name: "Monochrome", name: "Monochrome",
@@ -279,8 +290,9 @@ const StockThemes = {
background: "#131315", background: "#131315",
backgroundText: "#e4e2e3", backgroundText: "#e4e2e3",
outline: "#929092", outline: "#929092",
surfaceContainer: "#2a2a2a", surfaceContainer: "#353535",
surfaceContainerHigh: "#2a2a2b", surfaceContainerHigh: "#424242",
surfaceContainerHighest: "#505050",
error: "#ffb4ab", error: "#ffb4ab",
warning: "#3f4759", warning: "#3f4759",
info: "#595e6c", info: "#595e6c",
@@ -294,16 +306,17 @@ const StockThemes = {
primaryText: "#ffffff", primaryText: "#ffffff",
primaryContainer: "#e3f2fd", primaryContainer: "#e3f2fd",
secondary: "#42a5f5", secondary: "#42a5f5",
surface: "#fefefe", surface: "#f7f9ff",
surfaceText: "#1a1c1e", surfaceText: "#181c20",
surfaceVariant: "#e7e0ec", surfaceVariant: "#dee3eb",
surfaceVariantText: "#49454f", surfaceVariantText: "#42474e",
surfaceTint: "#1976d2", surfaceTint: "#1976d2",
background: "#fefefe", background: "#f7f9ff",
backgroundText: "#1a1c1e", backgroundText: "#181c20",
outline: "#79747e", outline: "#72777f",
surfaceContainer: "#f3f3f3", surfaceContainer: "#eceef4",
surfaceContainerHigh: "#ececec" surfaceContainerHigh: "#e6e8ee",
surfaceContainerHighest: "#e0e2e8"
}, },
purple: { purple: {
name: "Purple Light", name: "Purple Light",
@@ -311,16 +324,17 @@ const StockThemes = {
primaryText: "#ffffff", primaryText: "#ffffff",
primaryContainer: "#EADDFF", primaryContainer: "#EADDFF",
secondary: "#625B71", secondary: "#625B71",
surface: "#FFFBFE", surface: "#fef7ff",
surfaceText: "#1C1B1F", surfaceText: "#1d1b20",
surfaceVariant: "#E7E0EC", surfaceVariant: "#e7e0eb",
surfaceVariantText: "#49454F", surfaceVariantText: "#49454e",
surfaceTint: "#6750A4", surfaceTint: "#6750A4",
background: "#FFFBFE", background: "#fef7ff",
backgroundText: "#1C1B1F", backgroundText: "#1d1b20",
outline: "#79747E", outline: "#7a757f",
surfaceContainer: "#F3EDF7", surfaceContainer: "#f2ecf4",
surfaceContainerHigh: "#ECE6F0" surfaceContainerHigh: "#ece6ee",
surfaceContainerHighest: "#e6e0e9"
}, },
green: { green: {
name: "Green Light", name: "Green Light",
@@ -328,16 +342,17 @@ const StockThemes = {
primaryText: "#ffffff", primaryText: "#ffffff",
primaryContainer: "#e8f5e8", primaryContainer: "#e8f5e8",
secondary: "#4caf50", secondary: "#4caf50",
surface: "#fefefe", surface: "#f7fbf1",
surfaceText: "#1a1c1e", surfaceText: "#191d17",
surfaceVariant: "#e7e0ec", surfaceVariant: "#dee5d8",
surfaceVariantText: "#49454f", surfaceVariantText: "#424940",
surfaceTint: "#2e7d32", surfaceTint: "#2e7d32",
background: "#fefefe", background: "#f7fbf1",
backgroundText: "#1a1c1e", backgroundText: "#191d17",
outline: "#79747e", outline: "#72796f",
surfaceContainer: "#f3f3f3", surfaceContainer: "#ecefe6",
surfaceContainerHigh: "#ececec" surfaceContainerHigh: "#e6e9e0",
surfaceContainerHighest: "#e0e4db"
}, },
orange: { orange: {
name: "Orange Light", name: "Orange Light",
@@ -345,16 +360,17 @@ const StockThemes = {
primaryText: "#ffffff", primaryText: "#ffffff",
primaryContainer: "#ffecb3", primaryContainer: "#ffecb3",
secondary: "#ff9800", secondary: "#ff9800",
surface: "#fefefe", surface: "#fff8f6",
surfaceText: "#1a1c1e", surfaceText: "#221a16",
surfaceVariant: "#e7e0ec", surfaceVariant: "#f4ded5",
surfaceVariantText: "#49454f", surfaceVariantText: "#52443d",
surfaceTint: "#e65100", surfaceTint: "#e65100",
background: "#fefefe", background: "#fff8f6",
backgroundText: "#1a1c1e", backgroundText: "#221a16",
outline: "#79747e", outline: "#85736c",
surfaceContainer: "#f3f3f3", surfaceContainer: "#fceae3",
surfaceContainerHigh: "#ececec" surfaceContainerHigh: "#f6e5de",
surfaceContainerHighest: "#f0dfd8"
}, },
red: { red: {
name: "Red Light", name: "Red Light",
@@ -362,16 +378,17 @@ const StockThemes = {
primaryText: "#ffffff", primaryText: "#ffffff",
primaryContainer: "#ffebee", primaryContainer: "#ffebee",
secondary: "#f44336", secondary: "#f44336",
surface: "#fefefe", surface: "#fff8f7",
surfaceText: "#1a1c1e", surfaceText: "#231918",
surfaceVariant: "#e7e0ec", surfaceVariant: "#f5ddda",
surfaceVariantText: "#49454f", surfaceVariantText: "#534341",
surfaceTint: "#d32f2f", surfaceTint: "#d32f2f",
background: "#fefefe", background: "#fff8f7",
backgroundText: "#1a1c1e", backgroundText: "#231918",
outline: "#79747e", outline: "#857370",
surfaceContainer: "#f3f3f3", surfaceContainer: "#fceae7",
surfaceContainerHigh: "#ececec" surfaceContainerHigh: "#f7e4e1",
surfaceContainerHighest: "#f1dedc"
}, },
cyan: { cyan: {
name: "Cyan Light", name: "Cyan Light",
@@ -379,16 +396,17 @@ const StockThemes = {
primaryText: "#ffffff", primaryText: "#ffffff",
primaryContainer: "#e0f2f1", primaryContainer: "#e0f2f1",
secondary: "#00bcd4", secondary: "#00bcd4",
surface: "#fefefe", surface: "#f5fafc",
surfaceText: "#1a1c1e", surfaceText: "#171d1e",
surfaceVariant: "#e7e0ec", surfaceVariant: "#dbe4e6",
surfaceVariantText: "#49454f", surfaceVariantText: "#3f484a",
surfaceTint: "#0097a7", surfaceTint: "#0097a7",
background: "#fefefe", background: "#f5fafc",
backgroundText: "#1a1c1e", backgroundText: "#171d1e",
outline: "#79747e", outline: "#6f797b",
surfaceContainer: "#f3f3f3", surfaceContainer: "#e9eff0",
surfaceContainerHigh: "#ececec" surfaceContainerHigh: "#e3e9eb",
surfaceContainerHighest: "#dee3e5"
}, },
pink: { pink: {
name: "Pink Light", name: "Pink Light",
@@ -396,16 +414,17 @@ const StockThemes = {
primaryText: "#ffffff", primaryText: "#ffffff",
primaryContainer: "#fce4ec", primaryContainer: "#fce4ec",
secondary: "#e91e63", secondary: "#e91e63",
surface: "#fefefe", surface: "#fff8f7",
surfaceText: "#1a1c1e", surfaceText: "#22191a",
surfaceVariant: "#e7e0ec", surfaceVariant: "#f3dddf",
surfaceVariantText: "#49454f", surfaceVariantText: "#524345",
surfaceTint: "#c2185b", surfaceTint: "#c2185b",
background: "#fefefe", background: "#fff8f7",
backgroundText: "#1a1c1e", backgroundText: "#22191a",
outline: "#79747e", outline: "#847375",
surfaceContainer: "#f3f3f3", surfaceContainer: "#fbeaeb",
surfaceContainerHigh: "#ececec" surfaceContainerHigh: "#f5e4e5",
surfaceContainerHighest: "#f0dee0"
}, },
amber: { amber: {
name: "Amber Light", name: "Amber Light",
@@ -413,16 +432,17 @@ const StockThemes = {
primaryText: "#000000", primaryText: "#000000",
primaryContainer: "#fff8e1", primaryContainer: "#fff8e1",
secondary: "#ffc107", secondary: "#ffc107",
surface: "#fefefe", surface: "#fff8f2",
surfaceText: "#1a1c1e", surfaceText: "#1f1b13",
surfaceVariant: "#e7e0ec", surfaceVariant: "#ede1cf",
surfaceVariantText: "#49454f", surfaceVariantText: "#4d4639",
surfaceTint: "#ff8f00", surfaceTint: "#ff8f00",
background: "#fefefe", background: "#fff8f2",
backgroundText: "#1a1c1e", backgroundText: "#1f1b13",
outline: "#79747e", outline: "#7f7667",
surfaceContainer: "#f3f3f3", surfaceContainer: "#f6ecdf",
surfaceContainerHigh: "#ececec" surfaceContainerHigh: "#f1e7d9",
surfaceContainerHighest: "#ebe1d4"
}, },
coral: { coral: {
name: "Coral Light", name: "Coral Light",
@@ -430,16 +450,17 @@ const StockThemes = {
primaryText: "#ffffff", primaryText: "#ffffff",
primaryContainer: "#ffdad6", primaryContainer: "#ffdad6",
secondary: "#ff5449", secondary: "#ff5449",
surface: "#fefefe", surface: "#fff8f7",
surfaceText: "#1a1c1e", surfaceText: "#231918",
surfaceVariant: "#e7e0ec", surfaceVariant: "#f5ddda",
surfaceVariantText: "#49454f", surfaceVariantText: "#534341",
surfaceTint: "#8c1d18", surfaceTint: "#8c1d18",
background: "#fefefe", background: "#fff8f7",
backgroundText: "#1a1c1e", backgroundText: "#231918",
outline: "#79747e", outline: "#857371",
surfaceContainer: "#f3f3f3", surfaceContainer: "#fceae7",
surfaceContainerHigh: "#ececec" surfaceContainerHigh: "#f6e4e2",
surfaceContainerHighest: "#f1dedc"
}, },
monochrome: { monochrome: {
name: "Monochrome Light", name: "Monochrome Light",
@@ -455,8 +476,9 @@ const StockThemes = {
background: "#ffffff", background: "#ffffff",
backgroundText: "#1a1a1a", backgroundText: "#1a1a1a",
outline: "#757577", outline: "#757577",
surfaceContainer: "#f5f5f6", surfaceContainer: "#e8e8ea",
surfaceContainerHigh: "#eaeaeb", surfaceContainerHigh: "#dcdcde",
surfaceContainerHighest: "#d0d0d2",
error: "#ba1a1a", error: "#ba1a1a",
warning: "#f9e79f", warning: "#f9e79f",
info: "#5d6475", info: "#5d6475",

View File

@@ -14,11 +14,19 @@ import "StockThemes.js" as StockThemes
Singleton { Singleton {
id: root id: root
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericCacheLocation).toString()) + "/DankMaterialShell"
readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true" readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true"
readonly property real popupDistance: {
if (typeof SettingsData === "undefined") return 4
return SettingsData.popupGapsAuto ? Math.max(4, SettingsData.dankBarSpacing) : SettingsData.popupGapsManual
}
property string currentTheme: "blue" property string currentTheme: "blue"
property string currentThemeCategory: "generic" property string currentThemeCategory: "generic"
property bool isLightMode: false property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false
property bool colorsFileLoadFailed: false
readonly property string dynamic: "dynamic" readonly property string dynamic: "dynamic"
readonly property string custom : "custom" readonly property string custom : "custom"
@@ -30,7 +38,6 @@ Singleton {
if (typeof SessionData === "undefined") return "" if (typeof SessionData === "undefined") return ""
if (SessionData.perMonitorWallpaper) { if (SessionData.perMonitorWallpaper) {
// Use first monitor's wallpaper for dynamic theming
var screens = Quickshell.screens var screens = Quickshell.screens
if (screens.length > 0) { if (screens.length > 0) {
var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name) var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name)
@@ -72,25 +79,81 @@ Singleton {
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
property var workerRunning: false property var workerRunning: false
property var matugenColors: ({}) property var matugenColors: ({})
property bool extractionRequested: false
property int colorUpdateTrigger: 0
property var customThemeData: null property var customThemeData: null
readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.CacheLocation).toString()) + "/dankshell"
Component.onCompleted: { Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", stateDir]) Quickshell.execDetached(["mkdir", "-p", stateDir])
matugenCheck.running = true Proc.runCommand("matugenCheck", ["which", "matugen"], (output, code) => {
if (typeof SessionData !== "undefined") matugenAvailable = (code === 0) && !envDisableMatugen
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!matugenAvailable || isGreeterMode) {
return
}
if (colorsFileLoadFailed && currentTheme === dynamic && wallpaperPath) {
console.log("Theme: Matugen now available, regenerating colors for dynamic theme")
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (wallpaperPath.startsWith("#")) {
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
}
return
}
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
if (currentTheme === dynamic) {
if (wallpaperPath) {
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (wallpaperPath.startsWith("#")) {
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
}
}
} else {
let primaryColor
let matugenType
if (currentTheme === "custom") {
if (customThemeData && customThemeData.primary) {
primaryColor = customThemeData.primary
matugenType = customThemeData.matugen_type
}
} else {
primaryColor = currentThemeData.primary
matugenType = currentThemeData.matugen_type
}
if (primaryColor) {
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType)
}
}
}, 0)
if (typeof SessionData !== "undefined") {
SessionData.isLightModeChanged.connect(root.onLightModeChanged) SessionData.isLightModeChanged.connect(root.onLightModeChanged)
}
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) { if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
switchTheme(SettingsData.currentThemeName, false) switchTheme(SettingsData.currentThemeName, false, false)
}
}
function applyGreeterTheme(themeName) {
switchTheme(themeName, false, false)
if (themeName === dynamic && dynamicColorsFileView.path) {
dynamicColorsFileView.reload()
} }
} }
function getMatugenColor(path, fallback) { function getMatugenColor(path, fallback) {
colorUpdateTrigger
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark" const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode] let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode]
for (const part of path.split(".")) { for (const part of path.split(".")) {
@@ -120,6 +183,7 @@ Singleton {
"outline": getMatugenColor("outline", "#8e918f"), "outline": getMatugenColor("outline", "#8e918f"),
"surfaceContainer": getMatugenColor("surface_container", "#1e2023"), "surfaceContainer": getMatugenColor("surface_container", "#1e2023"),
"surfaceContainerHigh": getMatugenColor("surface_container_high", "#292b2f"), "surfaceContainerHigh": getMatugenColor("surface_container_high", "#292b2f"),
"surfaceContainerHighest": getMatugenColor("surface_container_highest", "#343740"),
"error": "#F2B8B5", "error": "#F2B8B5",
"warning": "#FF9800", "warning": "#FF9800",
"info": "#2196F3", "info": "#2196F3",
@@ -154,7 +218,12 @@ Singleton {
property color primaryText: currentThemeData.primaryText property color primaryText: currentThemeData.primaryText
property color primaryContainer: currentThemeData.primaryContainer property color primaryContainer: currentThemeData.primaryContainer
property color secondary: currentThemeData.secondary property color secondary: currentThemeData.secondary
property color surface: currentThemeData.surface property color surface: {
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
return currentThemeData.background
}
return currentThemeData.surface
}
property color surfaceText: currentThemeData.surfaceText property color surfaceText: currentThemeData.surfaceText
property color surfaceVariant: currentThemeData.surfaceVariant property color surfaceVariant: currentThemeData.surfaceVariant
property color surfaceVariantText: currentThemeData.surfaceVariantText property color surfaceVariantText: currentThemeData.surfaceVariantText
@@ -163,8 +232,24 @@ Singleton {
property color backgroundText: currentThemeData.backgroundText property color backgroundText: currentThemeData.backgroundText
property color outline: currentThemeData.outline property color outline: currentThemeData.outline
property color outlineVariant: currentThemeData.outlineVariant || Qt.rgba(outline.r, outline.g, outline.b, 0.6) property color outlineVariant: currentThemeData.outlineVariant || Qt.rgba(outline.r, outline.g, outline.b, 0.6)
property color surfaceContainer: currentThemeData.surfaceContainer property color surfaceContainer: {
property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
return currentThemeData.surface
}
return currentThemeData.surfaceContainer
}
property color surfaceContainerHigh: {
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
return currentThemeData.surfaceContainer
}
return currentThemeData.surfaceContainerHigh
}
property color surfaceContainerHighest: {
if (typeof SettingsData !== "undefined" && SettingsData.surfaceBase === "s") {
return currentThemeData.surfaceContainerHigh
}
return currentThemeData.surfaceContainerHighest
}
property color onSurface: surfaceText property color onSurface: surfaceText
property color onSurfaceVariant: surfaceVariantText property color onSurfaceVariant: surfaceVariantText
@@ -209,11 +294,22 @@ Singleton {
property color shadowMedium: Qt.rgba(0, 0, 0, 0.08) property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
property color shadowStrong: Qt.rgba(0, 0, 0, 0.3) property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
property int shorterDuration: 100 readonly property var animationDurations: [
property int shortDuration: 150 { shorter: 0, short: 0, medium: 0, long: 0, extraLong: 0 },
property int mediumDuration: 300 { shorter: 50, short: 75, medium: 150, long: 250, extraLong: 500 },
property int longDuration: 500 { shorter: 100, short: 150, medium: 300, long: 500, extraLong: 1000 },
property int extraLongDuration: 1000 { shorter: 150, short: 225, medium: 450, long: 750, extraLong: 1500 },
{ shorter: 200, short: 300, medium: 600, long: 1000, extraLong: 2000 }
]
readonly property int currentAnimationSpeed: typeof SettingsData !== "undefined" ? SettingsData.animationSpeed : SettingsData.AnimationSpeed.Short
readonly property var currentDurations: animationDurations[currentAnimationSpeed] || animationDurations[SettingsData.AnimationSpeed.Short]
property int shorterDuration: currentDurations.shorter
property int shortDuration: currentDurations.short
property int mediumDuration: currentDurations.medium
property int longDuration: currentDurations.long
property int extraLongDuration: currentDurations.extraLong
property int standardEasing: Easing.OutCubic property int standardEasing: Easing.OutCubic
property int emphasizedEasing: Easing.OutQuart property int emphasizedEasing: Easing.OutQuart
@@ -233,8 +329,8 @@ Singleton {
property real iconSizeLarge: 32 property real iconSizeLarge: 32
property real panelTransparency: 0.85 property real panelTransparency: 0.85
property real widgetTransparency: typeof SettingsData !== "undefined" && SettingsData.topBarWidgetTransparency !== undefined ? SettingsData.topBarWidgetTransparency : 0.85 property real widgetTransparency: typeof SettingsData !== "undefined" && SettingsData.dankBarWidgetTransparency !== undefined ? SettingsData.dankBarWidgetTransparency : 1.0
property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 0.92 property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 1.0
function screenTransition() { function screenTransition() {
CompositorService.isNiri && NiriService.doScreenTransition() CompositorService.isNiri && NiriService.doScreenTransition()
@@ -243,11 +339,15 @@ Singleton {
function switchTheme(themeName, savePrefs = true, enableTransition = true) { function switchTheme(themeName, savePrefs = true, enableTransition = true) {
if (enableTransition) { if (enableTransition) {
screenTransition() screenTransition()
themeTransitionTimer.themeName = themeName
themeTransitionTimer.savePrefs = savePrefs
themeTransitionTimer.restart()
return
} }
if (themeName === dynamic) { if (themeName === dynamic) {
currentTheme = dynamic currentTheme = dynamic
currentThemeCategory = dynamic currentThemeCategory = dynamic
extractColors()
} else if (themeName === custom) { } else if (themeName === custom) {
currentTheme = custom currentTheme = custom
currentThemeCategory = custom currentThemeCategory = custom
@@ -256,34 +356,44 @@ Singleton {
} }
} else { } else {
currentTheme = themeName currentTheme = themeName
// Determine category based on theme name
if (StockThemes.isCatppuccinVariant(themeName)) { if (StockThemes.isCatppuccinVariant(themeName)) {
currentThemeCategory = "catppuccin" currentThemeCategory = "catppuccin"
} else { } else {
currentThemeCategory = "generic" currentThemeCategory = "generic"
} }
} }
if (savePrefs && typeof SettingsData !== "undefined") const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode)
SettingsData.setTheme(currentTheme) SettingsData.setTheme(currentTheme)
if (!isGreeterMode) {
generateSystemThemesFromCurrentTheme() generateSystemThemesFromCurrentTheme()
} }
}
function setLightMode(light, savePrefs = true) { function setLightMode(light, savePrefs = true, enableTransition = false) {
if (enableTransition) {
screenTransition() screenTransition()
isLightMode = light lightModeTransitionTimer.lightMode = light
if (savePrefs && typeof SessionData !== "undefined") lightModeTransitionTimer.savePrefs = savePrefs
SessionData.setLightMode(isLightMode) lightModeTransitionTimer.restart()
PortalService.setLightMode(isLightMode) return
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
SessionData.setLightMode(light)
if (!isGreeterMode) {
PortalService.setLightMode(light)
generateSystemThemesFromCurrentTheme() generateSystemThemesFromCurrentTheme()
} }
}
function toggleLightMode(savePrefs = true) { function toggleLightMode(savePrefs = true) {
setLightMode(!isLightMode, savePrefs) setLightMode(!isLightMode, savePrefs, true)
} }
function forceGenerateSystemThemes() { function forceGenerateSystemThemes() {
screenTransition()
if (!matugenAvailable) { if (!matugenAvailable) {
return return
} }
@@ -307,8 +417,10 @@ Singleton {
} }
function switchThemeCategory(category, defaultTheme) { function switchThemeCategory(category, defaultTheme) {
currentThemeCategory = category screenTransition()
switchTheme(defaultTheme, true, false) themeCategoryTransitionTimer.category = category
themeCategoryTransitionTimer.defaultTheme = defaultTheme
themeCategoryTransitionTimer.restart()
} }
function getCatppuccinColor(variantName) { function getCatppuccinColor(variantName) {
@@ -332,7 +444,6 @@ Singleton {
} }
function loadCustomTheme(themeData) { function loadCustomTheme(themeData) {
screenTransition()
if (themeData.dark || themeData.light) { if (themeData.dark || themeData.light) {
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark" const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light
@@ -367,7 +478,7 @@ Singleton {
property real notepadTransparency: SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : popupTransparency property real notepadTransparency: SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : popupTransparency
property var widgetBaseBackgroundColor: { property var widgetBaseBackgroundColor: {
const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sth" const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"
switch (colorMode) { switch (colorMode) {
case "s": case "s":
return surface return surface
@@ -388,7 +499,7 @@ Singleton {
} }
property var widgetBackground: { property var widgetBackground: {
const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sth" const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"
switch (colorMode) { switch (colorMode) {
case "s": case "s":
return Qt.rgba(surface.r, surface.g, surface.b, widgetTransparency) return Qt.rgba(surface.r, surface.g, surface.b, widgetTransparency)
@@ -414,6 +525,19 @@ Singleton {
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5 return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5
} }
function barIconSize(barThickness, offset) {
const defaultOffset = offset !== undefined ? offset : -6
return Math.round((barThickness / 48) * (iconSize + defaultOffset))
}
function barTextSize(barThickness) {
const scale = barThickness / 48
const dankBarScale = (typeof SettingsData !== "undefined" ? SettingsData.dankBarFontScale : 1.0)
if (scale <= 0.75) return fontSizeSmall * 0.9 * dankBarScale
if (scale >= 1.25) return fontSizeMedium * dankBarScale
return fontSizeSmall * dankBarScale
}
function getBatteryIcon(level, isCharging, batteryAvailable) { function getBatteryIcon(level, isCharging, batteryAvailable) {
if (!batteryAvailable) if (!batteryAvailable)
return _getBatteryPowerProfileIcon() return _getBatteryPowerProfileIcon()
@@ -504,23 +628,8 @@ Singleton {
} }
} }
function extractColors() {
extractionRequested = true
if (matugenAvailable)
if (rawWallpaperPath.startsWith("we:")) {
fileCheckerTimer.start()
} else {
fileChecker.running = true
}
else
matugenCheck.running = true
}
function onLightModeChanged() { function onLightModeChanged() {
if (matugenColors && Object.keys(matugenColors).length > 0) {
colorUpdateTrigger++
}
if (currentTheme === "custom" && customThemeFileView.path) { if (currentTheme === "custom" && customThemeFileView.path) {
customThemeFileView.reload() customThemeFileView.reload()
} }
@@ -528,10 +637,12 @@ Singleton {
function setDesiredTheme(kind, value, isLight, iconTheme, matugenType) { function setDesiredTheme(kind, value, isLight, iconTheme, matugenType) {
if (!matugenAvailable) { if (!matugenAvailable) {
console.warn("matugen not available or disabled - cannot set system theme") console.warn("Theme: matugen not available or disabled - cannot set system theme")
return return
} }
console.log("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", "type:", matugenType)
if (typeof NiriService !== "undefined" && CompositorService.isNiri) { if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
NiriService.suppressNextToast() NiriService.suppressNextToast()
} }
@@ -541,7 +652,9 @@ Singleton {
"value": value, "value": value,
"mode": isLight ? "light" : "dark", "mode": isLight ? "light" : "dark",
"iconTheme": iconTheme || "System Default", "iconTheme": iconTheme || "System Default",
"matugenType": matugenType || "scheme-tonal-spot" "matugenType": matugenType || "scheme-tonal-spot",
"surfaceBase": (typeof SettingsData !== "undefined" && SettingsData.surfaceBase) ? SettingsData.surfaceBase : "sc",
"runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true
} }
const json = JSON.stringify(desired) const json = JSON.stringify(desired)
@@ -550,19 +663,21 @@ Singleton {
Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`]) Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`])
workerRunning = true workerRunning = true
if (rawWallpaperPath.startsWith("we:")) { if (rawWallpaperPath.startsWith("we:")) {
console.log("calling matugen worker") console.log("Theme: Starting matugen worker (WE wallpaper)")
systemThemeGenerator.command = [ systemThemeGenerator.command = [
"sh", "-c", "sh", "-c",
`sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' --run` `sleep 1 && ${shellDir}/scripts/matugen-worker.sh '${stateDir}' '${shellDir}' '${configDir}' --run`
] ]
} else { } else {
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, "--run"] console.log("Theme: Starting matugen worker")
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, "--run"]
} }
systemThemeGenerator.running = true systemThemeGenerator.running = true
} }
function generateSystemThemesFromCurrentTheme() { function generateSystemThemesFromCurrentTheme() {
if (!matugenAvailable) const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!matugenAvailable || isGreeterMode)
return return
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
@@ -610,8 +725,17 @@ Singleton {
} }
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false" const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false"
gtkApplier.command = [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir] Proc.runCommand("gtkApplier", [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir], (output, exitCode) => {
gtkApplier.running = true if (exitCode === 0) {
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
ToastService.showInfo("GTK colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply GTK colors")
}
}
})
} }
function applyQtColors() { function applyQtColors() {
@@ -622,243 +746,83 @@ Singleton {
return return
} }
qtApplier.command = [shellDir + "/scripts/qt.sh", configDir] Proc.runCommand("qtApplier", [shellDir + "/scripts/qt.sh", configDir], (output, exitCode) => {
qtApplier.running = true if (exitCode === 0) {
} if (typeof ToastService !== "undefined") {
ToastService.showInfo("Qt colors applied successfully")
function extractJsonFromText(text) {
if (!text)
return null
const start = text.search(/[{\[]/)
if (start === -1)
return null
const open = text[start]
const pairs = {
"{": '}',
"[": ']'
}
const close = pairs[open]
if (!close)
return null
let inString = false
let escape = false
const stack = [open]
for (var i = start + 1; i < text.length; i++) {
const ch = text[i]
if (inString) {
if (escape) {
escape = false
} else if (ch === '\\') {
escape = true
} else if (ch === '"') {
inString = false
}
continue
}
if (ch === '"') {
inString = true
continue
}
if (ch === '{' || ch === '[') {
stack.push(ch)
continue
}
if (ch === '}' || ch === ']') {
const last = stack.pop()
if (!last || pairs[last] !== ch) {
return null
}
if (stack.length === 0) {
return text.slice(start, i + 1)
}
}
}
return null
}
Process {
id: matugenCheck
command: ["which", "matugen"]
onExited: code => {
matugenAvailable = (code === 0) && !envDisableMatugen
if (!matugenAvailable) {
console.log("matugen not not available in path or disabled via DMS_DISABLE_MATUGEN")
return
}
if (extractionRequested) {
if (rawWallpaperPath.startsWith("we:")) {
fileCheckerTimer.start()
} else {
fileChecker.running = true
}
}
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"
if (currentTheme === dynamic) {
if (wallpaperPath) {
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"
if (wallpaperPath.startsWith("#")) {
setDesiredTheme("hex", wallpaperPath, isLight, iconTheme, selectedMatugenType)
} else {
setDesiredTheme("image", wallpaperPath, isLight, iconTheme, selectedMatugenType)
}
} }
} else { } else {
let primaryColor if (typeof ToastService !== "undefined") {
let matugenType ToastService.showError("Failed to apply Qt colors")
if (currentTheme === "custom") {
if (customThemeData && customThemeData.primary) {
primaryColor = customThemeData.primary
matugenType = customThemeData.matugen_type
} }
} else { }
primaryColor = currentThemeData.primary })
matugenType = currentThemeData.matugen_type
} }
if (primaryColor) { function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"])
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType) function snap(value, dpr) {
} const s = dpr || 1
} return Math.round(value * s) / s
}
} }
Process { function px(value, dpr) {
id: fileChecker const s = dpr || 1
command: ["test", "-r", wallpaperPath] return Math.round(value * s) / s
onExited: code => {
if (code === 0) {
matugenProcess.running = true
} else if (wallpaperPath.startsWith("#")) {
colorMatugenProcess.running = true
}
}
} }
Timer { function hairline(dpr) {
id: fileCheckerTimer return 1 / (dpr || 1)
interval: 1000
repeat: false
onTriggered: {
fileChecker.running = true
}
} }
Process { function invertHex(hex) {
id: matugenProcess hex = hex.replace('#', '');
command: {
const scheme = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot" if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
return ["matugen", "image", wallpaperPath, "--json", "hex", "-t", scheme] return hex;
} }
stdout: StdioCollector { const r = parseInt(hex.substr(0, 2), 16);
id: matugenCollector const g = parseInt(hex.substr(2, 2), 16);
onStreamFinished: { const b = parseInt(hex.substr(4, 2), 16);
if (!matugenCollector.text) {
if (typeof ToastService !== "undefined") { const invR = (255 - r).toString(16).padStart(2, '0');
ToastService.wallpaperErrorStatus = "error" const invG = (255 - g).toString(16).padStart(2, '0');
ToastService.showError("Wallpaper Processing Failed: Empty JSON extracted from matugen output.") const invB = (255 - b).toString(16).padStart(2, '0');
}
return return `#${invR}${invG}${invB}`;
}
const extractedJson = extractJsonFromText(matugenCollector.text)
if (!extractedJson) {
if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Wallpaper Processing Failed: Invalid JSON extracted from matugen output.")
}
console.log("Raw matugen output:", matugenCollector.text)
return
}
try {
root.matugenColors = JSON.parse(extractedJson)
root.colorUpdateTrigger++
if (typeof ToastService !== "undefined") {
ToastService.clearWallpaperError()
}
} catch (e) {
if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Wallpaper processing failed (JSON parse error after extraction)")
}
}
}
} }
onExited: code => { property string baseLogoColor: {
if (code !== 0) { if (typeof SettingsData === "undefined") return ""
if (typeof ToastService !== "undefined") { const colorOverride = SettingsData.launcherLogoColorOverride
ToastService.wallpaperErrorStatus = "error" if (!colorOverride || colorOverride === "") return ""
ToastService.showError("Matugen command failed with exit code " + code) if (colorOverride === "primary") return primary
} if (colorOverride === "surface") return surfaceText
} return colorOverride
}
} }
Process { property string effectiveLogoColor: {
id: colorMatugenProcess if (typeof SettingsData === "undefined") return ""
command: {
const scheme = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot" const colorOverride = SettingsData.launcherLogoColorOverride
return ["matugen", "color", "hex", wallpaperPath, "--json", "hex", "-t", scheme] if (!colorOverride || colorOverride === "") return ""
if (colorOverride === "primary") return primary
if (colorOverride === "surface") return surfaceText
if (!SettingsData.launcherLogoColorInvertOnMode) {
return colorOverride
} }
stdout: StdioCollector { if (isLightMode) {
id: colorMatugenCollector return invertHex(colorOverride)
onStreamFinished: {
if (!colorMatugenCollector.text) {
if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Color Processing Failed: Empty JSON extracted from matugen output.")
}
return
}
const extractedJson = extractJsonFromText(colorMatugenCollector.text)
if (!extractedJson) {
if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Color Processing Failed: Invalid JSON extracted from matugen output.")
}
console.log("Raw matugen output:", colorMatugenCollector.text)
return
}
try {
root.matugenColors = JSON.parse(extractedJson)
root.colorUpdateTrigger++
if (typeof ToastService !== "undefined") {
ToastService.clearWallpaperError()
}
} catch (e) {
if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Color processing failed (JSON parse error after extraction)")
}
}
}
} }
onExited: code => { return colorOverride
if (code !== 0) {
if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Matugen color command failed with exit code " + code)
}
}
}
} }
Process {
id: ensureStateDir
}
Process { Process {
id: systemThemeGenerator id: systemThemeGenerator
@@ -867,64 +831,19 @@ Singleton {
onExited: exitCode => { onExited: exitCode => {
workerRunning = false workerRunning = false
if (exitCode === 2) { if (exitCode === 0) {
// Exit code 2 means wallpaper/color not found - this is expected on first run console.log("Theme: Matugen worker completed successfully")
console.log("Theme worker: wallpaper/color not found, skipping theme generation") if (currentTheme === dynamic) {
} else if (exitCode !== 0) { console.log("Theme: Reloading dynamic colors file")
dynamicColorsFileView.reload()
}
} else if (exitCode === 2) {
console.log("Theme: Matugen worker completed with code 2 (no changes needed)")
} else {
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.showError("Theme worker failed (" + exitCode + ")") ToastService.showError("Theme worker failed (" + exitCode + ")")
} }
console.warn("Theme worker failed with exit code:", exitCode) console.warn("Theme: Matugen worker failed with exit code:", exitCode)
}
}
}
Process {
id: gtkApplier
running: false
stdout: StdioCollector {
id: gtkStdout
}
stderr: StdioCollector {
id: gtkStderr
}
onExited: exitCode => {
if (exitCode === 0) {
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
ToastService.showInfo("GTK colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply GTK colors: " + gtkStderr.text)
}
}
}
}
Process {
id: qtApplier
running: false
stdout: StdioCollector {
id: qtStdout
}
stderr: StdioCollector {
id: qtStderr
}
onExited: exitCode => {
if (exitCode === 0) {
if (typeof ToastService !== "undefined") {
ToastService.showInfo("Qt colors applied successfully")
}
} else {
if (typeof ToastService !== "undefined") {
ToastService.showError("Failed to apply Qt colors: " + qtStderr.text)
}
} }
} }
} }
@@ -957,21 +876,81 @@ Singleton {
} }
} }
FileView {
id: dynamicColorsFileView
path: {
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
const colorsPath = SessionData.isGreeterMode
? greetCfgDir + "/colors.json"
: stateDir + "/dms-colors.json"
return colorsPath
}
watchChanges: currentTheme === dynamic && !SessionData.isGreeterMode
function parseAndLoadColors() {
try {
const colorsText = dynamicColorsFileView.text()
if (colorsText) {
root.matugenColors = JSON.parse(colorsText)
if (typeof ToastService !== "undefined") {
ToastService.clearWallpaperError()
}
}
} catch (e) {
console.error("Theme: Failed to parse dynamic colors:", e)
if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Dynamic colors parse error: " + e.message)
}
}
}
onLoaded: {
if (currentTheme === dynamic) {
console.log("Theme: Dynamic colors file loaded successfully")
colorsFileLoadFailed = false
parseAndLoadColors()
}
}
onFileChanged: {
if (currentTheme === dynamic) {
dynamicColorsFileView.reload()
}
}
onLoadFailed: function (error) {
if (currentTheme === dynamic) {
console.log("Theme: Dynamic colors file load failed, marking for regeneration")
colorsFileLoadFailed = true
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!isGreeterMode && matugenAvailable && wallpaperPath) {
console.log("Theme: Matugen available, triggering immediate regeneration")
generateSystemThemesFromCurrentTheme()
}
}
}
onPathChanged: {
colorsFileLoadFailed = false
}
}
IpcHandler { IpcHandler {
target: "theme" target: "theme"
function toggle(): string { function toggle(): string {
root.toggleLightMode() root.toggleLightMode()
return root.isLightMode ? "light" : "dark" return root.isLightMode ? "dark" : "light"
} }
function light(): string { function light(): string {
root.setLightMode(true) root.setLightMode(true, true, true)
return "light" return "light"
} }
function dark(): string { function dark(): string {
root.setLightMode(false) root.setLightMode(false, true, true)
return "dark" return "dark"
} }
@@ -979,4 +958,35 @@ Singleton {
return root.isLightMode ? "light" : "dark" return root.isLightMode ? "light" : "dark"
} }
} }
// These timers are for screen transitions, since sometimes QML still beats the niri call
Timer {
id: themeTransitionTimer
interval: 50
repeat: false
property string themeName: ""
property bool savePrefs: true
onTriggered: root.switchTheme(themeName, savePrefs, false)
}
Timer {
id: lightModeTransitionTimer
interval: 100
repeat: false
property bool lightMode: false
property bool savePrefs: true
onTriggered: root.setLightMode(lightMode, savePrefs, false)
}
Timer {
id: themeCategoryTransitionTimer
interval: 50
repeat: false
property string category: ""
property string defaultTheme: ""
onTriggered: {
root.currentThemeCategory = category
root.switchTheme(defaultTheme, true, false)
}
}
} }

29
DMSGreeter.qml Normal file
View File

@@ -0,0 +1,29 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
import qs.Common
import qs.Modules.Greetd
Item {
id: root
WlSessionLock {
id: sessionLock
locked: false
Component.onCompleted: {
Qt.callLater(() => { locked = true })
}
onLockedChanged: {
if (!locked) {
console.log("Greetd session unlocked, exiting")
}
}
GreeterSurface {
lock: sessionLock
}
}
}

539
DMSShell.qml Normal file
View File

@@ -0,0 +1,539 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals
import qs.Modals.Clipboard
import qs.Modals.Common
import qs.Modals.Settings
import qs.Modals.Spotlight
import qs.Modules
import qs.Modules.AppDrawer
import qs.Modules.DankDash
import qs.Modules.ControlCenter
import qs.Modules.Dock
import qs.Modules.Lock
import qs.Modules.Notepad
import qs.Modules.Notifications.Center
import qs.Widgets
import qs.Modules.Notifications.Popup
import qs.Modules.OSD
import qs.Modules.ProcessList
import qs.Modules.Settings
import qs.Modules.DankBar
import qs.Modules.DankBar.Popouts
import qs.Modules.Plugins
import qs.Services
Item {
id: root
Instantiator {
id: daemonPluginInstantiator
asynchronous: true
model: Object.keys(PluginService.pluginDaemonComponents)
delegate: Loader {
id: daemonLoader
property string pluginId: modelData
sourceComponent: PluginService.pluginDaemonComponents[pluginId]
onLoaded: {
if (item) {
item.pluginService = PluginService
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
item.pluginId = pluginId
console.log("Daemon plugin loaded:", pluginId)
}
}
}
}
WallpaperBackground {}
Lock {
id: lock
}
Loader {
id: dankBarLoader
asynchronous: false
property var currentPosition: SettingsData.dankBarPosition
property bool initialized: false
sourceComponent: DankBar {
onColorPickerRequested: {
if (colorPickerModal.shouldBeVisible) {
colorPickerModal.close()
} else {
colorPickerModal.show()
}
}
}
Component.onCompleted: {
initialized = true
}
onCurrentPositionChanged: {
if (!initialized) return
const component = sourceComponent
sourceComponent = null
sourceComponent = component
}
}
Loader {
id: dockLoader
active: true
asynchronous: false
property var currentPosition: SettingsData.dockPosition
property bool initialized: false
sourceComponent: Dock {
contextMenu: dockContextMenuLoader.item ? dockContextMenuLoader.item : null
}
onLoaded: {
if (item) {
dockContextMenuLoader.active = true
}
}
Component.onCompleted: {
initialized = true
}
onCurrentPositionChanged: {
if (!initialized) return
console.log("DEBUG: Dock position changed to:", currentPosition, "- recreating dock")
const comp = sourceComponent
sourceComponent = null
sourceComponent = comp
}
}
Loader {
id: dankDashPopoutLoader
active: false
asynchronous: true
sourceComponent: Component {
DankDashPopout {
id: dankDashPopout
Component.onCompleted: {
PopoutService.dankDashPopout = dankDashPopout
}
}
}
}
LazyLoader {
id: dockContextMenuLoader
active: false
DockContextMenu {
id: dockContextMenu
}
}
LazyLoader {
id: notificationCenterLoader
active: false
NotificationCenterPopout {
id: notificationCenter
Component.onCompleted: {
PopoutService.notificationCenterPopout = notificationCenter
}
}
}
Variants {
model: SettingsData.getFilteredScreens("notifications")
delegate: NotificationPopupManager {
modelData: item
}
}
LazyLoader {
id: controlCenterLoader
active: false
property var modalRef: colorPickerModal
property LazyLoader powerModalLoaderRef: powerMenuModalLoader
ControlCenterPopout {
id: controlCenterPopout
colorPickerModal: controlCenterLoader.modalRef
powerMenuModalLoader: controlCenterLoader.powerModalLoaderRef
onLockRequested: {
lock.activate()
}
Component.onCompleted: {
PopoutService.controlCenterPopout = controlCenterPopout
}
}
}
LazyLoader {
id: wifiPasswordModalLoader
active: false
WifiPasswordModal {
id: wifiPasswordModal
Component.onCompleted: {
PopoutService.wifiPasswordModal = wifiPasswordModal
}
}
}
LazyLoader {
id: networkInfoModalLoader
active: false
NetworkInfoModal {
id: networkInfoModal
Component.onCompleted: {
PopoutService.networkInfoModal = networkInfoModal
}
}
}
LazyLoader {
id: batteryPopoutLoader
active: false
BatteryPopout {
id: batteryPopout
Component.onCompleted: {
PopoutService.batteryPopout = batteryPopout
}
}
}
LazyLoader {
id: vpnPopoutLoader
active: false
VpnPopout {
id: vpnPopout
Component.onCompleted: {
PopoutService.vpnPopout = vpnPopout
}
}
}
LazyLoader {
id: powerMenuLoader
active: false
PowerMenu {
id: powerMenu
onPowerActionRequested: (action, title, message) => {
if (SettingsData.powerActionConfirm) {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, () => actionApply(action), function () {})
}
} else {
actionApply(action)
}
}
function actionApply(action) {
switch (action) {
case "logout":
SessionService.logout()
break
case "suspend":
SessionService.suspend()
break
case "hibernate":
SessionService.hibernate()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}
}
}
LazyLoader {
id: powerConfirmModalLoader
active: false
ConfirmModal {
id: powerConfirmModal
}
}
LazyLoader {
id: processListPopoutLoader
active: false
ProcessListPopout {
id: processListPopout
Component.onCompleted: {
PopoutService.processListPopout = processListPopout
}
}
}
SettingsModal {
id: settingsModal
Component.onCompleted: {
PopoutService.settingsModal = settingsModal
}
}
LazyLoader {
id: appDrawerLoader
active: false
AppDrawerPopout {
id: appDrawerPopout
Component.onCompleted: {
PopoutService.appDrawerPopout = appDrawerPopout
}
}
}
SpotlightModal {
id: spotlightModal
Component.onCompleted: {
PopoutService.spotlightModal = spotlightModal
}
}
ClipboardHistoryModal {
id: clipboardHistoryModalPopup
Component.onCompleted: {
PopoutService.clipboardHistoryModal = clipboardHistoryModalPopup
}
}
NotificationModal {
id: notificationModal
Component.onCompleted: {
PopoutService.notificationModal = notificationModal
}
}
DankColorPickerModal {
id: colorPickerModal
Component.onCompleted: {
PopoutService.colorPickerModal = colorPickerModal
}
}
LazyLoader {
id: processListModalLoader
active: false
ProcessListModal {
id: processListModal
Component.onCompleted: {
PopoutService.processListModal = processListModal
}
}
}
LazyLoader {
id: systemUpdateLoader
active: false
SystemUpdatePopout {
id: systemUpdatePopout
Component.onCompleted: {
PopoutService.systemUpdatePopout = systemUpdatePopout
}
}
}
Variants {
id: notepadSlideoutVariants
model: SettingsData.getFilteredScreens("notepad")
delegate: DankSlideout {
id: notepadSlideout
modelData: item
title: I18n.tr("Notepad")
slideoutWidth: 480
expandable: true
expandedWidthValue: 960
customTransparency: SettingsData.notepadTransparencyOverride
content: Component {
Notepad {
onHideRequested: {
notepadSlideout.hide()
}
}
}
function toggle() {
if (isVisible) {
hide()
} else {
show()
}
}
}
}
LazyLoader {
id: powerMenuModalLoader
active: false
PowerMenuModal {
id: powerMenuModal
onPowerActionRequested: (action, title, message) => {
if (SettingsData.powerActionConfirm) {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, () => actionApply(action), function () {})
}
} else {
actionApply(action)
}
}
function actionApply(action) {
switch (action) {
case "logout":
SessionService.logout()
break
case "suspend":
SessionService.suspend()
break
case "hibernate":
SessionService.hibernate()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}
Component.onCompleted: {
PopoutService.powerMenuModal = powerMenuModal
}
}
}
LazyLoader {
id: hyprKeybindsModalLoader
active: false
HyprKeybindsModal {
id: hyprKeybindsModal
Component.onCompleted: {
PopoutService.hyprKeybindsModal = hyprKeybindsModal
}
}
}
DMSShellIPC {
powerMenuModalLoader: powerMenuModalLoader
processListModalLoader: processListModalLoader
controlCenterLoader: controlCenterLoader
dankDashPopoutLoader: dankDashPopoutLoader
notepadSlideoutVariants: notepadSlideoutVariants
hyprKeybindsModalLoader: hyprKeybindsModalLoader
}
Variants {
model: SettingsData.getFilteredScreens("toast")
delegate: Toast {
modelData: item
visible: ToastService.toastVisible
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: VolumeOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: MicMuteOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: BrightnessOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: IdleInhibitorOSD {
modelData: item
}
}
}

353
DMSShellIPC.qml Normal file
View File

@@ -0,0 +1,353 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import qs.Common
import qs.Services
Item {
id: root
required property var powerMenuModalLoader
required property var processListModalLoader
required property var controlCenterLoader
required property var dankDashPopoutLoader
required property var notepadSlideoutVariants
required property var hyprKeybindsModalLoader
IpcHandler {
function open() {
root.powerMenuModalLoader.active = true
if (root.powerMenuModalLoader.item)
root.powerMenuModalLoader.item.openCentered()
return "POWERMENU_OPEN_SUCCESS"
}
function close() {
if (root.powerMenuModalLoader.item)
root.powerMenuModalLoader.item.close()
return "POWERMENU_CLOSE_SUCCESS"
}
function toggle() {
root.powerMenuModalLoader.active = true
if (root.powerMenuModalLoader.item) {
if (root.powerMenuModalLoader.item.shouldBeVisible) {
root.powerMenuModalLoader.item.close()
} else {
root.powerMenuModalLoader.item.openCentered()
}
}
return "POWERMENU_TOGGLE_SUCCESS"
}
target: "powermenu"
}
IpcHandler {
function open(): string {
root.processListModalLoader.active = true
if (root.processListModalLoader.item)
root.processListModalLoader.item.show()
return "PROCESSLIST_OPEN_SUCCESS"
}
function close(): string {
if (root.processListModalLoader.item)
root.processListModalLoader.item.hide()
return "PROCESSLIST_CLOSE_SUCCESS"
}
function toggle(): string {
root.processListModalLoader.active = true
if (root.processListModalLoader.item)
root.processListModalLoader.item.toggle()
return "PROCESSLIST_TOGGLE_SUCCESS"
}
target: "processlist"
}
IpcHandler {
function open(): string {
root.controlCenterLoader.active = true
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.open()
return "CONTROL_CENTER_OPEN_SUCCESS"
}
return "CONTROL_CENTER_OPEN_FAILED"
}
function close(): string {
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.close()
return "CONTROL_CENTER_CLOSE_SUCCESS"
}
return "CONTROL_CENTER_CLOSE_FAILED"
}
function toggle(): string {
root.controlCenterLoader.active = true
if (root.controlCenterLoader.item) {
root.controlCenterLoader.item.toggle()
return "CONTROL_CENTER_TOGGLE_SUCCESS"
}
return "CONTROL_CENTER_TOGGLE_FAILED"
}
target: "control-center"
}
IpcHandler {
function open(tab: string): string {
root.dankDashPopoutLoader.active = true
if (root.dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0
break
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
root.dankDashPopoutLoader.item.dashVisible = true
return "DASH_OPEN_SUCCESS"
}
return "DASH_OPEN_FAILED"
}
function close(): string {
if (root.dankDashPopoutLoader.item) {
root.dankDashPopoutLoader.item.dashVisible = false
return "DASH_CLOSE_SUCCESS"
}
return "DASH_CLOSE_FAILED"
}
function toggle(tab: string): string {
root.dankDashPopoutLoader.active = true
if (root.dankDashPopoutLoader.item) {
if (root.dankDashPopoutLoader.item.dashVisible) {
root.dankDashPopoutLoader.item.dashVisible = false
} else {
switch (tab.toLowerCase()) {
case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
root.dankDashPopoutLoader.item.currentTabIndex = 0
break
}
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
root.dankDashPopoutLoader.item.dashVisible = true
}
return "DASH_TOGGLE_SUCCESS"
}
return "DASH_TOGGLE_FAILED"
}
target: "dash"
}
IpcHandler {
function getFocusedScreenName() {
if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) {
return Hyprland.focusedWorkspace.monitor.name
}
if (CompositorService.isNiri && NiriService.currentOutput) {
return NiriService.currentOutput
}
return ""
}
function getActiveNotepadInstance() {
if (root.notepadSlideoutVariants.instances.length === 0) {
return null
}
if (root.notepadSlideoutVariants.instances.length === 1) {
return root.notepadSlideoutVariants.instances[0]
}
var focusedScreen = getFocusedScreenName()
if (focusedScreen && root.notepadSlideoutVariants.instances.length > 0) {
for (var i = 0; i < root.notepadSlideoutVariants.instances.length; i++) {
var slideout = root.notepadSlideoutVariants.instances[i]
if (slideout.modelData && slideout.modelData.name === focusedScreen) {
return slideout
}
}
}
for (var i = 0; i < root.notepadSlideoutVariants.instances.length; i++) {
var slideout = root.notepadSlideoutVariants.instances[i]
if (slideout.isVisible) {
return slideout
}
}
return root.notepadSlideoutVariants.instances[0]
}
function open(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.show()
return "NOTEPAD_OPEN_SUCCESS"
}
return "NOTEPAD_OPEN_FAILED"
}
function close(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.hide()
return "NOTEPAD_CLOSE_SUCCESS"
}
return "NOTEPAD_CLOSE_FAILED"
}
function toggle(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.toggle()
return "NOTEPAD_TOGGLE_SUCCESS"
}
return "NOTEPAD_TOGGLE_FAILED"
}
target: "notepad"
}
IpcHandler {
function toggle(): string {
SessionService.toggleIdleInhibit()
return SessionService.idleInhibited ? "Idle inhibit enabled" : "Idle inhibit disabled"
}
function enable(): string {
SessionService.enableIdleInhibit()
return "Idle inhibit enabled"
}
function disable(): string {
SessionService.disableIdleInhibit()
return "Idle inhibit disabled"
}
function status(): string {
return SessionService.idleInhibited ? "Idle inhibit is enabled" : "Idle inhibit is disabled"
}
function reason(newReason: string): string {
if (!newReason) {
return `Current reason: ${SessionService.inhibitReason}`
}
SessionService.setInhibitReason(newReason)
return `Inhibit reason set to: ${newReason}`
}
target: "inhibit"
}
IpcHandler {
function list(): string {
return MprisController.availablePlayers.map(p => p.identity).join("\n")
}
function play(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canPlay) {
MprisController.activePlayer.play()
}
}
function pause(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canPause) {
MprisController.activePlayer.pause()
}
}
function playPause(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canTogglePlaying) {
MprisController.activePlayer.togglePlaying()
}
}
function previous(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canGoPrevious) {
MprisController.activePlayer.previous()
}
}
function next(): void {
if (MprisController.activePlayer && MprisController.activePlayer.canGoNext) {
MprisController.activePlayer.next()
}
}
function stop(): void {
if (MprisController.activePlayer) {
MprisController.activePlayer.stop()
}
}
target: "mpris"
}
IpcHandler {
function openBinds(): string {
if (!CompositorService.isHyprland) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprKeybindsModalLoader.active = true
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.open()
return "HYPR_KEYBINDS_OPEN_SUCCESS"
}
return "HYPR_KEYBINDS_OPEN_FAILED"
}
function closeBinds(): string {
if (!CompositorService.isHyprland) {
return "HYPR_NOT_AVAILABLE"
}
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.close()
return "HYPR_KEYBINDS_CLOSE_SUCCESS"
}
return "HYPR_KEYBINDS_CLOSE_FAILED"
}
function toggleBinds(): string {
if (!CompositorService.isHyprland) {
return "HYPR_NOT_AVAILABLE"
}
root.hyprKeybindsModalLoader.active = true
if (root.hyprKeybindsModalLoader.item) {
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
root.hyprKeybindsModalLoader.item.close()
} else {
root.hyprKeybindsModalLoader.item.open()
}
return "HYPR_KEYBINDS_TOGGLE_SUCCESS"
}
return "HYPR_KEYBINDS_TOGGLE_FAILED"
}
target: "hypr"
}
}

View File

@@ -30,7 +30,7 @@ Item {
showKeyboardHints: modal.showKeyboardHints showKeyboardHints: modal.showKeyboardHints
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
onClearAllClicked: { onClearAllClicked: {
clearConfirmDialog.show("Clear All History?", "This will permanently delete all clipboard history.", function () { clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () {
modal.clearAll() modal.clearAll()
modal.hide() modal.hide()
}, function () {}) }, function () {})
@@ -46,7 +46,7 @@ Item {
leftIconName: "search" leftIconName: "search"
showClearButton: true showClearButton: true
focus: true focus: true
ignoreLeftRightKeys: true ignoreTabKeys: true
keyForwardTargets: [modal.modalFocusScope] keyForwardTargets: [modal.modalFocusScope]
onTextChanged: { onTextChanged: {
modal.searchText = text modal.searchText = text
@@ -77,15 +77,12 @@ Item {
width: parent.width width: parent.width
height: parent.height - ClipboardConstants.headerHeight - 70 height: parent.height - ClipboardConstants.headerHeight - 70
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: "transparent"
border.color: Theme.outlineLight
border.width: 1
clip: true clip: true
DankListView { DankListView {
id: clipboardListView id: clipboardListView
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS
model: filteredModel model: filteredModel
currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0 currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0
@@ -119,7 +116,7 @@ Item {
} }
StyledText { StyledText {
text: "No clipboard entries found" text: I18n.tr("No clipboard entries found")
anchors.centerIn: parent anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText

View File

@@ -24,17 +24,10 @@ Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (isSelected) { if (isSelected) {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2) return Theme.primaryPressed
} }
return mouseArea.containsMouse ? Theme.primaryHover : Theme.primaryBackground return mouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
} }
border.color: {
if (isSelected) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5)
}
return Theme.outlineStrong
}
border.width: isSelected ? 1.5 : 1
Row { Row {
anchors.fill: parent anchors.fill: parent
@@ -87,11 +80,11 @@ Rectangle {
text: { text: {
switch (entryType) { switch (entryType) {
case "image": case "image":
return "Image • " + entryPreview return I18n.tr("Image") + " • " + entryPreview
case "long_text": case "long_text":
return "Long Text" return I18n.tr("Long Text")
default: default:
return "Text" return I18n.tr("Text")
} }
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall

View File

@@ -28,7 +28,7 @@ Item {
} }
StyledText { StyledText {
text: `Clipboard History (${totalCount})` text: I18n.tr("Clipboard History") + ` (${totalCount})`
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -60,6 +60,7 @@ DankModal {
open() open()
clipboardHistoryModal.searchText = "" clipboardHistoryModal.searchText = ""
clipboardHistoryModal.activeImageLoads = 0 clipboardHistoryModal.activeImageLoads = 0
clipboardHistoryModal.shouldHaveFocus = true
refreshClipboard() refreshClipboard()
keyboardController.reset() keyboardController.reset()
@@ -91,7 +92,7 @@ DankModal {
function copyEntry(entry) { function copyEntry(entry) {
const entryId = entry.split('\t')[0] const entryId = entry.split('\t')[0]
Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`]) Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`])
ToastService.showInfo("Copied to clipboard") ToastService.showInfo(I18n.tr("Copied to clipboard"))
hide() hide()
} }
@@ -153,7 +154,7 @@ DankModal {
ConfirmModal { ConfirmModal {
id: clearConfirmDialog id: clearConfirmDialog
confirmButtonText: "Clear All" confirmButtonText: I18n.tr("Clear All")
confirmButtonColor: Theme.primary confirmButtonColor: Theme.primary
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {

View File

@@ -53,7 +53,7 @@ QtObject {
modal.hide() modal.hide()
event.accepted = true event.accepted = true
} }
} else if (event.key === Qt.Key_Down) { } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) {
if (!modal.keyboardNavigationActive) { if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true modal.keyboardNavigationActive = true
modal.selectedIndex = 0 modal.selectedIndex = 0
@@ -62,7 +62,7 @@ QtObject {
selectNext() selectNext()
event.accepted = true event.accepted = true
} }
} else if (event.key === Qt.Key_Up) { } else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) {
if (!modal.keyboardNavigationActive) { if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true modal.keyboardNavigationActive = true
modal.selectedIndex = 0 modal.selectedIndex = 0
@@ -74,6 +74,42 @@ QtObject {
selectPrevious() selectPrevious()
event.accepted = true event.accepted = true
} }
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true
modal.selectedIndex = 0
} else {
selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true
modal.selectedIndex = 0
} else if (modal.selectedIndex === 0) {
modal.keyboardNavigationActive = false
} else {
selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true
modal.selectedIndex = 0
} else {
selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true
modal.selectedIndex = 0
} else if (modal.selectedIndex === 0) {
modal.keyboardNavigationActive = false
} else {
selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_Delete && (event.modifiers & Qt.ShiftModifier)) { } else if (event.key === Qt.Key_Delete && (event.modifiers & Qt.ShiftModifier)) {
modal.clearAll() modal.clearAll()
modal.hide() modal.hide()

View File

@@ -6,6 +6,8 @@ import qs.Modals.Clipboard
Rectangle { Rectangle {
id: keyboardHints id: keyboardHints
readonly property string hintsText: I18n.tr("Shift+Del: Clear All • Esc: Close")
height: ClipboardConstants.keyboardHintsHeight height: ClipboardConstants.keyboardHintsHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
@@ -26,7 +28,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "Shift+Del: Clear All • Esc: Close" text: keyboardHints.hintsText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -1,37 +0,0 @@
import QtQuick
import Qt.labs.platform
import Quickshell
import qs.Common
import qs.Services
Item {
id: colorPickerModal
signal colorSelected(color selectedColor)
function show() {
colorDialog.open()
}
function hide() {
colorDialog.close()
}
function copyColorToClipboard(colorValue) {
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
ToastService.showInfo(`Color ${colorValue} copied to clipboard`)
console.log("Copied color to clipboard:", colorValue)
}
ColorDialog {
id: colorDialog
title: "Color Picker - Select and copy color"
color: Theme.primary
onAccepted: {
const colorString = color.toString()
copyColorToClipboard(colorString)
colorSelected(color)
}
}
}

View File

@@ -68,9 +68,11 @@ DankModal {
} }
} }
onOpened: { onOpened: {
Qt.callLater(function () {
modalFocusScope.forceActiveFocus() modalFocusScope.forceActiveFocus()
modalFocusScope.focus = true modalFocusScope.focus = true
shouldHaveFocus = true shouldHaveFocus = true
})
} }
modalFocusScope.Keys.onPressed: function (event) { modalFocusScope.Keys.onPressed: function (event) {
switch (event.key) { switch (event.key) {
@@ -93,6 +95,48 @@ DankModal {
selectedButton = 1 selectedButton = 1
event.accepted = true event.accepted = true
break break
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = (selectedButton + 1) % 2
event.accepted = true
}
break
case Qt.Key_P:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2
event.accepted = true
}
break
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = 1
event.accepted = true
}
break
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = 0
event.accepted = true
}
break
case Qt.Key_H:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = 0
event.accepted = true
}
break
case Qt.Key_L:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = 1
event.accepted = true
}
break
case Qt.Key_Tab: case Qt.Key_Tab:
keyboardNavigation = true keyboardNavigation = true
selectedButton = selectedButton === -1 ? 0 : (selectedButton + 1) % 2 selectedButton = selectedButton === -1 ? 0 : (selectedButton + 1) % 2

View File

@@ -1,8 +1,9 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services
PanelWindow { PanelWindow {
id: root id: root
@@ -11,10 +12,22 @@ PanelWindow {
property alias content: contentLoader.sourceComponent property alias content: contentLoader.sourceComponent
property alias contentLoader: contentLoader property alias contentLoader: contentLoader
property Item directContent: null
property real width: 400 property real width: 400
property real height: 300 property real height: 300
readonly property real screenWidth: screen ? screen.width : 1920 readonly property real screenWidth: screen ? screen.width : 1920
readonly property real screenHeight: screen ? screen.height : 1080 readonly property real screenHeight: screen ? screen.height : 1080
readonly property real dpr: {
if (CompositorService.isNiri && screen) {
const niriScale = NiriService.displayScales[screen.name]
if (niriScale !== undefined) return niriScale
}
if (CompositorService.isHyprland && screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === screen.name)
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return (screen?.devicePixelRatio) || 1
}
property bool showBackground: true property bool showBackground: true
property real backgroundOpacity: 0.5 property real backgroundOpacity: 0.5
property string positioning: "center" property string positioning: "center"
@@ -22,7 +35,7 @@ PanelWindow {
property bool closeOnEscapeKey: true property bool closeOnEscapeKey: true
property bool closeOnBackgroundClick: true property bool closeOnBackgroundClick: true
property string animationType: "scale" property string animationType: "scale"
property int animationDuration: Theme.shorterDuration property int animationDuration: Theme.shortDuration
property var animationEasing: Theme.emphasizedEasing property var animationEasing: Theme.emphasizedEasing
property color backgroundColor: Theme.surfaceContainer property color backgroundColor: Theme.surfaceContainer
property color borderColor: Theme.outlineMedium property color borderColor: Theme.outlineMedium
@@ -34,6 +47,7 @@ PanelWindow {
property bool shouldHaveFocus: shouldBeVisible property bool shouldHaveFocus: shouldBeVisible
property bool allowFocusOverride: false property bool allowFocusOverride: false
property bool allowStacking: false property bool allowStacking: false
property bool keepContentLoaded: false
signal opened signal opened
signal dialogClosed signal dialogClosed
@@ -90,7 +104,7 @@ PanelWindow {
Timer { Timer {
id: closeTimer id: closeTimer
interval: animationDuration + 50 interval: animationDuration + 100
onTriggered: { onTriggered: {
visible = false visible = false
} }
@@ -133,22 +147,26 @@ PanelWindow {
Rectangle { Rectangle {
id: contentContainer id: contentContainer
width: root.width width: Theme.px(root.width, dpr)
height: root.height height: Theme.px(root.height, dpr)
anchors.centerIn: positioning === "center" ? parent : undefined anchors.centerIn: undefined
x: { x: {
if (positioning === "top-right") { if (positioning === "center") {
return Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL) return Theme.snap((root.screenWidth - width) / 2, dpr)
} else if (positioning === "top-right") {
return Theme.px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL), dpr)
} else if (positioning === "custom") { } else if (positioning === "custom") {
return root.customPosition.x return Theme.snap(root.customPosition.x, dpr)
} }
return 0 return 0
} }
y: { y: {
if (positioning === "top-right") { if (positioning === "center") {
return Theme.barHeight + Theme.spacingXS return Theme.snap((root.screenHeight - height) / 2, dpr)
} else if (positioning === "top-right") {
return Theme.px(Theme.barHeight + Theme.spacingXS, dpr)
} else if (positioning === "custom") { } else if (positioning === "custom") {
return root.customPosition.y return Theme.snap(root.customPosition.y, dpr)
} }
return 0 return 0
} }
@@ -156,50 +174,79 @@ PanelWindow {
radius: root.cornerRadius radius: root.cornerRadius
border.color: root.borderColor border.color: root.borderColor
border.width: root.borderWidth border.width: root.borderWidth
layer.enabled: root.enableShadow clip: false
layer.enabled: true
opacity: root.shouldBeVisible ? 1 : 0 opacity: root.shouldBeVisible ? 1 : 0
scale: root.animationType === "scale" ? (root.shouldBeVisible ? 1 : 0.9) : 1
transform: root.animationType === "slide" ? slideTransform : null transform: root.animationType === "slide" ? slideTransform : null
Translate { Translate {
id: slideTransform id: slideTransform
x: root.shouldBeVisible ? 0 : 15 readonly property real rawX: root.shouldBeVisible ? 0 : 15
y: root.shouldBeVisible ? 0 : -30 readonly property real rawY: root.shouldBeVisible ? 0 : -30
x: Theme.snap(rawX, root.dpr)
y: Theme.snap(rawY, root.dpr)
}
Behavior on opacity {
NumberAnimation {
duration: animationDuration
easing.type: animationEasing
}
}
FocusScope {
anchors.fill: parent
focus: root.shouldBeVisible
clip: false
Item {
id: directContentWrapper
anchors.fill: parent
visible: root.directContent !== null
focus: true
clip: false
Component.onCompleted: {
if (root.directContent) {
root.directContent.parent = directContentWrapper
root.directContent.anchors.fill = directContentWrapper
Qt.callLater(() => root.directContent.forceActiveFocus())
}
}
Connections {
function onDirectContentChanged() {
if (root.directContent) {
root.directContent.parent = directContentWrapper
root.directContent.anchors.fill = directContentWrapper
Qt.callLater(() => root.directContent.forceActiveFocus())
}
}
target: root
}
} }
Loader { Loader {
id: contentLoader id: contentLoader
anchors.fill: parent anchors.fill: parent
active: root.visible active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || root.visible)
asynchronous: false asynchronous: false
} focus: true
clip: false
visible: root.directContent === null
Behavior on opacity { onLoaded: {
NumberAnimation { if (item) {
duration: root.animationDuration Qt.callLater(() => item.forceActiveFocus())
easing.type: root.animationEasing
} }
} }
Behavior on scale {
enabled: root.animationType === "scale"
NumberAnimation {
duration: root.animationDuration
easing.type: root.animationEasing
} }
} }
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 8
shadowBlur: 1
shadowColor: Theme.shadowStrong
shadowOpacity: 0.3
}
} }
FocusScope { FocusScope {
@@ -207,8 +254,8 @@ PanelWindow {
objectName: "modalFocusScope" objectName: "modalFocusScope"
anchors.fill: parent anchors.fill: parent
visible: root.visible // Only active when the modal is visible visible: root.shouldBeVisible || root.visible
focus: root.visible focus: root.shouldBeVisible
Keys.onEscapePressed: event => { Keys.onEscapePressed: event => {
if (root.closeOnEscapeKey && shouldHaveFocus) { if (root.closeOnEscapeKey && shouldHaveFocus) {
root.close() root.close()
@@ -223,7 +270,7 @@ PanelWindow {
Connections { Connections {
function onShouldHaveFocusChanged() { function onShouldHaveFocusChanged() {
if (shouldHaveFocus && visible) { if (shouldHaveFocus && shouldBeVisible) {
Qt.callLater(() => focusScope.forceActiveFocus()) Qt.callLater(() => focusScope.forceActiveFocus())
} }
} }

View File

@@ -0,0 +1,542 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
property string pickerTitle: "Choose Color"
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
property var onColorSelectedCallback: null
signal colorSelected(color selectedColor)
property color currentColor: Theme.primary
property real hue: 0
property real saturation: 1
property real value: 1
property real alpha: 1
property real gradientX: 0
property real gradientY: 0
readonly property var standardColors: [
"#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4",
"#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722",
"#d32f2f", "#c2185b", "#7b1fa2", "#512da8", "#303f9f", "#1976d2", "#0288d1", "#0097a7",
"#00796b", "#388e3c", "#689f38", "#afb42b", "#fbc02d", "#ffa000", "#f57c00", "#e64a19",
"#c62828", "#ad1457", "#6a1b9a", "#4527a0", "#283593", "#1565c0", "#0277bd", "#00838f",
"#00695c", "#2e7d32", "#558b2f", "#9e9d24", "#f9a825", "#ff8f00", "#ef6c00", "#d84315",
"#ffffff", "#9e9e9e", "#212121"
]
function show() {
currentColor = selectedColor
updateFromColor(currentColor)
open()
}
function hide() {
onColorSelectedCallback = null
close()
}
onColorSelected: (color) => {
if (onColorSelectedCallback) {
onColorSelectedCallback(color)
}
}
function copyColorToClipboard(colorValue) {
Quickshell.execDetached(["sh", "-c", `echo "${colorValue}" | wl-copy`])
ToastService.showInfo(`Color ${colorValue} copied`)
SessionData.addRecentColor(currentColor)
}
function updateFromColor(color) {
hue = color.hsvHue
saturation = color.hsvSaturation
value = color.hsvValue
alpha = color.a
gradientX = saturation
gradientY = 1 - value
}
function updateColor() {
currentColor = Qt.hsva(hue, saturation, value, alpha)
}
function updateColorFromGradient(x, y) {
saturation = Math.max(0, Math.min(1, x))
value = Math.max(0, Math.min(1, 1 - y))
updateColor()
selectedColor = currentColor
}
function pickColorFromScreen() {
hide()
Proc.runCommand("hyprpicker", ["hyprpicker", "--format=hex"], (output, errorCode) => {
if (errorCode !== 0) {
console.warn("hyprpicker exited with code:", errorCode)
root.show()
return
}
const colorStr = output.trim()
if (colorStr.length >= 7 && colorStr.startsWith('#')) {
const pickedColor = Qt.color(colorStr)
root.selectedColor = pickedColor
root.currentColor = pickedColor
root.updateFromColor(pickedColor)
copyColorToClipboard(colorStr)
root.show()
}
})
}
width: 680
height: 680
backgroundColor: Theme.surfaceContainer
cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium
borderWidth: 1
keepContentLoaded: true
onBackgroundClicked: hide()
content: Component {
FocusScope {
id: colorContent
property alias hexInput: hexInput
anchors.fill: parent
focus: true
Keys.onEscapePressed: event => {
root.hide()
event.accepted = true
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingS
Column {
width: parent.width - 90
spacing: Theme.spacingXS
StyledText {
text: root.pickerTitle
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: I18n.tr("Select a color from the palette or use custom sliders")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
}
}
DankActionButton {
iconName: "colorize"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
root.pickColorFromScreen()
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
root.hide()
}
}
}
Row {
width: parent.width
spacing: Theme.spacingM
Rectangle {
id: gradientPicker
width: parent.width - 70
height: 280
radius: Theme.cornerRadius
border.color: Theme.outlineStrong
border.width: 1
clip: true
Rectangle {
anchors.fill: parent
color: Qt.hsva(root.hue, 1, 1, 1)
Rectangle {
anchors.fill: parent
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "#ffffff" }
GradientStop { position: 1.0; color: "transparent" }
}
}
Rectangle {
anchors.fill: parent
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 1.0; color: "#000000" }
}
}
}
Rectangle {
id: pickerCircle
width: 16
height: 16
radius: 8
border.color: "white"
border.width: 2
color: "transparent"
x: root.gradientX * parent.width - width / 2
y: root.gradientY * parent.height - height / 2
Rectangle {
anchors.centerIn: parent
width: parent.width - 4
height: parent.height - 4
radius: width / 2
border.color: "black"
border.width: 1
color: "transparent"
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.CrossCursor
onPressed: mouse => {
const x = Math.max(0, Math.min(1, mouse.x / width))
const y = Math.max(0, Math.min(1, mouse.y / height))
root.gradientX = x
root.gradientY = y
root.updateColorFromGradient(x, y)
}
onPositionChanged: mouse => {
if (pressed) {
const x = Math.max(0, Math.min(1, mouse.x / width))
const y = Math.max(0, Math.min(1, mouse.y / height))
root.gradientX = x
root.gradientY = y
root.updateColorFromGradient(x, y)
}
}
}
}
Rectangle {
id: hueSlider
width: 50
height: 280
radius: Theme.cornerRadius
border.color: Theme.outlineStrong
border.width: 1
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.00; color: "#ff0000" }
GradientStop { position: 0.17; color: "#ffff00" }
GradientStop { position: 0.33; color: "#00ff00" }
GradientStop { position: 0.50; color: "#00ffff" }
GradientStop { position: 0.67; color: "#0000ff" }
GradientStop { position: 0.83; color: "#ff00ff" }
GradientStop { position: 1.00; color: "#ff0000" }
}
Rectangle {
id: hueIndicator
width: parent.width
height: 4
color: "white"
border.color: "black"
border.width: 1
y: root.hue * parent.height - height / 2
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.SizeVerCursor
onPressed: mouse => {
const h = Math.max(0, Math.min(1, mouse.y / height))
root.hue = h
root.updateColor()
root.selectedColor = root.currentColor
}
onPositionChanged: mouse => {
if (pressed) {
const h = Math.max(0, Math.min(1, mouse.y / height))
root.hue = h
root.updateColor()
root.selectedColor = root.currentColor
}
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Material Colors")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
GridView {
width: parent.width
height: 140
cellWidth: 38
cellHeight: 38
clip: true
interactive: false
model: root.standardColors
delegate: Rectangle {
width: 36
height: 36
color: modelData
radius: 4
border.color: Theme.outlineStrong
border.width: 1
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: () => {
const pickedColor = Qt.color(modelData)
root.selectedColor = pickedColor
root.currentColor = pickedColor
root.updateFromColor(pickedColor)
}
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
Column {
width: 210
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Recent Colors")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Row {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: 5
Rectangle {
width: 36
height: 36
radius: 4
border.color: Theme.outlineStrong
border.width: 1
color: {
if (index < SessionData.recentColors.length) {
return SessionData.recentColors[index]
}
return Theme.surfaceContainerHigh
}
opacity: index < SessionData.recentColors.length ? 1.0 : 0.3
MouseArea {
anchors.fill: parent
cursorShape: index < SessionData.recentColors.length ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: index < SessionData.recentColors.length
onClicked: () => {
if (index < SessionData.recentColors.length) {
const pickedColor = SessionData.recentColors[index]
root.selectedColor = pickedColor
root.currentColor = pickedColor
root.updateFromColor(pickedColor)
}
}
}
}
}
}
}
Column {
width: parent.width - 330
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Opacity")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
DankSlider {
width: parent.width
value: Math.round(root.alpha * 100)
minimum: 0
maximum: 100
showValue: false
onSliderValueChanged: (newValue) => {
root.alpha = newValue / 100
root.updateColor()
root.selectedColor = root.currentColor
}
}
}
Rectangle {
width: 100
height: 50
radius: Theme.cornerRadius
color: root.currentColor
border.color: Theme.outlineStrong
border.width: 2
anchors.verticalCenter: parent.verticalCenter
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Hex:")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
id: hexInput
width: 120
height: 38
text: root.currentColor.toString()
font.pixelSize: Theme.fontSizeMedium
textColor: {
if (text.length === 0) return Theme.surfaceText
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
return hexPattern.test(text) ? Theme.surfaceText : Theme.error
}
placeholderText: "#000000"
backgroundColor: Theme.surfaceHover
borderWidth: 1
focusedBorderWidth: 2
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
onAccepted: () => {
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
if (!hexPattern.test(text)) return
const color = Qt.color(text)
if (color) {
root.selectedColor = color
root.currentColor = color
root.updateFromColor(color)
}
}
}
DankButton {
width: 80
buttonHeight: 36
text: I18n.tr("Apply")
backgroundColor: Theme.primary
textColor: Theme.background
anchors.verticalCenter: parent.verticalCenter
onClicked: {
const hexPattern = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/
if (!hexPattern.test(hexInput.text)) return
const color = Qt.color(hexInput.text)
if (color) {
root.currentColor = color
root.updateFromColor(color)
root.selectedColor = root.currentColor
root.colorSelected(root.currentColor)
SessionData.addRecentColor(root.currentColor)
root.hide()
}
}
}
Item {
width: parent.width - 460
height: 1
}
DankButton {
width: 70
buttonHeight: 36
text: I18n.tr("Cancel")
backgroundColor: "transparent"
textColor: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
onClicked: root.hide()
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: "transparent"
border.color: Theme.surfaceVariantAlpha
border.width: 1
z: -1
}
}
DankButton {
width: 70
buttonHeight: 36
text: I18n.tr("Copy")
backgroundColor: Theme.primary
textColor: Theme.background
anchors.verticalCenter: parent.verticalCenter
onClicked: {
const colorString = root.currentColor.toString()
root.copyColorToClipboard(colorString)
}
}
}
}
}
}
}

View File

@@ -35,6 +35,7 @@ DankModal {
property bool weAvailable: false property bool weAvailable: false
property string wePath: "" property string wePath: ""
property bool weMode: false property bool weMode: false
property var parentModal: null
signal fileSelected(string path) signal fileSelected(string path)
@@ -47,15 +48,17 @@ DankModal {
} }
function getLastPath() { function getLastPath() {
const lastPath = browserType === "wallpaper" ? SessionData.wallpaperLastPath : browserType === "profile" ? SessionData.profileLastPath : "" const lastPath = browserType === "wallpaper" ? CacheData.wallpaperLastPath : browserType === "profile" ? CacheData.profileLastPath : ""
return (lastPath && lastPath !== "") ? lastPath : homeDir return (lastPath && lastPath !== "") ? lastPath : homeDir
} }
function saveLastPath(path) { function saveLastPath(path) {
if (browserType === "wallpaper") { if (browserType === "wallpaper") {
SessionData.setWallpaperLastPath(path) CacheData.wallpaperLastPath = path
CacheData.saveCache()
} else if (browserType === "profile") { } else if (browserType === "profile") {
SessionData.setProfileLastPath(path) CacheData.profileLastPath = path
CacheData.saveCache()
} }
} }
@@ -131,6 +134,8 @@ DankModal {
objectName: "fileBrowserModal" objectName: "fileBrowserModal"
allowStacking: true allowStacking: true
closeOnEscapeKey: false
shouldHaveFocus: shouldBeVisible
Component.onCompleted: { Component.onCompleted: {
currentPath = getLastPath() currentPath = getLastPath()
} }
@@ -165,10 +170,23 @@ DankModal {
visible: false visible: false
onBackgroundClicked: close() onBackgroundClicked: close()
onOpened: { onOpened: {
modalFocusScope.forceActiveFocus() if (parentModal) {
parentModal.shouldHaveFocus = false
parentModal.allowFocusOverride = true
}
Qt.callLater(() => {
if (contentLoader && contentLoader.item) {
contentLoader.item.forceActiveFocus()
}
})
}
onDialogClosed: {
if (parentModal) {
parentModal.allowFocusOverride = false
parentModal.shouldHaveFocus = Qt.binding(() => {
return parentModal.shouldBeVisible
})
} }
modalFocusScope.Keys.onPressed: function (event) {
keyboardController.handleKey(event)
} }
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
@@ -239,7 +257,12 @@ DankModal {
return return
} }
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right) { const isInitKey = event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right ||
(event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) ||
(event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) ||
(event.key === Qt.Key_L && event.modifiers & Qt.ControlModifier)
if (isInitKey) {
keyboardNavigationActive = true keyboardNavigationActive = true
if (currentPath !== homeDir) { if (currentPath !== homeDir) {
backButtonFocused = true backButtonFocused = true
@@ -281,6 +304,69 @@ DankModal {
} }
event.accepted = true event.accepted = true
break break
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
if (backButtonFocused) {
backButtonFocused = false
selectedIndex = 0
} else if (selectedIndex < totalItems - 1) {
selectedIndex++
}
event.accepted = true
}
break
case Qt.Key_P:
if (event.modifiers & Qt.ControlModifier) {
if (selectedIndex > 0) {
selectedIndex--
} else if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
}
event.accepted = true
}
break
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
if (selectedIndex < totalItems - 1) {
selectedIndex++
}
event.accepted = true
}
break
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
if (selectedIndex > 0) {
selectedIndex--
} else if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
}
event.accepted = true
}
break
case Qt.Key_H:
if (event.modifiers & Qt.ControlModifier) {
if (!backButtonFocused && selectedIndex > 0) {
selectedIndex--
} else if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
}
event.accepted = true
}
break
case Qt.Key_L:
if (event.modifiers & Qt.ControlModifier) {
if (backButtonFocused) {
backButtonFocused = false
selectedIndex = 0
} else if (selectedIndex < totalItems - 1) {
selectedIndex++
}
event.accepted = true
}
break
case Qt.Key_Left: case Qt.Key_Left:
if (backButtonFocused) if (backButtonFocused)
return return
@@ -387,6 +473,16 @@ DankModal {
Item { Item {
anchors.fill: parent anchors.fill: parent
Keys.onPressed: event => {
keyboardController.handleKey(event)
}
onVisibleChanged: {
if (visible) {
forceActiveFocus()
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
@@ -527,7 +623,6 @@ DankModal {
required property bool fileIsDir required property bool fileIsDir
required property string filePath required property string filePath
required property string fileName required property string fileName
required property url fileURL
required property int index required property int index
width: weMode ? 245 : 140 width: weMode ? 245 : 140
@@ -687,7 +782,7 @@ DankModal {
width: parent.width - saveButton.width - Theme.spacingM width: parent.width - saveButton.width - Theme.spacingM
height: 40 height: 40
text: defaultFileName text: defaultFileName
placeholderText: "Enter filename..." placeholderText: I18n.tr("Enter filename...")
ignoreLeftRightKeys: false ignoreLeftRightKeys: false
focus: saveMode focus: saveMode
topPadding: Theme.spacingS topPadding: Theme.spacingS
@@ -720,7 +815,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: "Save" text: I18n.tr("Save")
color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
} }
@@ -824,7 +919,7 @@ DankModal {
spacing: Theme.spacingM spacing: Theme.spacingM
StyledText { StyledText {
text: qsTr("File Already Exists") text: I18n.tr("File Already Exists")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -832,7 +927,7 @@ DankModal {
} }
StyledText { StyledText {
text: qsTr("A file with this name already exists. Do you want to overwrite it?") text: I18n.tr("A file with this name already exists. Do you want to overwrite it?")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
width: parent.width width: parent.width
@@ -854,7 +949,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("Cancel") text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -880,7 +975,7 @@ DankModal {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("Overwrite") text: I18n.tr("Overwrite")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -134,7 +134,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "File Information" text: I18n.tr("File Information")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -197,7 +197,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "F1/I: Toggle • F10: Help" text: I18n.tr("F1/I: Toggle • F10: Help")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
anchors.bottom: parent.bottom anchors.bottom: parent.bottom

View File

@@ -23,7 +23,7 @@ Rectangle {
spacing: 2 spacing: 2
StyledText { StyledText {
text: "Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select" text: I18n.tr("Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width
@@ -32,7 +32,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close" text: I18n.tr("Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width

View File

@@ -0,0 +1,266 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
width: 1400
height: 900
onBackgroundClicked: close()
function categorizeKeybinds() {
const categories = {
"Workspace": [],
"Window": [],
"Monitor": [],
"Execute": [],
"System": [],
"Other": []
}
function addKeybind(keybind) {
const dispatcher = keybind.dispatcher || ""
if (dispatcher.includes("workspace")) {
categories["Workspace"].push(keybind)
} else if (dispatcher.includes("monitor")) {
categories["Monitor"].push(keybind)
} else if (dispatcher.includes("window") || dispatcher.includes("focus") || dispatcher.includes("move") || dispatcher.includes("swap") || dispatcher.includes("resize") || dispatcher === "killactive" || dispatcher === "fullscreen" || dispatcher === "togglefloating") {
categories["Window"].push(keybind)
} else if (dispatcher === "exec") {
categories["Execute"].push(keybind)
} else if (dispatcher === "exit" || dispatcher.includes("dpms")) {
categories["System"].push(keybind)
} else {
categories["Other"].push(keybind)
}
}
const allKeybinds = HyprKeybindsService.keybinds.keybinds || []
for (let i = 0; i < allKeybinds.length; i++) {
addKeybind(allKeybinds[i])
}
const children = HyprKeybindsService.keybinds.children || []
for (let i = 0; i < children.length; i++) {
const child = children[i]
const childKeybinds = child.keybinds || []
for (let j = 0; j < childKeybinds.length; j++) {
addKeybind(childKeybinds[j])
}
}
categories["Workspace"].sort((a, b) => {
const dispA = a.dispatcher || ""
const dispB = b.dispatcher || ""
return dispA.localeCompare(dispB)
})
categories["Window"].sort((a, b) => {
const dispA = a.dispatcher || ""
const dispB = b.dispatcher || ""
return dispA.localeCompare(dispB)
})
categories["Monitor"].sort((a, b) => {
const dispA = a.dispatcher || ""
const dispB = b.dispatcher || ""
return dispA.localeCompare(dispB)
})
categories["Execute"].sort((a, b) => {
const modsA = a.mods || []
const keyA = a.key || ""
const bindA = [...modsA, keyA].join("+")
const modsB = b.mods || []
const keyB = b.key || ""
const bindB = [...modsB, keyB].join("+")
return bindA.localeCompare(bindB)
})
return categories
}
content: Component {
Item {
anchors.fill: parent
DankFlickable {
id: mainFlickable
anchors.fill: parent
anchors.margins: Theme.spacingL
contentWidth: rowLayout.implicitWidth
contentHeight: rowLayout.implicitHeight
clip: true
Row {
id: rowLayout
spacing: Theme.spacingM
property var categories: root.categorizeKeybinds()
property real columnWidth: (mainFlickable.width - spacing * 2) / 3
Column {
width: rowLayout.columnWidth
spacing: Theme.spacingXS
StyledText {
text: "Window / Monitor"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.primary
}
Rectangle {
width: parent.width
height: 1
color: Theme.primary
opacity: 0.3
}
Item { width: 1; height: Theme.spacingXS }
Column {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: [...(rowLayout.categories["Window"] || []), ...(rowLayout.categories["Monitor"] || [])]
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: Math.min(140, parent.width * 0.42)
height: 22
radius: 4
opacity: 0.3
StyledText {
anchors.centerIn: parent
anchors.margins: 2
width: parent.width - 4
text: {
const mods = modelData.mods || []
const key = modelData.key || ""
const parts = [...mods, key]
return parts.join("+")
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
isMonospace: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
StyledText {
width: parent.width - 150
text: {
const comment = modelData.comment || ""
if (comment) return comment
const dispatcher = modelData.dispatcher || ""
const params = modelData.params || ""
return params ? `${dispatcher} ${params}` : dispatcher
}
font.pixelSize: Theme.fontSizeSmall
opacity: 0.9
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
Repeater {
model: ["Workspace", "Execute"]
Column {
width: rowLayout.columnWidth
spacing: Theme.spacingXS
StyledText {
text: modelData
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.primary
}
Rectangle {
width: parent.width
height: 1
color: Theme.primary
opacity: 0.3
}
Item { width: 1; height: Theme.spacingXS }
Column {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: rowLayout.categories[modelData] || []
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: Math.min(140, parent.width * 0.42)
height: 22
radius: 4
opacity: 0.3
StyledText {
anchors.centerIn: parent
anchors.margins: 2
width: parent.width - 4
text: {
const mods = modelData.mods || []
const key = modelData.key || ""
const parts = [...mods, key]
return parts.join("+")
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
isMonospace: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
StyledText {
width: parent.width - 150
text: {
const comment = modelData.comment || ""
if (comment) return comment
const dispatcher = modelData.dispatcher || ""
const params = modelData.params || ""
return params ? `${dispatcher} ${params}` : dispatcher
}
font.pixelSize: Theme.fontSizeSmall
opacity: 0.9
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
}
}
}
}
}
}

View File

@@ -56,7 +56,7 @@ DankModal {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Network Information" text: I18n.tr("Network Information")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -126,7 +126,7 @@ DankModal {
id: closeText id: closeText
anchors.centerIn: parent anchors.centerIn: parent
text: "Close" text: I18n.tr("Close")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -54,6 +54,9 @@ DankModal {
height: 700 height: 700
visible: false visible: false
onBackgroundClicked: hide() onBackgroundClicked: hide()
onOpened: () => {
Qt.callLater(() => modalFocusScope.forceActiveFocus());
}
onShouldBeVisibleChanged: (shouldBeVisible) => { onShouldBeVisibleChanged: (shouldBeVisible) => {
if (!shouldBeVisible) { if (!shouldBeVisible) {
notificationModalOpen = false notificationModalOpen = false

View File

@@ -9,31 +9,47 @@ DankModal {
property int selectedIndex: 0 property int selectedIndex: 0
property int optionCount: SessionService.hibernateSupported ? 5 : 4 property int optionCount: SessionService.hibernateSupported ? 5 : 4
property rect parentBounds: Qt.rect(0, 0, 0, 0)
property var parentScreen: null
signal powerActionRequested(string action, string title, string message) signal powerActionRequested(string action, string title, string message)
function openCentered() {
parentBounds = Qt.rect(0, 0, 0, 0)
parentScreen = null
backgroundOpacity = 0.5
open()
}
function openFromControlCenter(bounds, targetScreen) {
parentBounds = bounds
parentScreen = targetScreen
backgroundOpacity = 0
open()
}
function selectOption(action) { function selectOption(action) {
close(); close();
const actions = { const actions = {
"logout": { "logout": {
"title": "Log Out", "title": I18n.tr("Log Out"),
"message": "Are you sure you want to log out?" "message": I18n.tr("Are you sure you want to log out?")
}, },
"suspend": { "suspend": {
"title": "Suspend", "title": I18n.tr("Suspend"),
"message": "Are you sure you want to suspend the system?" "message": I18n.tr("Are you sure you want to suspend the system?")
}, },
"hibernate": { "hibernate": {
"title": "Hibernate", "title": I18n.tr("Hibernate"),
"message": "Are you sure you want to hibernate the system?" "message": I18n.tr("Are you sure you want to hibernate the system?")
}, },
"reboot": { "reboot": {
"title": "Reboot", "title": I18n.tr("Reboot"),
"message": "Are you sure you want to reboot the system?" "message": I18n.tr("Are you sure you want to reboot the system?")
}, },
"poweroff": { "poweroff": {
"title": "Power Off", "title": I18n.tr("Power Off"),
"message": "Are you sure you want to power off the system?" "message": I18n.tr("Are you sure you want to power off the system?")
} }
} }
const selected = actions[action] const selected = actions[action]
@@ -47,23 +63,31 @@ DankModal {
width: 320 width: 320
height: contentLoader.item ? contentLoader.item.implicitHeight : 300 height: contentLoader.item ? contentLoader.item.implicitHeight : 300
enableShadow: true enableShadow: true
screen: parentScreen
positioning: parentBounds.width > 0 ? "custom" : "center"
customPosition: {
if (parentBounds.width > 0) {
const centerX = parentBounds.x + (parentBounds.width - width) / 2
const centerY = parentBounds.y + (parentBounds.height - height) / 2
return Qt.point(centerX, centerY)
}
return Qt.point(0, 0)
}
onBackgroundClicked: () => { onBackgroundClicked: () => {
return close(); return close();
} }
onOpened: () => { onOpened: () => {
selectedIndex = 0; selectedIndex = 0;
modalFocusScope.forceActiveFocus(); Qt.callLater(() => modalFocusScope.forceActiveFocus());
} }
modalFocusScope.Keys.onPressed: (event) => { modalFocusScope.Keys.onPressed: (event) => {
switch (event.key) { switch (event.key) {
case Qt.Key_Up: case Qt.Key_Up:
case Qt.Key_Backtab:
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount; selectedIndex = (selectedIndex - 1 + optionCount) % optionCount;
event.accepted = true; event.accepted = true;
break; break;
case Qt.Key_Down: case Qt.Key_Down:
selectedIndex = (selectedIndex + 1) % optionCount;
event.accepted = true;
break;
case Qt.Key_Tab: case Qt.Key_Tab:
selectedIndex = (selectedIndex + 1) % optionCount; selectedIndex = (selectedIndex + 1) % optionCount;
event.accepted = true; event.accepted = true;
@@ -78,6 +102,30 @@ DankModal {
} }
event.accepted = true; event.accepted = true;
break; break;
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % optionCount;
event.accepted = true;
}
break;
case Qt.Key_P:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount;
event.accepted = true;
}
break;
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % optionCount;
event.accepted = true;
}
break;
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount;
event.accepted = true;
}
break;
} }
} }
@@ -96,7 +144,7 @@ DankModal {
width: parent.width width: parent.width
StyledText { StyledText {
text: "Power Options" text: I18n.tr("Power Options")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -153,7 +201,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Log Out" text: I18n.tr("Log Out")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -206,7 +254,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Suspend" text: I18n.tr("Suspend")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -260,7 +308,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Hibernate" text: I18n.tr("Hibernate")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -314,7 +362,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Reboot" text: I18n.tr("Reboot")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -368,7 +416,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Power Off" text: I18n.tr("Power Off")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -123,7 +123,7 @@ DankModal {
} }
StyledText { StyledText {
text: "System Monitor Unavailable" text: I18n.tr("System Monitor Unavailable")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.error color: Theme.error
@@ -131,7 +131,7 @@ DankModal {
} }
StyledText { StyledText {
text: "The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature." text: I18n.tr("The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature.")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -154,7 +154,7 @@ DankModal {
height: 40 height: 40
StyledText { StyledText {
text: "System Monitor" text: I18n.tr("System Monitor")
font.pixelSize: Theme.fontSizeLarge + 4 font.pixelSize: Theme.fontSizeLarge + 4
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.surfaceText color: Theme.surfaceText
@@ -181,7 +181,7 @@ DankModal {
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
height: 52 height: 52
color: Theme.surfaceSelected color: Theme.surfaceContainerHigh
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Theme.outlineLight border.color: Theme.outlineLight
border.width: 1 border.width: 1
@@ -281,7 +281,7 @@ DankModal {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.surfaceContainerHigh
border.color: Theme.outlineLight border.color: Theme.outlineLight
border.width: 1 border.width: 1

View File

@@ -19,19 +19,103 @@ Item {
spacing: Theme.spacingXL spacing: Theme.spacingXL
StyledText { StyledText {
text: "Battery not detected - only AC power settings available" text: I18n.tr("Battery not detected - only AC power settings available")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: !BatteryService.batteryAvailable visible: !BatteryService.batteryAvailable
} }
StyledRect {
width: parent.width
height: lockScreenSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
Column {
id: lockScreenSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "lock"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Lock Screen")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
width: parent.width
text: I18n.tr("Show Power Actions")
description: I18n.tr("Show power, restart, and logout buttons on the lock screen")
checked: SettingsData.lockScreenShowPowerActions
onToggled: checked => SettingsData.setLockScreenShowPowerActions(checked)
}
StyledText {
text: I18n.tr("loginctl not available - lock integration requires DMS socket connection")
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
visible: !SessionService.loginctlAvailable
width: parent.width
wrapMode: Text.Wrap
}
DankToggle {
width: parent.width
text: I18n.tr("Enable loginctl lock integration")
description: I18n.tr("Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen")
checked: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration
enabled: SessionService.loginctlAvailable
onToggled: checked => {
if (SessionService.loginctlAvailable) {
SettingsData.setLoginctlLockIntegration(checked)
}
}
}
DankToggle {
width: parent.width
text: I18n.tr("Lock before suspend")
description: I18n.tr("Automatically lock the screen when the system prepares to suspend")
checked: SettingsData.lockBeforeSuspend
visible: SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration
onToggled: checked => SettingsData.setLockBeforeSuspend(checked)
}
DankToggle {
width: parent.width
text: I18n.tr("Enable fingerprint authentication")
description: I18n.tr("Use fingerprint reader for lock screen authentication (requires enrolled fingerprints)")
checked: SettingsData.enableFprint
visible: SettingsData.fprintdAvailable
onToggled: checked => SettingsData.setEnableFprint(checked)
}
}
}
StyledRect { StyledRect {
width: parent.width width: parent.width
height: timeoutSection.implicitHeight + Theme.spacingL * 2 height: timeoutSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 0
Column { Column {
id: timeoutSection id: timeoutSection
@@ -51,7 +135,7 @@ Item {
} }
StyledText { StyledText {
text: "Idle Settings" text: I18n.tr("Idle Settings")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -79,21 +163,20 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"] property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width text: I18n.tr("Automatically lock after")
text: "Automatically lock after"
options: timeoutOptions options: timeoutOptions
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acLockTimeout : SessionData.batteryLockTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout
const index = lockDropdown.timeoutValues.indexOf(currentTimeout) const index = lockDropdown.timeoutValues.indexOf(currentTimeout)
lockDropdown.currentValue = index >= 0 ? lockDropdown.timeoutOptions[index] : "Never" lockDropdown.currentValue = index >= 0 ? lockDropdown.timeoutOptions[index] : "Never"
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acLockTimeout : SessionData.batteryLockTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acLockTimeout : SettingsData.batteryLockTimeout
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout)
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never"
} }
@@ -103,9 +186,9 @@ Item {
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SessionData.setAcLockTimeout(timeout) SettingsData.setAcLockTimeout(timeout)
} else { } else {
SessionData.setBatteryLockTimeout(timeout) SettingsData.setBatteryLockTimeout(timeout)
} }
} }
} }
@@ -116,21 +199,20 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"] property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width text: I18n.tr("Turn off monitors after")
text: "Turn off monitors after"
options: timeoutOptions options: timeoutOptions
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acMonitorTimeout : SessionData.batteryMonitorTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout
const index = monitorDropdown.timeoutValues.indexOf(currentTimeout) const index = monitorDropdown.timeoutValues.indexOf(currentTimeout)
monitorDropdown.currentValue = index >= 0 ? monitorDropdown.timeoutOptions[index] : "Never" monitorDropdown.currentValue = index >= 0 ? monitorDropdown.timeoutOptions[index] : "Never"
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acMonitorTimeout : SessionData.batteryMonitorTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acMonitorTimeout : SettingsData.batteryMonitorTimeout
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout)
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never"
} }
@@ -140,9 +222,9 @@ Item {
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SessionData.setAcMonitorTimeout(timeout) SettingsData.setAcMonitorTimeout(timeout)
} else { } else {
SessionData.setBatteryMonitorTimeout(timeout) SettingsData.setBatteryMonitorTimeout(timeout)
} }
} }
} }
@@ -153,21 +235,20 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"] property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width text: I18n.tr("Suspend system after")
text: "Suspend system after"
options: timeoutOptions options: timeoutOptions
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acSuspendTimeout : SessionData.batterySuspendTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout
const index = suspendDropdown.timeoutValues.indexOf(currentTimeout) const index = suspendDropdown.timeoutValues.indexOf(currentTimeout)
suspendDropdown.currentValue = index >= 0 ? suspendDropdown.timeoutOptions[index] : "Never" suspendDropdown.currentValue = index >= 0 ? suspendDropdown.timeoutOptions[index] : "Never"
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acSuspendTimeout : SessionData.batterySuspendTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acSuspendTimeout : SettingsData.batterySuspendTimeout
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout)
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never"
} }
@@ -177,9 +258,9 @@ Item {
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SessionData.setAcSuspendTimeout(timeout) SettingsData.setAcSuspendTimeout(timeout)
} else { } else {
SessionData.setBatterySuspendTimeout(timeout) SettingsData.setBatterySuspendTimeout(timeout)
} }
} }
} }
@@ -190,22 +271,21 @@ Item {
property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"] property var timeoutOptions: ["Never", "1 minute", "2 minutes", "3 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "1 hour 30 minutes", "2 hours", "3 hours"]
property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800] property var timeoutValues: [0, 60, 120, 180, 300, 600, 900, 1200, 1800, 3600, 5400, 7200, 10800]
width: parent.width text: I18n.tr("Hibernate system after")
text: "Hibernate system after"
options: timeoutOptions options: timeoutOptions
visible: SessionService.hibernateSupported visible: SessionService.hibernateSupported
Connections { Connections {
target: powerCategory target: powerCategory
function onCurrentIndexChanged() { function onCurrentIndexChanged() {
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acHibernateTimeout : SessionData.batteryHibernateTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acHibernateTimeout : SettingsData.batteryHibernateTimeout
const index = hibernateDropdown.timeoutValues.indexOf(currentTimeout) const index = hibernateDropdown.timeoutValues.indexOf(currentTimeout)
hibernateDropdown.currentValue = index >= 0 ? hibernateDropdown.timeoutOptions[index] : "Never" hibernateDropdown.currentValue = index >= 0 ? hibernateDropdown.timeoutOptions[index] : "Never"
} }
} }
Component.onCompleted: { Component.onCompleted: {
const currentTimeout = powerCategory.currentIndex === 0 ? SessionData.acHibernateTimeout : SessionData.batteryHibernateTimeout const currentTimeout = powerCategory.currentIndex === 0 ? SettingsData.acHibernateTimeout : SettingsData.batteryHibernateTimeout
const index = timeoutValues.indexOf(currentTimeout) const index = timeoutValues.indexOf(currentTimeout)
currentValue = index >= 0 ? timeoutOptions[index] : "Never" currentValue = index >= 0 ? timeoutOptions[index] : "Never"
} }
@@ -215,24 +295,16 @@ Item {
if (index >= 0) { if (index >= 0) {
const timeout = timeoutValues[index] const timeout = timeoutValues[index]
if (powerCategory.currentIndex === 0) { if (powerCategory.currentIndex === 0) {
SessionData.setAcHibernateTimeout(timeout) SettingsData.setAcHibernateTimeout(timeout)
} else { } else {
SessionData.setBatteryHibernateTimeout(timeout) SettingsData.setBatteryHibernateTimeout(timeout)
} }
} }
} }
} }
DankToggle {
width: parent.width
text: "Lock before suspend"
description: "Automatically lock the screen when the system prepares to suspend"
checked: SessionData.lockBeforeSuspend
onToggled: checked => SessionData.setLockBeforeSuspend(checked)
}
StyledText { StyledText {
text: "Idle monitoring not supported - requires newer Quickshell version" text: I18n.tr("Idle monitoring not supported - requires newer Quickshell version")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.error color: Theme.error
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -241,6 +313,245 @@ Item {
} }
} }
StyledRect {
width: parent.width
height: powerCommandConfirmSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
Column {
id: powerCommandConfirmSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "check_circle"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Power Action Confirmation")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
width: parent.width
text: I18n.tr("Show Confirmation on Power Actions")
description: I18n.tr("Request confirmation on power off, restart, suspend, hibernate and logout actions")
checked: SettingsData.powerActionConfirm
onToggled: checked => SettingsData.setPowerActionConfirm(checked)
}
}
}
StyledRect {
width: parent.width
height: powerCommandCustomization.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
Column {
id: powerCommandCustomization
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "developer_mode"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Custom Power Actions")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard logout procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customLogoutCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myLogout.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionLogout) {
text = SettingsData.customPowerActionLogout;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionLogout(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard suspend procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customSuspendCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/mySuspend.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionSuspend) {
text = SettingsData.customPowerActionSuspend;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionSuspend(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard hibernate procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customHibernateCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myHibernate.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionHibernate) {
text = SettingsData.customPowerActionHibernate;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionHibernate(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard reboot procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customRebootCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myReboot.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionReboot) {
text = SettingsData.customPowerActionReboot;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionReboot(text.trim());
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
anchors.left: parent.left
StyledText {
text: I18n.tr("Command or script to run instead of the standard power off procedure")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customPowerOffCommand
width: parent.width
height: 48
placeholderText: "/usr/bin/myPowerOff.sh"
backgroundColor: Theme.surfaceVariant
normalBorderColor: Theme.primarySelected
focusedBorderColor: Theme.primary
Component.onCompleted: {
if (SettingsData.customPowerActionPowerOff) {
text = SettingsData.customPowerActionPowerOff;
}
}
onTextEdited: {
SettingsData.setCustomPowerActionPowerOff(text.trim());
}
}
}
}
}
} }
} }
} }

View File

@@ -2,11 +2,12 @@ import QtQuick
import qs.Common import qs.Common
import qs.Modules.Settings import qs.Modules.Settings
Item { FocusScope {
id: root id: root
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
focus: true
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -34,27 +35,14 @@ Item {
} }
Loader { Loader {
id: timeLoader id: timeWeatherLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 1 active: root.currentIndex === 1
visible: active visible: active
asynchronous: true asynchronous: true
sourceComponent: TimeTab { sourceComponent: TimeWeatherTab {
}
}
Loader {
id: weatherLoader
anchors.fill: parent
active: root.currentIndex === 2
visible: active
asynchronous: true
sourceComponent: WeatherTab {
} }
} }
@@ -63,11 +51,12 @@ Item {
id: topBarLoader id: topBarLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 3 active: root.currentIndex === 2
visible: active visible: active
asynchronous: true asynchronous: true
sourceComponent: TopBarTab { sourceComponent: DankBarTab {
parentModal: root.parentModal
} }
} }
@@ -76,7 +65,7 @@ Item {
id: widgetsLoader id: widgetsLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 4 active: root.currentIndex === 3
visible: active visible: active
asynchronous: true asynchronous: true
@@ -89,7 +78,7 @@ Item {
id: dockLoader id: dockLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 5 active: root.currentIndex === 4
visible: active visible: active
asynchronous: true asynchronous: true
@@ -105,7 +94,7 @@ Item {
id: displaysLoader id: displaysLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 6 active: root.currentIndex === 5
visible: active visible: active
asynchronous: true asynchronous: true
@@ -118,7 +107,7 @@ Item {
id: launcherLoader id: launcherLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 7 active: root.currentIndex === 6
visible: active visible: active
asynchronous: true asynchronous: true
@@ -131,7 +120,7 @@ Item {
id: themeColorsLoader id: themeColorsLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 8 active: root.currentIndex === 7
visible: active visible: active
asynchronous: true asynchronous: true
@@ -144,7 +133,7 @@ Item {
id: powerLoader id: powerLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 9 active: root.currentIndex === 8
visible: active visible: active
asynchronous: true asynchronous: true
@@ -153,6 +142,20 @@ Item {
} }
Loader {
id: pluginsLoader
anchors.fill: parent
active: root.currentIndex === 9
visible: active
asynchronous: true
sourceComponent: PluginsTab {
parentModal: root.parentModal
}
}
Loader { Loader {
id: aboutLoader id: aboutLoader

View File

@@ -34,7 +34,7 @@ DankModal {
objectName: "settingsModal" objectName: "settingsModal"
width: 800 width: 800
height: 750 height: 800
visible: false visible: false
onBackgroundClicked: () => { onBackgroundClicked: () => {
return hide(); return hide();
@@ -78,6 +78,7 @@ DankModal {
id: profileBrowser id: profileBrowser
allowStacking: true allowStacking: true
parentModal: settingsModal
browserTitle: "Select Profile Image" browserTitle: "Select Profile Image"
browserIcon: "person" browserIcon: "person"
browserType: "profile" browserType: "profile"
@@ -87,12 +88,6 @@ DankModal {
close(); close();
} }
onDialogClosed: () => { onDialogClosed: () => {
if (settingsModal) {
settingsModal.allowFocusOverride = false;
settingsModal.shouldHaveFocus = Qt.binding(() => {
return settingsModal.shouldBeVisible;
});
}
allowStacking = true; allowStacking = true;
} }
} }
@@ -101,6 +96,7 @@ DankModal {
id: wallpaperBrowser id: wallpaperBrowser
allowStacking: true allowStacking: true
parentModal: settingsModal
browserTitle: "Select Wallpaper" browserTitle: "Select Wallpaper"
browserIcon: "wallpaper" browserIcon: "wallpaper"
browserType: "wallpaper" browserType: "wallpaper"
@@ -115,7 +111,7 @@ DankModal {
} }
settingsContent: Component { settingsContent: Component {
Item { FocusScope {
anchors.fill: parent anchors.fill: parent
focus: true focus: true
@@ -144,7 +140,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Settings" text: I18n.tr("Settings")
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -9,37 +9,37 @@ Rectangle {
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
readonly property var sidebarItems: [{ readonly property var sidebarItems: [{
"text": "Personalization", "text": I18n.tr("Personalization"),
"icon": "person" "icon": "person"
}, { }, {
"text": "Time & Date", "text": I18n.tr("Time & Weather"),
"icon": "schedule" "icon": "schedule"
}, { }, {
"text": "Weather", "text": I18n.tr("Dank Bar"),
"icon": "cloud"
}, {
"text": "Top Bar",
"icon": "toolbar" "icon": "toolbar"
}, { }, {
"text": "Widgets", "text": I18n.tr("Widgets"),
"icon": "widgets" "icon": "widgets"
}, { }, {
"text": "Dock", "text": I18n.tr("Dock"),
"icon": "dock_to_bottom" "icon": "dock_to_bottom"
}, { }, {
"text": "Displays", "text": I18n.tr("Displays"),
"icon": "monitor" "icon": "monitor"
}, { }, {
"text": "Launcher", "text": I18n.tr("Launcher"),
"icon": "apps" "icon": "apps"
}, { }, {
"text": "Theme & Colors", "text": I18n.tr("Theme & Colors"),
"icon": "palette" "icon": "palette"
}, { }, {
"text": "Power", "text": I18n.tr("Power & Security"),
"icon": "power_settings_new" "icon": "power"
}, { }, {
"text": "About", "text": I18n.tr("Plugins"),
"icon": "extension"
}, {
"text": I18n.tr("About"),
"icon": "info" "icon": "info"
}] }]
@@ -83,7 +83,7 @@ Rectangle {
width: parent.width - Theme.spacingS * 2 width: parent.width - Theme.spacingS * 2
height: 44 height: 44
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: isActive ? Theme.primaryContainer : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent" color: isActive ? Theme.primary : tabMouseArea.containsMouse ? Theme.surfaceHover : "transparent"
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -94,14 +94,14 @@ Rectangle {
DankIcon { DankIcon {
name: modelData.icon || "" name: modelData.icon || ""
size: Theme.iconSize - 2 size: Theme.iconSize - 2
color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: modelData.text || "" text: modelData.text || ""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: parent.parent.isActive ? Theme.surfaceText : Theme.surfaceText color: parent.parent.isActive ? Theme.primaryText : Theme.surfaceText
font.weight: parent.parent.isActive ? Font.Medium : Font.Normal font.weight: parent.parent.isActive ? Font.Medium : Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }

View File

@@ -13,8 +13,13 @@ Item {
property alias searchField: searchField property alias searchField: searchField
property var parentModal: null property var parentModal: null
function resetScroll() {
resultsView.resetScroll()
}
anchors.fill: parent anchors.fill: parent
focus: true focus: true
clip: false
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
if (parentModal) if (parentModal)
@@ -33,13 +38,49 @@ Item {
} else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") { } else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow() appLauncher.selectPreviousInRow()
event.accepted = true event.accepted = true
} else if (event.key == Qt.Key_J && event.modifiers & Qt.ControlModifier) {
appLauncher.selectNext()
event.accepted = true
} else if (event.key == Qt.Key_K && event.modifiers & Qt.ControlModifier) {
appLauncher.selectPrevious()
event.accepted = true
} else if (event.key == Qt.Key_L && event.modifiers & Qt.ControlModifier && appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
event.accepted = true
} else if (event.key == Qt.Key_H && event.modifiers & Qt.ControlModifier && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
event.accepted = true
} else if (event.key === Qt.Key_Tab) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
} else {
appLauncher.selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_Backtab) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
} else {
appLauncher.selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
} else {
appLauncher.selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
} else {
appLauncher.selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
appLauncher.launchSelected() appLauncher.launchSelected()
event.accepted = true event.accepted = true
} else if (!searchField.activeFocus && event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
searchField.forceActiveFocus()
searchField.insertText(event.text)
event.accepted = true
} }
} }
@@ -59,43 +100,22 @@ Item {
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingM
spacing: Theme.spacingL spacing: Theme.spacingM
clip: false
Rectangle {
width: parent.width
height: categorySelector.height + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceVariantAlpha
border.color: Theme.outlineMedium
border.width: 1
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
CategorySelector {
id: categorySelector
anchors.centerIn: parent
width: parent.width - Theme.spacingM * 2
categories: appLauncher.categories
selectedCategory: appLauncher.selectedCategory
compact: false
onCategorySelected: category => {
appLauncher.setCategory(category)
}
}
}
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
leftPadding: Theme.spacingS
DankTextField { DankTextField {
id: searchField id: searchField
width: parent.width - 80 - Theme.spacingM width: parent.width - 80 - Theme.spacingL
height: 56 height: 56
cornerRadius: Theme.cornerRadius cornerRadius: Theme.cornerRadius
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7) backgroundColor: Theme.surfaceContainerHigh
normalBorderColor: Theme.outlineMedium normalBorderColor: Theme.outlineMedium
focusedBorderColor: Theme.primary focusedBorderColor: Theme.primary
leftIconName: "search" leftIconName: "search"
@@ -107,7 +127,8 @@ Item {
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: parentModal ? parentModal.spotlightOpen : true enabled: parentModal ? parentModal.spotlightOpen : true
placeholderText: "" placeholderText: ""
ignoreLeftRightKeys: true ignoreLeftRightKeys: appLauncher.viewMode !== "list"
ignoreTabKeys: true
keyForwardTargets: [spotlightKeyHandler] keyForwardTargets: [spotlightKeyHandler]
text: appLauncher.searchQuery text: appLauncher.searchQuery
onTextEdited: () => { onTextEdited: () => {
@@ -125,7 +146,7 @@ Item {
else if (appLauncher.model.count > 0) else if (appLauncher.model.count > 0)
appLauncher.launchApp(appLauncher.model.get(0)) appLauncher.launchApp(appLauncher.model.get(0))
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) { } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
event.accepted = false event.accepted = false
} }
} }
@@ -141,8 +162,6 @@ Item {
height: 36 height: 36
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: appLauncher.viewMode === "list" ? Theme.primaryHover : listViewArea.containsMouse ? Theme.surfaceHover : "transparent" color: appLauncher.viewMode === "list" ? Theme.primaryHover : listViewArea.containsMouse ? Theme.surfaceHover : "transparent"
border.color: appLauncher.viewMode === "list" ? Theme.primarySelected : "transparent"
border.width: 1
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -168,8 +187,6 @@ Item {
height: 36 height: 36
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: appLauncher.viewMode === "grid" ? Theme.primaryHover : gridViewArea.containsMouse ? Theme.surfaceHover : "transparent" color: appLauncher.viewMode === "grid" ? Theme.primaryHover : gridViewArea.containsMouse ? Theme.surfaceHover : "transparent"
border.color: appLauncher.viewMode === "grid" ? Theme.primarySelected : "transparent"
border.width: 1
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -193,6 +210,7 @@ Item {
} }
SpotlightResults { SpotlightResults {
id: resultsView
appLauncher: spotlightKeyHandler.appLauncher appLauncher: spotlightKeyHandler.appLauncher
contextMenu: contextMenu contextMenu: contextMenu
} }
@@ -210,7 +228,7 @@ Item {
visible: contextMenu.visible visible: contextMenu.visible
z: 999 z: 999
onClicked: () => { onClicked: () => {
contextMenu.close() contextMenu.hide()
} }
MouseArea { MouseArea {

View File

@@ -1,55 +1,42 @@
import QtQuick import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Rectangle { Popup {
id: contextMenu id: contextMenu
property var currentApp: null property var currentApp: null
property bool menuVisible: false
property var appLauncher: null property var appLauncher: null
property var parentHandler: null property var parentHandler: null
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
function show(x, y, app) { function show(x, y, app) {
currentApp = app currentApp = app
const menuWidth = 180 contextMenu.x = x + 4
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2 contextMenu.y = y + 4
let finalX = x + 8 contextMenu.open()
let finalY = y + 8
if (parentHandler) {
if (finalX + menuWidth > parentHandler.width)
finalX = x - menuWidth - 8
if (finalY + menuHeight > parentHandler.height)
finalY = y - menuHeight - 8
finalX = Math.max(8, Math.min(finalX, parentHandler.width - menuWidth - 8))
finalY = Math.max(8, Math.min(finalY, parentHandler.height - menuHeight - 8))
}
contextMenu.x = finalX
contextMenu.y = finalY
contextMenu.visible = true
contextMenu.menuVisible = true
} }
function close() { function hide() {
contextMenu.menuVisible = false contextMenu.close()
Qt.callLater(() => {
contextMenu.visible = false
})
} }
visible: false width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
padding: 0
closePolicy: Popup.CloseOnPressOutside
modal: false
dim: false
background: Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.popupBackground() color: Theme.popupBackground()
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -59,7 +46,28 @@ Rectangle {
anchors.bottomMargin: -4 anchors.bottomMargin: -4
radius: parent.radius radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15) color: Qt.rgba(0, 0, 0, 0.15)
z: parent.z - 1 z: -1
}
}
enter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
exit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
} }
Column { Column {
@@ -76,6 +84,7 @@ Rectangle {
color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row { Row {
id: pinRow
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -83,10 +92,10 @@ Rectangle {
DankIcon { DankIcon {
name: { name: {
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) if (!desktopEntry)
return "push_pin" return "push_pin"
const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || "" const appId = desktopEntry.id || desktopEntry.execString || ""
return SessionData.isPinnedApp(appId) ? "keep_off" : "push_pin" return SessionData.isPinnedApp(appId) ? "keep_off" : "push_pin"
} }
size: Theme.iconSize - 2 size: Theme.iconSize - 2
@@ -97,11 +106,11 @@ Rectangle {
StyledText { StyledText {
text: { text: {
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) if (!desktopEntry)
return "Pin to Dock" return I18n.tr("Pin to Dock")
const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || "" const appId = desktopEntry.id || desktopEntry.execString || ""
return SessionData.isPinnedApp(appId) ? "Unpin from Dock" : "Pin to Dock" return SessionData.isPinnedApp(appId) ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
@@ -117,15 +126,15 @@ Rectangle {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: () => { onClicked: () => {
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) if (!desktopEntry)
return return
const appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || "" const appId = desktopEntry.id || desktopEntry.execString || ""
if (SessionData.isPinnedApp(appId)) if (SessionData.isPinnedApp(appId))
SessionData.removePinnedApp(appId) SessionData.removePinnedApp(appId)
else else
SessionData.addPinnedApp(appId) SessionData.addPinnedApp(appId)
contextMenu.close() contextMenu.hide()
} }
} }
} }
@@ -144,6 +153,79 @@ Rectangle {
} }
} }
Repeater {
model: desktopEntry && desktopEntry.actions ? desktopEntry.actions : []
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadius
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
id: actionRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Item {
anchors.verticalCenter: parent.verticalCenter
width: Theme.iconSize - 2
height: Theme.iconSize - 2
visible: modelData.icon && modelData.icon !== ""
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
}
StyledText {
text: modelData.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: actionMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && desktopEntry) {
SessionService.launchDesktopAction(desktopEntry, modelData)
if (appLauncher && contextMenu.currentApp) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
}
}
}
}
Rectangle {
visible: desktopEntry && desktopEntry.actions && desktopEntry.actions.length > 0
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
@@ -151,6 +233,7 @@ Rectangle {
color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row { Row {
id: launchRow
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -165,7 +248,7 @@ Rectangle {
} }
StyledText { StyledText {
text: "Launch" text: I18n.tr("Launch")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -183,23 +266,73 @@ Rectangle {
if (contextMenu.currentApp && appLauncher) if (contextMenu.currentApp && appLauncher)
appLauncher.launchApp(contextMenu.currentApp) appLauncher.launchApp(contextMenu.currentApp)
contextMenu.close() contextMenu.hide()
}
} }
} }
} }
Behavior on opacity { Rectangle {
NumberAnimation { visible: SessionService.hasPrimeRun
duration: Theme.mediumDuration width: parent.width - Theme.spacingS * 2
easing.type: Theme.emphasizedEasing height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
} }
} }
Behavior on scale { Rectangle {
NumberAnimation { visible: SessionService.hasPrimeRun
duration: Theme.mediumDuration width: parent.width
easing.type: Theme.emphasizedEasing height: 32
radius: Theme.cornerRadius
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
id: primeRunRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "memory"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Launch on dGPU")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: primeRunMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
if (desktopEntry) {
SessionService.launchDesktopEntry(desktopEntry, true)
if (appLauncher && contextMenu.currentApp) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
}
}
} }
} }
} }

View File

@@ -13,18 +13,35 @@ DankModal {
id: spotlightModal id: spotlightModal
property bool spotlightOpen: false property bool spotlightOpen: false
property Component spotlightContent property alias spotlightContent: spotlightContentInstance
function show() { function show() {
spotlightOpen = true spotlightOpen = true
open() open()
if (contentLoader.item && contentLoader.item.appLauncher) {
contentLoader.item.appLauncher.searchQuery = ""
}
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) { if (spotlightContent && spotlightContent.searchField) {
contentLoader.item.searchField.forceActiveFocus() spotlightContent.searchField.forceActiveFocus()
}
})
}
function showWithQuery(query) {
if (spotlightContent) {
if (spotlightContent.appLauncher) {
spotlightContent.appLauncher.searchQuery = query
}
if (spotlightContent.searchField) {
spotlightContent.searchField.text = query
}
}
spotlightOpen = true
open()
Qt.callLater(() => {
if (spotlightContent && spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus()
} }
}) })
} }
@@ -32,10 +49,21 @@ DankModal {
function hide() { function hide() {
spotlightOpen = false spotlightOpen = false
close() close()
if (contentLoader.item && contentLoader.item.appLauncher) { }
contentLoader.item.appLauncher.searchQuery = ""
contentLoader.item.appLauncher.selectedIndex = 0 onDialogClosed: {
contentLoader.item.appLauncher.setCategory("All") if (spotlightContent) {
if (spotlightContent.appLauncher) {
spotlightContent.appLauncher.searchQuery = ""
spotlightContent.appLauncher.selectedIndex = 0
spotlightContent.appLauncher.setCategory(I18n.tr("All"))
}
if (spotlightContent.resetScroll) {
spotlightContent.resetScroll()
}
if (spotlightContent.searchField) {
spotlightContent.searchField.text = ""
}
} }
} }
@@ -49,20 +77,21 @@ DankModal {
shouldBeVisible: spotlightOpen shouldBeVisible: spotlightOpen
width: 550 width: 550
height: 600 height: 700
backgroundColor: Theme.popupBackground() backgroundColor: Theme.popupBackground()
cornerRadius: Theme.cornerRadius cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
borderWidth: 1 borderWidth: 1
enableShadow: true enableShadow: true
keepContentLoaded: true
onVisibleChanged: () => { onVisibleChanged: () => {
if (visible && !spotlightOpen) { if (visible && !spotlightOpen) {
show() show()
} }
if (visible && contentLoader.item) { if (visible && spotlightContent) {
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item.searchField) { if (spotlightContent.searchField) {
contentLoader.item.searchField.forceActiveFocus() spotlightContent.searchField.forceActiveFocus()
} }
}) })
} }
@@ -70,7 +99,6 @@ DankModal {
onBackgroundClicked: () => { onBackgroundClicked: () => {
return hide() return hide()
} }
content: spotlightContent
Connections { Connections {
function onCloseAllModalsExcept(excludedModal) { function onCloseAllModalsExcept(excludedModal) {
@@ -98,12 +126,28 @@ DankModal {
return "SPOTLIGHT_TOGGLE_SUCCESS" return "SPOTLIGHT_TOGGLE_SUCCESS"
} }
function openQuery(query: string): string {
spotlightModal.showWithQuery(query)
return "SPOTLIGHT_OPEN_QUERY_SUCCESS"
}
function toggleQuery(query: string): string {
if (spotlightModal.spotlightOpen) {
spotlightModal.hide()
} else {
spotlightModal.showWithQuery(query)
}
return "SPOTLIGHT_TOGGLE_QUERY_SUCCESS"
}
target: "spotlight" target: "spotlight"
} }
spotlightContent: Component {
SpotlightContent { SpotlightContent {
id: spotlightContentInstance
parentModal: spotlightModal parentModal: spotlightModal
} }
}
directContent: spotlightContentInstance
} }

View File

@@ -10,12 +10,16 @@ Rectangle {
property var appLauncher: null property var appLauncher: null
property var contextMenu: null property var contextMenu: null
function resetScroll() {
resultsList.contentY = 0
resultsGrid.contentY = 0
}
width: parent.width width: parent.width
height: parent.height - y height: parent.height - y
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: "transparent"
border.color: Theme.outlineLight clip: true
border.width: 1
DankListView { DankListView {
id: resultsList id: resultsList
@@ -75,9 +79,7 @@ Rectangle {
width: ListView.view.width width: ListView.view.width
height: resultsList.itemHeight height: resultsList.itemHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03) color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium
border.width: ListView.isCurrentItem ? 2 : 1
Row { Row {
anchors.fill: parent anchors.fill: parent
@@ -128,6 +130,8 @@ Rectangle {
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
} }
StyledText { StyledText {
@@ -160,8 +164,9 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
resultsList.itemClicked(index, model) resultsList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton && !model.isPlugin) {
const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y) const globalPos = mapToItem(null, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y) resultsList.itemRightClicked(index, model, modalPos.x, modalPos.y)
} }
} }
@@ -238,9 +243,7 @@ Rectangle {
width: resultsGrid.cellWidth - resultsGrid.cellPadding width: resultsGrid.cellWidth - resultsGrid.cellPadding
height: resultsGrid.cellHeight - resultsGrid.cellPadding height: resultsGrid.cellHeight - resultsGrid.cellPadding
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: resultsGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03) color: resultsGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
border.color: resultsGrid.currentIndex === index ? Theme.primarySelected : Theme.outlineMedium
border.width: resultsGrid.currentIndex === index ? 2 : 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -290,8 +293,8 @@ Rectangle {
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
maximumLineCount: 2 maximumLineCount: 1
wrapMode: Text.WordWrap wrapMode: Text.NoWrap
} }
} }
@@ -313,8 +316,9 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
resultsGrid.itemClicked(index, model) resultsGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton && !model.isPlugin) {
const modalPos = mapToItem(resultsContainer.parent, mouse.x, mouse.y) const globalPos = mapToItem(null, mouse.x, mouse.y)
const modalPos = resultsContainer.parent.mapFromItem(null, globalPos.x, globalPos.y)
resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y) resultsGrid.itemRightClicked(index, model, modalPos.x, modalPos.y)
} }
} }

View File

@@ -9,33 +9,62 @@ DankModal {
property string wifiPasswordSSID: "" property string wifiPasswordSSID: ""
property string wifiPasswordInput: "" property string wifiPasswordInput: ""
property string wifiUsernameInput: ""
property bool requiresEnterprise: false
property string wifiAnonymousIdentityInput: ""
property string wifiDomainInput: ""
function show(ssid) { function show(ssid) {
wifiPasswordSSID = ssid wifiPasswordSSID = ssid
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid)
requiresEnterprise = network?.enterprise || false
open() open()
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordInput) if (contentLoader.item) {
if (requiresEnterprise && contentLoader.item.usernameInput) {
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus() contentLoader.item.passwordInput.forceActiveFocus()
}
}
}) })
} }
shouldBeVisible: false shouldBeVisible: false
width: 420 width: 420
height: 230 height: requiresEnterprise ? 430 : 230
onShouldBeVisibleChanged: () => { onShouldBeVisibleChanged: () => {
if (!shouldBeVisible) if (!shouldBeVisible) {
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
}
} }
onOpened: { onOpened: {
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordInput) if (contentLoader.item) {
if (requiresEnterprise && contentLoader.item.usernameInput) {
contentLoader.item.usernameInput.forceActiveFocus()
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus() contentLoader.item.passwordInput.forceActiveFocus()
}
}
}) })
} }
onBackgroundClicked: () => { onBackgroundClicked: () => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
} }
Connections { Connections {
@@ -55,6 +84,7 @@ DankModal {
FocusScope { FocusScope {
id: wifiContent id: wifiContent
property alias usernameInput: usernameInput
property alias passwordInput: passwordInput property alias passwordInput: passwordInput
anchors.fill: parent anchors.fill: parent
@@ -62,6 +92,9 @@ DankModal {
Keys.onEscapePressed: event => { Keys.onEscapePressed: event => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
event.accepted = true event.accepted = true
} }
@@ -78,14 +111,14 @@ DankModal {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Connect to Wi-Fi" text: I18n.tr("Connect to Wi-Fi")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
StyledText { StyledText {
text: `Enter password for "${wifiPasswordSSID}"` text: requiresEnterprise ? I18n.tr("Enter credentials for ") + wifiPasswordSSID : I18n.tr("Enter password for ") + wifiPasswordSSID
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
width: parent.width width: parent.width
@@ -100,6 +133,46 @@ DankModal {
onClicked: () => { onClicked: () => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
}
}
}
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: usernameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: usernameInput.activeFocus ? 2 : 1
visible: requiresEnterprise
MouseArea {
anchors.fill: parent
onClicked: () => {
usernameInput.forceActiveFocus()
}
}
DankTextField {
id: usernameInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiUsernameInput
placeholderText: I18n.tr("Username")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiUsernameInput = text
}
onAccepted: () => {
if (passwordInput) {
passwordInput.forceActiveFocus()
}
} }
} }
} }
@@ -127,21 +200,32 @@ DankModal {
textColor: Theme.surfaceText textColor: Theme.surfaceText
text: wifiPasswordInput text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
placeholderText: "" placeholderText: requiresEnterprise ? I18n.tr("Password") : ""
backgroundColor: "transparent" backgroundColor: "transparent"
focus: true focus: !requiresEnterprise
enabled: root.shouldBeVisible enabled: root.shouldBeVisible
onTextEdited: () => { onTextEdited: () => {
wifiPasswordInput = text wifiPasswordInput = text
} }
onAccepted: () => { onAccepted: () => {
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text) const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
passwordInput.text = "" passwordInput.text = ""
if (requiresEnterprise) usernameInput.text = ""
} }
Component.onCompleted: () => { Component.onCompleted: () => {
if (root.shouldBeVisible) if (root.shouldBeVisible && !requiresEnterprise)
focusDelayTimer.start() focusDelayTimer.start()
} }
@@ -151,10 +235,15 @@ DankModal {
interval: 100 interval: 100
repeat: false repeat: false
onTriggered: () => { onTriggered: () => {
if (root.shouldBeVisible) if (root.shouldBeVisible) {
if (requiresEnterprise && usernameInput) {
usernameInput.forceActiveFocus()
} else {
passwordInput.forceActiveFocus() passwordInput.forceActiveFocus()
} }
} }
}
}
Connections { Connections {
target: root target: root
@@ -167,6 +256,70 @@ DankModal {
} }
} }
Rectangle {
visible: requiresEnterprise
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: anonInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: anonInput.activeFocus ? 2 : 1
MouseArea {
anchors.fill: parent
onClicked: () => {
anonInput.forceActiveFocus()
}
}
DankTextField {
id: anonInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiAnonymousIdentityInput
placeholderText: I18n.tr("Anonymous Identity (optional)")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiAnonymousIdentityInput = text
}
}
}
Rectangle {
visible: requiresEnterprise
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: domainMatchInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: domainMatchInput.activeFocus ? 2 : 1
MouseArea {
anchors.fill: parent
onClicked: () => {
domainMatchInput.forceActiveFocus()
}
}
DankTextField {
id: domainMatchInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiDomainInput
placeholderText: I18n.tr("Domain (optional)")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiDomainInput = text
}
}
}
Row { Row {
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -201,7 +354,7 @@ DankModal {
} }
StyledText { StyledText {
text: "Show password" text: I18n.tr("Show password")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -229,7 +382,7 @@ DankModal {
id: cancelText id: cancelText
anchors.centerIn: parent anchors.centerIn: parent
text: "Cancel" text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -244,6 +397,9 @@ DankModal {
onClicked: () => { onClicked: () => {
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
} }
} }
} }
@@ -253,14 +409,14 @@ DankModal {
height: 36 height: 36
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: passwordInput.text.length > 0 enabled: requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0
opacity: enabled ? 1 : 0.5 opacity: enabled ? 1 : 0.5
StyledText { StyledText {
id: connectText id: connectText
anchors.centerIn: parent anchors.centerIn: parent
text: "Connect" text: I18n.tr("Connect")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.background color: Theme.background
font.weight: Font.Medium font.weight: Font.Medium
@@ -274,10 +430,21 @@ DankModal {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: () => { onClicked: () => {
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text) const username = requiresEnterprise ? usernameInput.text : ""
NetworkService.connectToWifi(
wifiPasswordSSID,
passwordInput.text,
username,
wifiAnonymousIdentityInput,
wifiDomainInput
)
close() close()
wifiPasswordInput = "" wifiPasswordInput = ""
wifiUsernameInput = ""
wifiAnonymousIdentityInput = ""
wifiDomainInput = ""
passwordInput.text = "" passwordInput.text = ""
if (requiresEnterprise) usernameInput.text = ""
} }
} }

View File

@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -12,7 +13,6 @@ import qs.Widgets
DankPopout { DankPopout {
id: appDrawerPopout id: appDrawerPopout
property string triggerSection: "left"
property var triggerScreen: null property var triggerScreen: null
// Setting to Exclusive, so virtual keyboards can send input to app drawer // Setting to Exclusive, so virtual keyboards can send input to app drawer
@@ -33,16 +33,16 @@ DankPopout {
popupWidth: 520 popupWidth: 520
popupHeight: 600 popupHeight: 600
triggerX: Theme.spacingL triggerX: Theme.spacingL
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS triggerY: Math.max(26 + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding)) + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap - 2
triggerWidth: 40 triggerWidth: 40
positioning: "center" positioning: ""
screen: triggerScreen screen: triggerScreen
onShouldBeVisibleChanged: { onShouldBeVisibleChanged: {
if (shouldBeVisible) { if (shouldBeVisible) {
appLauncher.searchQuery = "" appLauncher.searchQuery = ""
appLauncher.selectedIndex = 0 appLauncher.selectedIndex = 0
appLauncher.setCategory("All") appLauncher.setCategory(I18n.tr("All"))
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) { if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.text = "" contentLoader.item.searchField.text = ""
@@ -95,7 +95,7 @@ DankPopout {
color: "transparent" color: "transparent"
radius: parent.radius + Math.abs(modelData.margin) radius: parent.radius + Math.abs(modelData.margin)
border.color: modelData.color border.color: modelData.color
border.width: 1 border.width: 0
z: modelData.z z: modelData.z
} }
} }
@@ -112,6 +112,8 @@ DankPopout {
mappings[Qt.Key_Up] = () => appLauncher.selectPrevious() mappings[Qt.Key_Up] = () => appLauncher.selectPrevious()
mappings[Qt.Key_Return] = () => appLauncher.launchSelected() mappings[Qt.Key_Return] = () => appLauncher.launchSelected()
mappings[Qt.Key_Enter] = () => appLauncher.launchSelected() mappings[Qt.Key_Enter] = () => appLauncher.launchSelected()
mappings[Qt.Key_Tab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : appLauncher.selectNext()
mappings[Qt.Key_Backtab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : appLauncher.selectPrevious()
if (appLauncher.viewMode === "grid") { if (appLauncher.viewMode === "grid") {
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow() mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow()
@@ -128,38 +130,70 @@ DankPopout {
return return
} }
if (!searchField.activeFocus && event.text && /[a-zA-Z0-9\s]/.test(event.text)) { if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
searchField.forceActiveFocus() appLauncher.selectNext()
searchField.insertText(event.text)
event.accepted = true event.accepted = true
return
} }
if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
appLauncher.selectPrevious()
event.accepted = true
return
}
if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
appLauncher.selectNext()
event.accepted = true
return
}
if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
appLauncher.selectPrevious()
event.accepted = true
return
}
if (appLauncher.viewMode === "grid") {
if (event.key === Qt.Key_L && event.modifiers & Qt.ControlModifier) {
appLauncher.selectNextInRow()
event.accepted = true
return
}
if (event.key === Qt.Key_H && event.modifiers & Qt.ControlModifier) {
appLauncher.selectPreviousInRow()
event.accepted = true
return
}
}
} }
Column { Column {
width: parent.width - Theme.spacingL * 2 width: parent.width - Theme.spacingS * 2
height: parent.height - Theme.spacingL * 2 height: parent.height - Theme.spacingS * 2
x: Theme.spacingL x: Theme.spacingS
y: Theme.spacingL y: Theme.spacingS
spacing: Theme.spacingL spacing: Theme.spacingS
Row { Item {
width: parent.width width: parent.width
height: 40 height: 40
StyledText { StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: "Applications" text: I18n.tr("Applications")
font.pixelSize: Theme.fontSizeLarge + 4 font.pixelSize: Theme.fontSizeLarge + 4
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.surfaceText color: Theme.surfaceText
} }
Item {
width: parent.width - 200
height: 1
}
StyledText { StyledText {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: appLauncher.model.count + " apps" text: appLauncher.model.count + " apps"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -170,7 +204,8 @@ DankPopout {
DankTextField { DankTextField {
id: searchField id: searchField
width: parent.width width: parent.width - Theme.spacingS * 2
anchors.horizontalCenter: parent.horizontalCenter
height: 52 height: 52
cornerRadius: Theme.cornerRadius cornerRadius: Theme.cornerRadius
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7) backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
@@ -183,7 +218,8 @@ DankPopout {
showClearButton: true showClearButton: true
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: appDrawerPopout.shouldBeVisible enabled: appDrawerPopout.shouldBeVisible
ignoreLeftRightKeys: true ignoreLeftRightKeys: appLauncher.viewMode !== "list"
ignoreTabKeys: true
keyForwardTargets: [keyHandler] keyForwardTargets: [keyHandler]
onTextEdited: { onTextEdited: {
appLauncher.searchQuery = text appLauncher.searchQuery = text
@@ -208,7 +244,7 @@ DankPopout {
return return
} }
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right] const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right, Qt.Key_Tab, Qt.Key_Backtab]
const isNavigationKey = navigationKeys.includes(event.key) const isNavigationKey = navigationKeys.includes(event.key)
const isEmptyEnter = isEnterKey && !hasText const isEmptyEnter = isEnterKey && !hasText
@@ -231,14 +267,18 @@ DankPopout {
height: 40 height: 40
spacing: Theme.spacingM spacing: Theme.spacingM
visible: searchField.text.length === 0 visible: searchField.text.length === 0
leftPadding: Theme.spacingS
Item { Rectangle {
width: 200 width: 180
height: 36 height: 40
radius: Theme.cornerRadius
color: "transparent"
DankDropdown { DankDropdown {
anchors.fill: parent anchors.fill: parent
text: "" text: ""
dropdownWidth: 180
currentValue: appLauncher.selectedCategory currentValue: appLauncher.selectedCategory
options: appLauncher.categories options: appLauncher.categories
optionIcons: appLauncher.categoryIcons optionIcons: appLauncher.categoryIcons
@@ -249,7 +289,7 @@ DankPopout {
} }
Item { Item {
width: parent.width - 300 width: parent.width - 290
height: 1 height: 1
} }
@@ -286,15 +326,13 @@ DankPopout {
Rectangle { Rectangle {
width: parent.width width: parent.width
height: { height: {
let usedHeight = 40 + Theme.spacingL let usedHeight = 40 + Theme.spacingS
usedHeight += 52 + Theme.spacingL usedHeight += 52 + Theme.spacingS
usedHeight += (searchField.text.length === 0 ? 40 + Theme.spacingL : 0) usedHeight += (searchField.text.length === 0 ? 40 : 0)
return parent.height - usedHeight return parent.height - usedHeight
} }
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1) color: "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
border.width: 1
DankListView { DankListView {
id: appList id: appList
@@ -323,7 +361,9 @@ DankPopout {
} }
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
visible: appLauncher.viewMode === "list" visible: appLauncher.viewMode === "list"
model: appLauncher.model model: appLauncher.model
currentIndex: appLauncher.selectedIndex currentIndex: appLauncher.selectedIndex
@@ -353,9 +393,7 @@ DankPopout {
width: ListView.view.width width: ListView.view.width
height: appList.itemHeight height: appList.itemHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03) color: ListView.isCurrentItem ? Theme.primaryPressed : listMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium
border.width: ListView.isCurrentItem ? 2 : 1
Row { Row {
anchors.fill: parent anchors.fill: parent
@@ -371,6 +409,7 @@ DankPopout {
id: listIconImg id: listIconImg
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingXS
source: Quickshell.iconPath(model.icon, true) source: Quickshell.iconPath(model.icon, true)
smooth: true smooth: true
asynchronous: true asynchronous: true
@@ -379,10 +418,13 @@ DankPopout {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingM
visible: !listIconImg.visible visible: !listIconImg.visible
color: Theme.surfaceLight color: Theme.surfaceLight
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 1 border.width: 0
border.color: Theme.primarySelected border.color: Theme.primarySelected
StyledText { StyledText {
@@ -407,6 +449,8 @@ DankPopout {
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
} }
StyledText { StyledText {
@@ -425,6 +469,9 @@ DankPopout {
id: listMouseArea id: listMouseArea
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingM
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
@@ -440,7 +487,8 @@ DankPopout {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appList.itemClicked(index, model) appList.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y) var globalPos = mapToItem(null, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appList.itemRightClicked(index, model, panelPos.x, panelPos.y) appList.itemRightClicked(index, model, panelPos.x, panelPos.y)
} }
} }
@@ -484,7 +532,9 @@ DankPopout {
} }
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
visible: appLauncher.viewMode === "grid" visible: appLauncher.viewMode === "grid"
model: appLauncher.model model: appLauncher.model
clip: true clip: true
@@ -516,9 +566,7 @@ DankPopout {
width: appGrid.cellWidth - appGrid.cellPadding width: appGrid.cellWidth - appGrid.cellPadding
height: appGrid.cellHeight - appGrid.cellPadding height: appGrid.cellHeight - appGrid.cellPadding
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: appGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03) color: appGrid.currentIndex === index ? Theme.primaryPressed : gridMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceContainerHigh
border.color: appGrid.currentIndex === index ? Theme.primarySelected : Theme.outlineMedium
border.width: appGrid.currentIndex === index ? 2 : 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -535,6 +583,9 @@ DankPopout {
id: gridIconImg id: gridIconImg
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
source: Quickshell.iconPath(model.icon, true) source: Quickshell.iconPath(model.icon, true)
smooth: true smooth: true
asynchronous: true asynchronous: true
@@ -543,10 +594,13 @@ DankPopout {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
visible: !gridIconImg.visible visible: !gridIconImg.visible
color: Theme.surfaceLight color: Theme.surfaceLight
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 1 border.width: 0
border.color: Theme.primarySelected border.color: Theme.primarySelected
StyledText { StyledText {
@@ -568,8 +622,8 @@ DankPopout {
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
maximumLineCount: 2 maximumLineCount: 1
wrapMode: Text.WordWrap wrapMode: Text.NoWrap
} }
} }
@@ -577,6 +631,9 @@ DankPopout {
id: gridMouseArea id: gridMouseArea
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
@@ -592,7 +649,8 @@ DankPopout {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appGrid.itemClicked(index, model) appGrid.itemClicked(index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y) var globalPos = mapToItem(null, mouse.x, mouse.y)
var panelPos = contextMenu.parent.mapFromItem(null, globalPos.x, globalPos.y)
appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y) appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y)
} }
} }
@@ -605,58 +663,37 @@ DankPopout {
} }
} }
Rectangle { Popup {
id: contextMenu id: contextMenu
property var currentApp: null property var currentApp: null
property bool menuVisible: false readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
readonly property string appId: desktopEntry ? (desktopEntry.id || desktopEntry.execString || "") : ""
readonly property string appId: (currentApp && currentApp.desktopEntry) ? (currentApp.desktopEntry.id || currentApp.desktopEntry.execString || "") : ""
readonly property bool isPinned: appId && SessionData.isPinnedApp(appId) readonly property bool isPinned: appId && SessionData.isPinnedApp(appId)
function show(x, y, app) { function show(x, y, app) {
currentApp = app currentApp = app
contextMenu.x = x + 4
const menuWidth = 180 contextMenu.y = y + 4
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2 contextMenu.open()
let finalX = x + 8
let finalY = y + 8
if (finalX + menuWidth > appDrawerPopout.popupWidth) {
finalX = x - menuWidth - 8
} }
if (finalY + menuHeight > appDrawerPopout.popupHeight) { function hide() {
finalY = y - menuHeight - 8 contextMenu.close()
} }
finalX = Math.max(8, Math.min(finalX, appDrawerPopout.popupWidth - menuWidth - 8)) width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
finalY = Math.max(8, Math.min(finalY, appDrawerPopout.popupHeight - menuHeight - 8))
contextMenu.x = finalX
contextMenu.y = finalY
contextMenu.visible = true
contextMenu.menuVisible = true
}
function close() {
contextMenu.menuVisible = false
Qt.callLater(() => {
contextMenu.visible = false
})
}
visible: false
width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
padding: 0
closePolicy: Popup.CloseOnPressOutside
modal: false
dim: false
background: Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.popupBackground() color: Theme.popupBackground()
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -666,7 +703,28 @@ DankPopout {
anchors.bottomMargin: -4 anchors.bottomMargin: -4
radius: parent.radius radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15) color: Qt.rgba(0, 0, 0, 0.15)
z: parent.z - 1 z: -1
}
}
enter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
exit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
} }
Column { Column {
@@ -697,7 +755,7 @@ DankPopout {
} }
StyledText { StyledText {
text: contextMenu.isPinned ? "Unpin from Dock" : "Pin to Dock" text: contextMenu.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -712,7 +770,7 @@ DankPopout {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) { if (!contextMenu.desktopEntry) {
return return
} }
@@ -721,7 +779,7 @@ DankPopout {
} else { } else {
SessionData.addPinnedApp(contextMenu.appId) SessionData.addPinnedApp(contextMenu.appId)
} }
contextMenu.close() contextMenu.hide()
} }
} }
} }
@@ -740,6 +798,79 @@ DankPopout {
} }
} }
Repeater {
model: contextMenu.desktopEntry && contextMenu.desktopEntry.actions ? contextMenu.desktopEntry.actions : []
Rectangle {
width: Math.max(parent.width, actionRow.implicitWidth + Theme.spacingS * 2)
height: 32
radius: Theme.cornerRadius
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
id: actionRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Item {
anchors.verticalCenter: parent.verticalCenter
width: Theme.iconSize - 2
height: Theme.iconSize - 2
visible: modelData.icon && modelData.icon !== ""
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
}
StyledText {
text: modelData.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: actionMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && contextMenu.desktopEntry) {
SessionService.launchDesktopAction(contextMenu.desktopEntry, modelData)
if (contextMenu.currentApp) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
}
}
}
}
Rectangle {
visible: contextMenu.desktopEntry && contextMenu.desktopEntry.actions && contextMenu.desktopEntry.actions.length > 0
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
@@ -761,7 +892,7 @@ DankPopout {
} }
StyledText { StyledText {
text: "Launch" text: I18n.tr("Launch")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -779,23 +910,72 @@ DankPopout {
if (contextMenu.currentApp) if (contextMenu.currentApp)
appLauncher.launchApp(contextMenu.currentApp) appLauncher.launchApp(contextMenu.currentApp)
contextMenu.close() contextMenu.hide()
}
} }
} }
} }
Behavior on opacity { Rectangle {
NumberAnimation { visible: SessionService.hasPrimeRun
duration: Theme.mediumDuration width: parent.width - Theme.spacingS * 2
easing.type: Theme.emphasizedEasing height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
} }
} }
Behavior on scale { Rectangle {
NumberAnimation { visible: SessionService.hasPrimeRun
duration: Theme.mediumDuration width: parent.width
easing.type: Theme.emphasizedEasing height: 32
radius: Theme.cornerRadius
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "memory"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Launch on dGPU")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: primeRunMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (contextMenu.desktopEntry) {
SessionService.launchDesktopEntry(contextMenu.desktopEntry, true)
if (contextMenu.currentApp) {
appLauncher.appLaunched(contextMenu.currentApp)
}
}
contextMenu.hide()
}
}
} }
} }
} }
@@ -805,7 +985,7 @@ DankPopout {
visible: contextMenu.visible visible: contextMenu.visible
z: 999 z: 999
onClicked: { onClicked: {
contextMenu.close() contextMenu.hide()
} }
MouseArea { MouseArea {

View File

@@ -9,7 +9,7 @@ Item {
id: root id: root
property string searchQuery: "" property string searchQuery: ""
property string selectedCategory: "All" property string selectedCategory: I18n.tr("All")
property string viewMode: "list" // "list" or "grid" property string viewMode: "list" // "list" or "grid"
property int selectedIndex: 0 property int selectedIndex: 0
property int maxResults: 50 property int maxResults: 50
@@ -18,20 +18,46 @@ Item {
property int debounceInterval: 50 property int debounceInterval: 50
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
property bool suppressUpdatesWhileLaunching: false property bool suppressUpdatesWhileLaunching: false
readonly property var categories: { property var categories: {
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science") const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
const result = ["All"] const result = [I18n.tr("All")]
return result.concat(allCategories.filter(cat => cat !== "All")) return result.concat(allCategories.filter(cat => cat !== I18n.tr("All")))
} }
readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category)) readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {} property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
property alias model: filteredModel property alias model: filteredModel
property var _watchApplications: AppSearchService.applications property var _watchApplications: AppSearchService.applications
property var _uniqueApps: []
property bool _isTriggered: false
property string _triggeredCategory: ""
property bool _updatingFromTrigger: false
signal appLaunched(var app) signal appLaunched(var app)
signal categorySelected(string category) signal categorySelected(string category)
signal viewModeSelected(string mode) signal viewModeSelected(string mode)
function updateCategories() {
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
const result = [I18n.tr("All")]
categories = result.concat(allCategories.filter(cat => cat !== I18n.tr("All")))
}
Connections {
target: PluginService
function onPluginLoaded() { updateCategories() }
function onPluginUnloaded() { updateCategories() }
function onPluginListUpdated() { updateCategories() }
}
Connections {
target: SettingsData
function onSortAppsAlphabeticallyChanged() {
updateFilteredModel()
}
}
function updateFilteredModel() { function updateFilteredModel() {
if (suppressUpdatesWhileLaunching) { if (suppressUpdatesWhileLaunching) {
suppressUpdatesWhileLaunching = false suppressUpdatesWhileLaunching = false
@@ -41,12 +67,55 @@ Item {
selectedIndex = 0 selectedIndex = 0
keyboardNavigationActive = false keyboardNavigationActive = false
const triggerResult = checkPluginTriggers(searchQuery)
if (triggerResult.triggered) {
console.log("AppLauncher: Plugin trigger detected:", triggerResult.trigger, "for plugin:", triggerResult.pluginId)
}
let apps = [] let apps = []
if (searchQuery.length === 0) { const allCategory = I18n.tr("All")
apps = selectedCategory === "All" ? AppSearchService.getAppsInCategory("All") : AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults) const emptyTriggerPlugins = typeof PluginService !== "undefined" ? PluginService.getPluginsWithEmptyTrigger() : []
if (triggerResult.triggered) {
_isTriggered = true
_triggeredCategory = triggerResult.pluginCategory
_updatingFromTrigger = true
selectedCategory = triggerResult.pluginCategory
_updatingFromTrigger = false
apps = AppSearchService.getPluginItems(triggerResult.pluginCategory, triggerResult.query)
} else { } else {
if (selectedCategory === "All") { if (_isTriggered) {
_updatingFromTrigger = true
selectedCategory = allCategory
_updatingFromTrigger = false
_isTriggered = false
_triggeredCategory = ""
}
if (searchQuery.length === 0) {
if (selectedCategory === allCategory) {
let emptyTriggerItems = []
emptyTriggerPlugins.forEach(pluginId => {
const plugin = PluginService.getLauncherPlugin(pluginId)
const pluginCategory = plugin.name || pluginId
const items = AppSearchService.getPluginItems(pluginCategory, "")
emptyTriggerItems = emptyTriggerItems.concat(items)
})
apps = AppSearchService.applications.concat(emptyTriggerItems)
} else {
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
}
} else {
if (selectedCategory === allCategory) {
apps = AppSearchService.searchApplications(searchQuery) apps = AppSearchService.searchApplications(searchQuery)
let emptyTriggerItems = []
emptyTriggerPlugins.forEach(pluginId => {
const plugin = PluginService.getLauncherPlugin(pluginId)
const pluginCategory = plugin.name || pluginId
const items = AppSearchService.getPluginItems(pluginCategory, searchQuery)
emptyTriggerItems = emptyTriggerItems.concat(items)
})
apps = apps.concat(emptyTriggerItems)
} else { } else {
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory) const categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
if (categoryApps.length > 0) { if (categoryApps.length > 0) {
@@ -58,8 +127,14 @@ Item {
} }
} }
} }
}
if (searchQuery.length === 0) { if (searchQuery.length === 0) {
if (SettingsData.sortAppsAlphabetically) {
apps = apps.sort((a, b) => {
return (a.name || "").localeCompare(b.name || "")
})
} else {
apps = apps.sort((a, b) => { apps = apps.sort((a, b) => {
const aId = a.id || a.execString || a.exec || "" const aId = a.id || a.execString || a.exec || ""
const bId = b.id || b.execString || b.exec || "" const bId = b.id || b.execString || b.exec || ""
@@ -71,19 +146,33 @@ Item {
return (a.name || "").localeCompare(b.name || "") return (a.name || "").localeCompare(b.name || "")
}) })
} }
}
const seenNames = new Set()
const uniqueApps = []
apps.forEach(app => { apps.forEach(app => {
if (app) { if (app) {
const itemKey = app.name + "|" + (app.execString || app.exec || app.action || "")
if (seenNames.has(itemKey)) {
return
}
seenNames.add(itemKey)
uniqueApps.push(app)
const isPluginItem = app.action !== undefined
filteredModel.append({ filteredModel.append({
"name": app.name || "", "name": app.name || "",
"exec": app.execString || "", "exec": app.execString || app.exec || app.action || "",
"icon": app.icon || "application-x-executable", "icon": app.icon || "application-x-executable",
"comment": app.comment || "", "comment": app.comment || "",
"categories": app.categories || [], "categories": app.categories || [],
"desktopEntry": app "isPlugin": isPluginItem,
"appIndex": uniqueApps.length - 1
}) })
} }
}) })
root._uniqueApps = uniqueApps
} }
function selectNext() { function selectNext() {
@@ -127,13 +216,25 @@ Item {
} }
function launchApp(appData) { function launchApp(appData) {
if (!appData) { if (!appData || typeof appData.appIndex === "undefined" || appData.appIndex < 0 || appData.appIndex >= _uniqueApps.length) {
return return
} }
suppressUpdatesWhileLaunching = true suppressUpdatesWhileLaunching = true
SessionService.launchDesktopEntry(appData.desktopEntry)
const actualApp = _uniqueApps[appData.appIndex]
if (appData.isPlugin) {
const pluginId = getPluginIdForItem(actualApp)
if (pluginId) {
AppSearchService.executePluginItem(actualApp, pluginId)
appLaunched(appData) appLaunched(appData)
AppUsageHistoryData.addAppUsage(appData.desktopEntry) return
}
} else {
SessionService.launchDesktopEntry(actualApp)
appLaunched(appData)
AppUsageHistoryData.addAppUsage(actualApp)
}
} }
function setCategory(category) { function setCategory(category) {
@@ -153,7 +254,12 @@ Item {
updateFilteredModel() updateFilteredModel()
} }
} }
onSelectedCategoryChanged: updateFilteredModel() onSelectedCategoryChanged: {
if (_updatingFromTrigger) {
return
}
updateFilteredModel()
}
onAppUsageRankingChanged: updateFilteredModel() onAppUsageRankingChanged: updateFilteredModel()
on_WatchApplicationsChanged: updateFilteredModel() on_WatchApplicationsChanged: updateFilteredModel()
Component.onCompleted: { Component.onCompleted: {
@@ -171,4 +277,63 @@ Item {
repeat: false repeat: false
onTriggered: updateFilteredModel() onTriggered: updateFilteredModel()
} }
// Plugin trigger system functions
function checkPluginTriggers(query) {
if (!query || typeof PluginService === "undefined") {
return { triggered: false, pluginCategory: "", query: "" }
}
const triggers = PluginService.getAllPluginTriggers()
for (const trigger in triggers) {
if (query.startsWith(trigger)) {
const pluginId = triggers[trigger]
const plugin = PluginService.getLauncherPlugin(pluginId)
if (plugin) {
const remainingQuery = query.substring(trigger.length).trim()
const result = {
triggered: true,
pluginId: pluginId,
pluginCategory: plugin.name || pluginId,
query: remainingQuery,
trigger: trigger
}
return result
}
}
}
return { triggered: false, pluginCategory: "", query: "" }
}
function getPluginIdForItem(item) {
if (!item || !item.categories || typeof PluginService === "undefined") {
return null
}
const launchers = PluginService.getLauncherPlugins()
for (const pluginId in launchers) {
const plugin = launchers[pluginId]
const pluginCategory = plugin.name || pluginId
let hasCategory = false
if (Array.isArray(item.categories)) {
hasCategory = item.categories.includes(pluginCategory)
} else if (item.categories && typeof item.categories.count !== "undefined") {
for (let i = 0; i < item.categories.count; i++) {
if (item.categories.get(i) === pluginCategory) {
hasCategory = true
break
}
}
}
if (hasCategory) {
return pluginId
}
}
return null
}
} }

View File

@@ -1,13 +1,14 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Services
import qs.Widgets import qs.Widgets
Item { Item {
id: root id: root
property var categories: [] property var categories: []
property string selectedCategory: "All" property string selectedCategory: I18n.tr("All")
property bool compact: false property bool compact: false
signal categorySelected(string category) signal categorySelected(string category)
@@ -15,10 +16,9 @@ Item {
readonly property int maxCompactItems: 8 readonly property int maxCompactItems: 8
readonly property int itemHeight: 36 readonly property int itemHeight: 36
readonly property color selectedBorderColor: "transparent" readonly property color selectedBorderColor: "transparent"
readonly property color unselectedBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) readonly property color unselectedBorderColor: "transparent"
function handleCategoryClick(category) { function handleCategoryClick(category) {
selectedCategory = category
categorySelected(category) categorySelected(category)
} }
@@ -42,8 +42,7 @@ Item {
height: root.itemHeight height: root.itemHeight
width: root.getButtonWidth(itemCount, parent.width) width: root.getButtonWidth(itemCount, parent.width)
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: selectedCategory === modelData ? Theme.primary : "transparent" color: selectedCategory === modelData ? Theme.primary : Theme.surfaceContainerHigh
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
@@ -82,7 +81,7 @@ Item {
height: root.itemHeight height: root.itemHeight
width: root.getButtonWidth(itemCount, parent.width) width: root.getButtonWidth(itemCount, parent.width)
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: selectedCategory === modelData ? Theme.primary : "transparent" color: selectedCategory === modelData ? Theme.primary : Theme.surfaceContainerHigh
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
StyledText { StyledText {
@@ -118,7 +117,7 @@ Item {
height: root.itemHeight height: root.itemHeight
width: root.getButtonWidth(itemCount, parent.width) width: root.getButtonWidth(itemCount, parent.width)
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: selectedCategory === modelData ? Theme.primary : "transparent" color: selectedCategory === modelData ? Theme.primary : Theme.surfaceContainerHigh
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
StyledText { StyledText {

View File

@@ -0,0 +1,246 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
Ref {
service: VpnService
}
ccWidgetIcon: VpnService.isBusy ? "sync" : (VpnService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN"
ccWidgetSecondaryText: {
if (!VpnService.connected)
return "Disconnected"
const names = VpnService.activeNames || []
if (names.length <= 1)
return names[0] || "Connected"
return names[0] + " +" + (names.length - 1)
}
ccWidgetIsActive: VpnService.connected
onCcWidgetToggled: {
if (VpnService.connected) {
VpnService.disconnectAllActive()
} else if (VpnService.profiles.length > 0) {
VpnService.connect(VpnService.profiles[0].uuid)
}
}
ccDetailContent: Component {
Rectangle {
id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Column {
id: detailColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
RowLayout {
spacing: Theme.spacingS
width: parent.width
StyledText {
text: {
if (!VpnService.connected)
return "Active: None"
const names = VpnService.activeNames || []
if (names.length <= 1)
return "Active: " + (names[0] || "VPN")
return "Active: " + names[0] + " +" + (names.length - 1)
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Item {
Layout.fillWidth: true
}
Rectangle {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: VpnService.connected
width: 110
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "link_off"
size: Theme.fontSizeSmall
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Disconnect")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: discAllArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.disconnectAllActive()
}
}
}
Rectangle {
height: 1
width: parent.width
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
DankFlickable {
width: parent.width
height: 160
contentHeight: listCol.height
clip: true
Column {
id: listCol
width: parent.width
spacing: Theme.spacingXS
Item {
width: parent.width
height: VpnService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "playlist_remove"
size: 36
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No VPN profiles found")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("Add a VPN in NetworkManager")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Repeater {
model: VpnService.profiles
delegate: Rectangle {
required property var modelData
width: parent ? parent.width : 300
height: 50
radius: Theme.cornerRadius
color: rowArea.containsMouse ? Theme.primaryHoverLight : (VpnService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: VpnService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: VpnService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
Column {
spacing: 2
Layout.alignment: Qt.AlignVCenter
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: VpnService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
}
StyledText {
text: {
if (modelData.type === "wireguard")
return "WireGuard"
const svc = modelData.serviceType || ""
if (svc.indexOf("openvpn") !== -1)
return "OpenVPN"
if (svc.indexOf("wireguard") !== -1)
return "WireGuard (plugin)"
if (svc.indexOf("openconnect") !== -1)
return "OpenConnect"
if (svc.indexOf("fortissl") !== -1 || svc.indexOf("forti") !== -1)
return "Fortinet"
if (svc.indexOf("strongswan") !== -1)
return "IPsec (strongSwan)"
if (svc.indexOf("libreswan") !== -1)
return "IPsec (Libreswan)"
if (svc.indexOf("l2tp") !== -1)
return "L2TP/IPsec"
if (svc.indexOf("pptp") !== -1)
return "PPTP"
if (svc.indexOf("vpnc") !== -1)
return "Cisco (vpnc)"
if (svc.indexOf("sstp") !== -1)
return "SSTP"
if (svc)
return svc.split('.').pop()
return "VPN"
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
Item {
Layout.fillWidth: true
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: VpnService.toggle(modelData.uuid)
}
}
}
}
}
}
}
}
}

View File

@@ -25,8 +25,7 @@ Rectangle {
readonly property color _tileBgActive: Theme.primary readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive: readonly property color _tileBgInactive:
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.surfaceContainerHigh
Theme.getContentBackgroundAlpha() * 0.60)
readonly property color _tileRingActive: readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
@@ -60,7 +59,7 @@ Rectangle {
DankIcon { DankIcon {
name: root.iconName name: root.iconName
size: Theme.iconSize size: Theme.iconSize
color: isActive ? Theme.primaryContainer : Theme.primary color: isActive ? Theme.primaryText : Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -78,7 +77,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.text text: root.text
style: Typography.Style.Body style: Typography.Style.Body
color: isActive ? Theme.primaryContainer : Theme.surfaceText color: isActive ? Theme.primaryText : Theme.surfaceText
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
@@ -87,7 +86,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.secondaryText text: root.secondaryText
style: Typography.Style.Caption style: Typography.Style.Caption
color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText color: isActive ? Theme.primaryText : Theme.surfaceVariantText
visible: text.length > 0 visible: text.length > 0
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap

View File

@@ -1,38 +1,162 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services
import qs.Modules.ControlCenter.Details import qs.Modules.ControlCenter.Details
import qs.Modules.ControlCenter.Models
Item { Item {
id: root id: root
property string expandedSection: "" property string expandedSection: ""
property var expandedWidgetData: null property var expandedWidgetData: null
property var bluetoothCodecSelector: null
property var pluginDetailInstance: null
property var widgetModel: null
property var collapseCallback: null
Loader { Loader {
id: pluginDetailLoader
width: parent.width width: parent.width
height: 250 height: 250
y: Theme.spacingS y: Theme.spacingS
active: parent.height > 0 active: false
property string sectionKey: root.expandedSection sourceComponent: null
sourceComponent: { }
Loader {
id: coreDetailLoader
width: parent.width
height: 250
y: Theme.spacingS
active: false
sourceComponent: null
}
Connections {
target: coreDetailLoader.item
enabled: root.expandedSection.startsWith("brightnessSlider_")
ignoreUnknownSignals: true
function onDeviceNameChanged(newDeviceName) {
if (root.expandedWidgetData && root.expandedWidgetData.id === "brightnessSlider") {
const widgets = SettingsData.controlCenterWidgets || []
const newWidgets = widgets.map(w => {
if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.deviceName = newDeviceName
return updatedWidget
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
if (root.collapseCallback) {
root.collapseCallback()
}
}
}
}
Connections {
target: coreDetailLoader.item
enabled: root.expandedSection.startsWith("diskUsage_")
ignoreUnknownSignals: true
function onMountPathChanged(newMountPath) {
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
const widgets = SettingsData.controlCenterWidgets || []
const newWidgets = widgets.map(w => {
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.mountPath = newMountPath
return updatedWidget
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
if (root.collapseCallback) {
root.collapseCallback()
}
}
}
}
onExpandedSectionChanged: {
if (pluginDetailInstance) {
pluginDetailInstance.destroy()
pluginDetailInstance = null
}
pluginDetailLoader.active = false
coreDetailLoader.active = false
if (!root.expandedSection) {
return
}
if (root.expandedSection.startsWith("builtin_")) {
const builtinId = root.expandedSection
let builtinInstance = null
if (builtinId === "builtin_vpn") {
if (widgetModel?.vpnLoader) {
widgetModel.vpnLoader.active = true
}
builtinInstance = widgetModel.vpnBuiltinInstance
}
if (!builtinInstance || !builtinInstance.ccDetailContent) {
return
}
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent
pluginDetailLoader.active = parent.height > 0
return
}
if (root.expandedSection.startsWith("plugin_")) {
const pluginId = root.expandedSection.replace("plugin_", "")
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
if (!pluginComponent) {
return
}
pluginDetailInstance = pluginComponent.createObject(null)
if (!pluginDetailInstance || !pluginDetailInstance.ccDetailContent) {
if (pluginDetailInstance) {
pluginDetailInstance.destroy()
pluginDetailInstance = null
}
return
}
pluginDetailLoader.sourceComponent = pluginDetailInstance.ccDetailContent
pluginDetailLoader.active = parent.height > 0
return
}
if (root.expandedSection.startsWith("diskUsage_")) {
coreDetailLoader.sourceComponent = diskUsageDetailComponent
coreDetailLoader.active = parent.height > 0
return
}
if (root.expandedSection.startsWith("brightnessSlider_")) {
coreDetailLoader.sourceComponent = brightnessDetailComponent
coreDetailLoader.active = parent.height > 0
return
}
switch (root.expandedSection) { switch (root.expandedSection) {
case "network": case "network":
case "wifi": return networkDetailComponent case "wifi": coreDetailLoader.sourceComponent = networkDetailComponent; break
case "bluetooth": return bluetoothDetailComponent case "bluetooth": coreDetailLoader.sourceComponent = bluetoothDetailComponent; break
case "audioOutput": return audioOutputDetailComponent case "audioOutput": coreDetailLoader.sourceComponent = audioOutputDetailComponent; break
case "audioInput": return audioInputDetailComponent case "audioInput": coreDetailLoader.sourceComponent = audioInputDetailComponent; break
case "battery": return batteryDetailComponent case "battery": coreDetailLoader.sourceComponent = batteryDetailComponent; break
default: default: return
if (root.expandedSection.startsWith("diskUsage_")) {
return diskUsageDetailComponent
}
return null
}
}
onSectionKeyChanged: {
active = false
active = true
} }
coreDetailLoader.active = parent.height > 0
} }
Component { Component {
@@ -42,7 +166,17 @@ Item {
Component { Component {
id: bluetoothDetailComponent id: bluetoothDetailComponent
BluetoothDetail {} BluetoothDetail {
id: bluetoothDetail
onShowCodecSelector: function(device) {
if (root.bluetoothCodecSelector) {
root.bluetoothCodecSelector.show(device)
root.bluetoothCodecSelector.codecSelected.connect(function(deviceAddress, codecName) {
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName)
})
}
}
}
} }
Component { Component {
@@ -65,22 +199,14 @@ Item {
DiskUsageDetail { DiskUsageDetail {
currentMountPath: root.expandedWidgetData?.mountPath || "/" currentMountPath: root.expandedWidgetData?.mountPath || "/"
instanceId: root.expandedWidgetData?.instanceId || "" instanceId: root.expandedWidgetData?.instanceId || ""
}
}
Component {
onMountPathChanged: (newMountPath) => { id: brightnessDetailComponent
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") { BrightnessDetail {
const widgets = SettingsData.controlCenterWidgets || [] currentDeviceName: root.expandedWidgetData?.deviceName || ""
const newWidgets = widgets.map(w => { instanceId: root.expandedWidgetData?.instanceId || ""
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.mountPath = newMountPath
return updatedWidget
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
}
}
} }
} }
} }

View File

@@ -0,0 +1,91 @@
import QtQuick
import qs.Common
import qs.Modules.ControlCenter.Details
Item {
id: root
property string expandedSection: ""
property var expandedWidgetData: null
height: active ? 250 : 0
visible: active
readonly property bool active: expandedSection !== ""
Behavior on height {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Easing.OutCubic
}
}
Loader {
anchors.fill: parent
anchors.topMargin: Theme.spacingS
sourceComponent: {
if (!root.active) return null
if (expandedSection.startsWith("diskUsage_")) {
return diskUsageDetailComponent
}
switch (expandedSection) {
case "wifi": return networkDetailComponent
case "bluetooth": return bluetoothDetailComponent
case "audioOutput": return audioOutputDetailComponent
case "audioInput": return audioInputDetailComponent
case "battery": return batteryDetailComponent
default: return null
}
}
}
Component {
id: networkDetailComponent
NetworkDetail {}
}
Component {
id: bluetoothDetailComponent
BluetoothDetail {}
}
Component {
id: audioOutputDetailComponent
AudioOutputDetail {}
}
Component {
id: audioInputDetailComponent
AudioInputDetail {}
}
Component {
id: batteryDetailComponent
BatteryDetail {}
}
Component {
id: diskUsageDetailComponent
DiskUsageDetail {
currentMountPath: root.expandedWidgetData?.mountPath || "/"
instanceId: root.expandedWidgetData?.instanceId || ""
onMountPathChanged: (newMountPath) => {
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
const widgets = SettingsData.controlCenterWidgets || []
const newWidgets = widgets.map(w => {
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.mountPath = newMountPath
return updatedWidget
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,289 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property bool editMode: false
property var widgetData: null
property int widgetIndex: -1
property bool isSlider: false
property Component widgetComponent: null
property real gridCellWidth: 100
property real gridCellHeight: 60
property int gridColumns: 4
property var gridLayout: null
z: dragArea.drag.active ? 10000 : 1
signal widgetMoved(int fromIndex, int toIndex)
signal removeWidget(int index)
signal toggleWidgetSize(int index)
width: {
const widgetWidth = widgetData?.width || 50
if (widgetWidth <= 25) return gridCellWidth
else if (widgetWidth <= 50) return gridCellWidth * 2
else if (widgetWidth <= 75) return gridCellWidth * 3
else return gridCellWidth * 4
}
height: isSlider ? 16 : gridCellHeight
Rectangle {
id: dragIndicator
anchors.fill: parent
color: "transparent"
border.color: Theme.primary
border.width: dragArea.drag.active ? 2 : 0
radius: Theme.cornerRadius
opacity: dragArea.drag.active ? 0.8 : 1.0
z: dragArea.drag.active ? 10000 : 1
Behavior on border.width {
NumberAnimation { duration: 150 }
}
Behavior on opacity {
NumberAnimation { duration: 150 }
}
}
Loader {
id: widgetLoader
anchors.fill: parent
sourceComponent: widgetComponent
property var widgetData: root.widgetData
property int widgetIndex: root.widgetIndex
property int globalWidgetIndex: root.widgetIndex
property int widgetWidth: root.widgetData?.width || 50
MouseArea {
id: editModeBlocker
anchors.fill: parent
enabled: root.editMode
acceptedButtons: Qt.AllButtons
onPressed: function(mouse) { mouse.accepted = true }
onWheel: function(wheel) { wheel.accepted = true }
z: 100
}
}
MouseArea {
id: dragArea
anchors.fill: parent
enabled: editMode
cursorShape: editMode ? Qt.OpenHandCursor : Qt.PointingHandCursor
drag.target: editMode ? root : null
drag.axis: Drag.XAndYAxis
drag.smoothed: true
onPressed: function(mouse) {
if (editMode) {
cursorShape = Qt.ClosedHandCursor
if (root.gridLayout && root.gridLayout.moveToTop) {
root.gridLayout.moveToTop(root)
}
}
}
onReleased: function(mouse) {
if (editMode) {
cursorShape = Qt.OpenHandCursor
root.snapToGrid()
}
}
}
Drag.active: dragArea.drag.active
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
function swapIndices(i, j) {
if (i === j) return;
const arr = SettingsData.controlCenterWidgets;
if (!arr || i < 0 || j < 0 || i >= arr.length || j >= arr.length) return;
const copy = arr.slice();
const tmp = copy[i];
copy[i] = copy[j];
copy[j] = tmp;
SettingsData.setControlCenterWidgets(copy);
}
function snapToGrid() {
if (!editMode || !gridLayout) return
const globalPos = root.mapToItem(gridLayout, 0, 0)
const cellWidth = gridLayout.width / gridColumns
const cellHeight = gridCellHeight + Theme.spacingS
const centerX = globalPos.x + (root.width / 2)
const centerY = globalPos.y + (root.height / 2)
let targetCol = Math.max(0, Math.floor(centerX / cellWidth))
let targetRow = Math.max(0, Math.floor(centerY / cellHeight))
targetCol = Math.min(targetCol, gridColumns - 1)
const newIndex = findBestInsertionIndex(targetRow, targetCol)
if (newIndex !== widgetIndex && newIndex >= 0 && newIndex < (SettingsData.controlCenterWidgets?.length || 0)) {
swapIndices(widgetIndex, newIndex)
}
}
function findBestInsertionIndex(targetRow, targetCol) {
const widgets = SettingsData.controlCenterWidgets || [];
const n = widgets.length;
if (!n || widgetIndex < 0 || widgetIndex >= n) return -1;
function spanFor(width) {
const w = width ?? 50;
if (w <= 25) return 1;
if (w <= 50) return 2;
if (w <= 75) return 3;
return 4;
}
const cols = gridColumns || 4;
let row = 0, col = 0;
let draggedOrigKey = null;
const pos = [];
for (let i = 0; i < n; i++) {
const span = Math.min(spanFor(widgets[i].width), cols);
if (col + span > cols) {
row++;
col = 0;
}
const startCol = col;
const centerKey = row * cols + (startCol + (span - 1) / 2);
if (i === widgetIndex) {
draggedOrigKey = centerKey;
} else {
pos.push({ index: i, row, startCol, span, centerKey });
}
col += span;
if (col >= cols) {
row++;
col = 0;
}
}
if (pos.length === 0) return -1;
const centerColCoord = targetCol + 0.5;
const targetKey = targetRow * cols + centerColCoord;
for (let k = 0; k < pos.length; k++) {
const p = pos[k];
if (p.row === targetRow && centerColCoord >= p.startCol && centerColCoord < (p.startCol + p.span)) {
return p.index;
}
}
let lo = 0, hi = pos.length - 1;
if (targetKey <= pos[0].centerKey) return pos[0].index;
if (targetKey >= pos[hi].centerKey) return pos[hi].index;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
const mk = pos[mid].centerKey;
if (targetKey < mk) hi = mid - 1;
else if (targetKey > mk) lo = mid + 1;
else return pos[mid].index;
}
const movingUp = (draggedOrigKey != null) ? (targetKey < draggedOrigKey) : false;
return (movingUp ? pos[lo].index : pos[hi].index);
}
Rectangle {
width: 16
height: 16
radius: 8
color: Theme.error
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: -4
visible: editMode
z: 10
DankIcon {
anchors.centerIn: parent
name: "close"
size: 12
color: Theme.primaryText
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: removeWidget(widgetIndex)
}
}
SizeControls {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: -6
visible: editMode
z: 10
currentSize: root.widgetData?.width || 50
isSlider: root.isSlider
widgetIndex: root.widgetIndex
onSizeChanged: (newSize) => {
var widgets = SettingsData.controlCenterWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets[widgetIndex].width = newSize
SettingsData.setControlCenterWidgets(widgets)
}
}
}
Rectangle {
id: dragHandle
width: 16
height: 12
radius: 2
color: Theme.primary
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 4
visible: editMode
z: 15
opacity: dragArea.drag.active ? 1.0 : 0.7
DankIcon {
anchors.centerIn: parent
name: "drag_indicator"
size: 10
color: Theme.primaryText
}
Behavior on opacity {
NumberAnimation { duration: 150 }
}
}
Rectangle {
anchors.fill: parent
color: editMode ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius
border.color: "transparent"
border.width: 0
z: -1
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
}
}

View File

@@ -7,6 +7,7 @@ Row {
id: root id: root
property var availableWidgets: [] property var availableWidgets: []
property Item popoutContent: null
signal addWidget(string widgetId) signal addWidget(string widgetId)
signal resetToDefault() signal resetToDefault()
@@ -19,7 +20,9 @@ Row {
Popup { Popup {
id: addWidgetPopup id: addWidgetPopup
anchors.centerIn: parent parent: popoutContent
x: parent ? Math.round((parent.width - width) / 2) : 0
y: parent ? Math.round((parent.height - height) / 2) : 0
width: 400 width: 400
height: 300 height: 300
modal: true modal: true
@@ -29,7 +32,7 @@ Row {
background: Rectangle { background: Rectangle {
color: Theme.surfaceContainer color: Theme.surfaceContainer
border.color: Theme.primarySelected border.color: Theme.primarySelected
border.width: 1 border.width: 0
radius: Theme.cornerRadius radius: Theme.cornerRadius
} }
@@ -52,7 +55,7 @@ Row {
} }
Typography { Typography {
text: "Add Widget" text: I18n.tr("Add Widget")
style: Typography.Style.Subtitle style: Typography.Style.Subtitle
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -66,15 +69,16 @@ Row {
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
spacing: Theme.spacingS spacing: Theme.spacingS
clip: true
model: root.availableWidgets model: root.availableWidgets
delegate: Rectangle { delegate: Rectangle {
width: 400 - Theme.spacingL * 2 width: 400 - Theme.spacingL * 2
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: widgetMouseArea.containsMouse ? Theme.primaryHover : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: widgetMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 0
Row { Row {
anchors.fill: parent anchors.fill: parent
@@ -138,7 +142,7 @@ Row {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
border.color: Theme.primary border.color: Theme.primary
border.width: 1 border.width: 0
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
@@ -152,7 +156,7 @@ Row {
} }
Typography { Typography {
text: "Add Widget" text: I18n.tr("Add Widget")
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -172,7 +176,7 @@ Row {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12) color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
border.color: Theme.warning border.color: Theme.warning
border.width: 1 border.width: 0
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
@@ -186,7 +190,7 @@ Row {
} }
Typography { Typography {
text: "Defaults" text: I18n.tr("Defaults")
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.warning color: Theme.warning
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -206,7 +210,7 @@ Row {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
border.color: Theme.error border.color: Theme.error
border.width: 1 border.width: 0
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
@@ -220,7 +224,7 @@ Row {
} }
Typography { Typography {
text: "Reset" text: I18n.tr("Reset")
style: Typography.Style.Button style: Typography.Style.Button
color: Theme.error color: Theme.error
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -1,241 +0,0 @@
import QtQuick
import qs.Common
import qs.Widgets
Item {
id: root
property bool editMode: false
property var widgetData: null
property int widgetIndex: -1
property bool showSizeControls: true
property bool isSlider: false
signal removeWidget(int index)
signal toggleWidgetSize(int index)
signal moveWidget(int fromIndex, int toIndex)
// Delete button in top-right
Rectangle {
width: 16
height: 16
radius: 8
color: Theme.error
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: -4
visible: editMode
z: 10
DankIcon {
anchors.centerIn: parent
name: "close"
size: 12
color: Theme.primaryText
}
MouseArea {
anchors.fill: parent
onClicked: root.removeWidget(widgetIndex)
}
}
// Size control buttons in bottom-right
Row {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.margins: -8
spacing: 4
visible: editMode && showSizeControls
z: 10
Rectangle {
width: 24
height: 24
radius: 12
color: (widgetData?.width || 50) === 25 ? Theme.primary : Theme.primaryContainer
border.color: Theme.primary
border.width: 1
visible: !isSlider
StyledText {
anchors.centerIn: parent
text: "25"
font.pixelSize: 10
font.weight: Font.Medium
color: (widgetData?.width || 50) === 25 ? Theme.primaryText : Theme.primary
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
var widgets = SettingsData.controlCenterWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets[widgetIndex].width = 25
SettingsData.setControlCenterWidgets(widgets)
}
}
}
}
Rectangle {
width: 24
height: 24
radius: 12
color: (widgetData?.width || 50) === 50 ? Theme.primary : Theme.primaryContainer
border.color: Theme.primary
border.width: 1
StyledText {
anchors.centerIn: parent
text: "50"
font.pixelSize: 10
font.weight: Font.Medium
color: (widgetData?.width || 50) === 50 ? Theme.primaryText : Theme.primary
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
var widgets = SettingsData.controlCenterWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets[widgetIndex].width = 50
SettingsData.setControlCenterWidgets(widgets)
}
}
}
}
Rectangle {
width: 24
height: 24
radius: 12
color: (widgetData?.width || 50) === 75 ? Theme.primary : Theme.primaryContainer
border.color: Theme.primary
border.width: 1
visible: !isSlider
StyledText {
anchors.centerIn: parent
text: "75"
font.pixelSize: 10
font.weight: Font.Medium
color: (widgetData?.width || 50) === 75 ? Theme.primaryText : Theme.primary
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
var widgets = SettingsData.controlCenterWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets[widgetIndex].width = 75
SettingsData.setControlCenterWidgets(widgets)
}
}
}
}
Rectangle {
width: 24
height: 24
radius: 12
color: (widgetData?.width || 50) === 100 ? Theme.primary : Theme.primaryContainer
border.color: Theme.primary
border.width: 1
StyledText {
anchors.centerIn: parent
text: "100"
font.pixelSize: 9
font.weight: Font.Medium
color: (widgetData?.width || 50) === 100 ? Theme.primaryText : Theme.primary
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
var widgets = SettingsData.controlCenterWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets[widgetIndex].width = 100
SettingsData.setControlCenterWidgets(widgets)
}
}
}
}
}
// Arrow buttons for reordering in top-left
Row {
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 4
spacing: 2
visible: editMode
z: 20
Rectangle {
width: 16
height: 16
radius: 8
color: Theme.surfaceContainer
border.color: Theme.outline
border.width: 1
DankIcon {
anchors.centerIn: parent
name: "keyboard_arrow_left"
size: 12
color: Theme.surfaceText
}
MouseArea {
anchors.fill: parent
enabled: widgetIndex > 0
opacity: enabled ? 1.0 : 0.5
onClicked: root.moveWidget(widgetIndex, widgetIndex - 1)
}
}
Rectangle {
width: 16
height: 16
radius: 8
color: Theme.surfaceContainer
border.color: Theme.outline
border.width: 1
DankIcon {
anchors.centerIn: parent
name: "keyboard_arrow_right"
size: 12
color: Theme.surfaceText
}
MouseArea {
anchors.fill: parent
enabled: widgetIndex < ((SettingsData.controlCenterWidgets?.length ?? 0) - 1)
opacity: enabled ? 1.0 : 0.5
onClicked: root.moveWidget(widgetIndex, widgetIndex + 1)
}
}
}
// Border highlight
Rectangle {
anchors.fill: parent
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
radius: Theme.cornerRadius
border.color: Theme.primary
border.width: editMode ? 1 : 0
visible: editMode
z: -1
Behavior on border.width {
NumberAnimation { duration: Theme.shortDuration }
}
}
}

View File

@@ -6,22 +6,19 @@ import qs.Widgets
Rectangle { Rectangle {
id: root id: root
property bool powerOptionsExpanded: false
property bool editMode: false property bool editMode: false
signal powerActionRequested(string action, string title, string message) signal powerButtonClicked()
signal lockRequested() signal lockRequested()
signal editModeToggled() signal editModeToggled()
signal settingsButtonClicked()
implicitHeight: 70 implicitHeight: 70
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, color: Theme.surfaceContainerHigh
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha() * 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08) Theme.outline.b, 0.08)
border.width: 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -86,13 +83,11 @@ Rectangle {
DankActionButton { DankActionButton {
buttonSize: 36 buttonSize: 36
iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new" iconName: "power_settings_new"
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 4
iconColor: root.powerOptionsExpanded ? Theme.primary : Theme.surfaceText iconColor: Theme.surfaceText
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: { onClicked: root.powerButtonClicked()
root.powerOptionsExpanded = !root.powerOptionsExpanded
}
} }
DankActionButton { DankActionButton {
@@ -102,6 +97,7 @@ Rectangle {
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: { onClicked: {
root.settingsButtonClicked()
settingsModal.show() settingsModal.show()
} }
} }

View File

@@ -1,73 +0,0 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property bool expanded: false
signal powerActionRequested(string action, string title, string message)
implicitHeight: expanded ? 60 : 0
height: implicitHeight
clip: true
Rectangle {
width: parent.width
height: 60
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha() * 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: root.expanded ? 1 : 0
opacity: root.expanded ? 1 : 0
clip: true
Row {
anchors.centerIn: parent
spacing: SessionService.hibernateSupported ? Theme.spacingS : Theme.spacingL
visible: root.expanded
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "logout"
text: "Logout"
onPressed: root.powerActionRequested("logout", "Logout", "Are you sure you want to logout?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "restart_alt"
text: "Restart"
onPressed: root.powerActionRequested("reboot", "Restart", "Are you sure you want to restart?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "bedtime"
text: "Suspend"
onPressed: root.powerActionRequested("suspend", "Suspend", "Are you sure you want to suspend?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "ac_unit"
text: "Hibernate"
visible: SessionService.hibernateSupported
onPressed: root.powerActionRequested("hibernate", "Hibernate", "Are you sure you want to hibernate?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "power_settings_new"
text: "Shutdown"
onPressed: root.powerActionRequested("poweroff", "Shutdown", "Are you sure you want to shutdown?")
}
}
}
}

View File

@@ -0,0 +1,52 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Widgets
Row {
id: root
property int currentSize: 50
property bool isSlider: false
property int widgetIndex: -1
signal sizeChanged(int newSize)
readonly property var availableSizes: isSlider ? [50, 100] : [25, 50, 75, 100]
spacing: 2
Repeater {
model: root.availableSizes
Rectangle {
width: 16
height: 16
radius: 3
color: modelData === root.currentSize ? Theme.primary : Theme.surfaceContainer
border.color: modelData === root.currentSize ? Theme.primary : Theme.outline
border.width: 1
StyledText {
anchors.centerIn: parent
text: modelData.toString()
font.pixelSize: 8
font.weight: Font.Medium
color: modelData === root.currentSize ? Theme.primaryText : Theme.surfaceText
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.currentSize = modelData
root.sizeChanged(modelData)
}
}
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
}
}
}

View File

@@ -1,734 +0,0 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Modules.ControlCenter.Widgets
import qs.Modules.ControlCenter.Components
import "../utils/layout.js" as LayoutUtils
Column {
id: root
property bool editMode: false
property string expandedSection: ""
property int expandedWidgetIndex: -1
property var model: null
property var expandedWidgetData: null
signal expandClicked(var widgetData, int globalIndex)
signal removeWidget(int index)
signal moveWidget(int fromIndex, int toIndex)
signal toggleWidgetSize(int index)
spacing: editMode ? Theme.spacingL : Theme.spacingS
property var currentRowWidgets: []
property real currentRowWidth: 0
property int expandedRowIndex: -1
function calculateRowsAndWidgets() {
return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex)
}
property var layoutResult: {
const dummy = [expandedSection, expandedWidgetIndex, model?.controlCenterWidgets]
return calculateRowsAndWidgets()
}
onLayoutResultChanged: {
expandedRowIndex = layoutResult.expandedRowIndex
}
Repeater {
model: root.layoutResult.rows
Column {
width: root.width
spacing: 0
property int rowIndex: index
property var rowWidgets: modelData
property bool isSliderOnlyRow: {
const widgets = rowWidgets || []
if (widgets.length === 0) return false
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
}
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -12) : 0
bottomPadding: isSliderOnlyRow ? (root.editMode ? 4 : -12) : 0
Flow {
width: parent.width
spacing: Theme.spacingS
Repeater {
model: rowWidgets || []
Item {
property var widgetData: modelData
property int globalWidgetIndex: {
const widgets = SettingsData.controlCenterWidgets || []
for (var i = 0; i < widgets.length; i++) {
if (widgets[i].id === modelData.id) {
if (modelData.id === "diskUsage") {
if (widgets[i].instanceId === modelData.instanceId) {
return i
}
} else {
return i
}
}
}
return -1
}
property int widgetWidth: modelData.width || 50
width: {
const baseWidth = root.width
const spacing = Theme.spacingS
if (widgetWidth <= 25) {
return (baseWidth - spacing * 3) / 4
} else if (widgetWidth <= 50) {
return (baseWidth - spacing) / 2
} else if (widgetWidth <= 75) {
return (baseWidth - spacing * 2) * 0.75
} else {
return baseWidth
}
}
height: 60
Loader {
id: widgetLoader
anchors.fill: parent
property var widgetData: parent.widgetData
property int widgetIndex: parent.globalWidgetIndex
property int globalWidgetIndex: parent.globalWidgetIndex
property int widgetWidth: parent.widgetWidth
sourceComponent: {
const id = modelData.id || ""
if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
return compoundPillComponent
} else if (id === "volumeSlider") {
return audioSliderComponent
} else if (id === "brightnessSlider") {
return brightnessSliderComponent
} else if (id === "inputVolumeSlider") {
return inputAudioSliderComponent
} else if (id === "battery") {
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent
} else if (id === "diskUsage") {
return diskUsagePillComponent
} else {
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
}
}
}
}
}
}
DetailHost {
width: parent.width
height: active ? (250 + Theme.spacingS) : 0
property bool active: {
if (root.expandedSection === "") return false
if (root.expandedSection.startsWith("diskUsage_") && root.expandedWidgetData) {
const expandedInstanceId = root.expandedWidgetData.instanceId
return rowWidgets.some(w => w.id === "diskUsage" && w.instanceId === expandedInstanceId)
}
return rowIndex === root.expandedRowIndex
}
visible: active
expandedSection: root.expandedSection
expandedWidgetData: root.expandedWidgetData
}
}
}
Component {
id: compoundPillComponent
CompoundPill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
width: parent.width
height: 60
iconName: {
switch (widgetData.id || "") {
case "wifi": {
if (NetworkService.wifiToggling) {
return "sync"
}
if (NetworkService.networkStatus === "ethernet") {
return "settings_ethernet"
}
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalIcon
}
if (NetworkService.wifiEnabled) {
return "wifi_off"
}
return "wifi_off"
}
case "bluetooth": {
if (!BluetoothService.available) {
return "bluetooth_disabled"
}
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
return "bluetooth_disabled"
}
const primaryDevice = (() => {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) {
return null
}
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
for (let device of devices) {
if (device && device.connected) {
return device
}
}
return null
})()
if (primaryDevice) {
return BluetoothService.getDeviceIcon(primaryDevice)
}
return "bluetooth"
}
case "audioOutput": {
if (!AudioService.sink) return "volume_off"
let volume = AudioService.sink.audio.volume
let muted = AudioService.sink.audio.muted
if (muted || volume === 0.0) return "volume_off"
if (volume <= 0.33) return "volume_down"
if (volume <= 0.66) return "volume_up"
return "volume_up"
}
case "audioInput": {
if (!AudioService.source) return "mic_off"
let muted = AudioService.source.audio.muted
return muted ? "mic_off" : "mic"
}
default: return widgetDef?.icon || "help"
}
}
primaryText: {
switch (widgetData.id || "") {
case "wifi": {
if (NetworkService.wifiToggling) {
return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
}
if (NetworkService.networkStatus === "ethernet") {
return "Ethernet"
}
if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID) {
return NetworkService.currentWifiSSID
}
if (NetworkService.wifiEnabled) {
return "Not connected"
}
return "WiFi off"
}
case "bluetooth": {
if (!BluetoothService.available) {
return "Bluetooth"
}
if (!BluetoothService.adapter) {
return "No adapter"
}
if (!BluetoothService.adapter.enabled) {
return "Disabled"
}
return "Enabled"
}
case "audioOutput": return AudioService.sink?.description || "No output device"
case "audioInput": return AudioService.source?.description || "No input device"
default: return widgetDef?.text || "Unknown"
}
}
secondaryText: {
switch (widgetData.id || "") {
case "wifi": {
if (NetworkService.wifiToggling) {
return "Please wait..."
}
if (NetworkService.networkStatus === "ethernet") {
return "Connected"
}
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
}
if (NetworkService.wifiEnabled) {
return "Select network"
}
return ""
}
case "bluetooth": {
if (!BluetoothService.available) {
return "No adapters"
}
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
return "Off"
}
const primaryDevice = (() => {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) {
return null
}
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
for (let device of devices) {
if (device && device.connected) {
return device
}
}
return null
})()
if (primaryDevice) {
return primaryDevice.name || primaryDevice.alias || primaryDevice.deviceName || "Connected Device"
}
return "No devices"
}
case "audioOutput": {
if (!AudioService.sink) {
return "Select device"
}
if (AudioService.sink.audio.muted) {
return "Muted"
}
return Math.round(AudioService.sink.audio.volume * 100) + "%"
}
case "audioInput": {
if (!AudioService.source) {
return "Select device"
}
if (AudioService.source.audio.muted) {
return "Muted"
}
return Math.round(AudioService.source.audio.volume * 100) + "%"
}
default: return widgetDef?.description || ""
}
}
isActive: {
switch (widgetData.id || "") {
case "wifi": {
if (NetworkService.wifiToggling) {
return false
}
if (NetworkService.networkStatus === "ethernet") {
return true
}
if (NetworkService.networkStatus === "wifi") {
return true
}
return NetworkService.wifiEnabled
}
case "bluetooth": return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled)
case "audioOutput": return !!(AudioService.sink && !AudioService.sink.audio.muted)
case "audioInput": return !!(AudioService.source && !AudioService.source.audio.muted)
default: return false
}
}
enabled: (widgetDef?.enabled ?? true)
onToggled: {
if (root.editMode) return
switch (widgetData.id || "") {
case "wifi": {
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
NetworkService.toggleWifiRadio()
}
break
}
case "bluetooth": {
if (BluetoothService.available && BluetoothService.adapter) {
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
}
break
}
case "audioOutput": {
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
}
break
}
case "audioInput": {
if (AudioService.source && AudioService.source.audio) {
AudioService.source.audio.muted = !AudioService.source.audio.muted
}
break
}
}
}
onExpandClicked: {
if (root.editMode) return
root.expandClicked(widgetData, widgetIndex)
}
onWheelEvent: function (wheelEvent) {
const id = widgetData.id || ""
if (id === "audioOutput") {
if (!AudioService.sink || !AudioService.sink.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = AudioService.sink.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
AudioService.sink.audio.muted = false
AudioService.sink.audio.volume = newVolume / 100
wheelEvent.accepted = true
} else if (id === "audioInput") {
if (!AudioService.source || !AudioService.source.audio) return
let delta = wheelEvent.angleDelta.y
let currentVolume = AudioService.source.audio.volume * 100
let newVolume
if (delta > 0)
newVolume = Math.min(100, currentVolume + 5)
else
newVolume = Math.max(0, currentVolume - 5)
AudioService.source.audio.muted = false
AudioService.source.audio.volume = newVolume / 100
wheelEvent.accepted = true
}
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: false
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
Component {
id: audioSliderComponent
Item {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
width: parent.width
height: 16
AudioSliderRow {
anchors.centerIn: parent
width: parent.width
height: 14
property color sliderTrackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: true
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
Component {
id: brightnessSliderComponent
Item {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
width: parent.width
height: 16
BrightnessSliderRow {
anchors.centerIn: parent
width: parent.width
height: 14
property color sliderTrackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: true
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
Component {
id: inputAudioSliderComponent
Item {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
width: parent.width
height: 16
InputAudioSliderRow {
anchors.centerIn: parent
width: parent.width
height: 14
property color sliderTrackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: true
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
Component {
id: batteryPillComponent
BatteryPill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
width: parent.width
height: 60
onExpandClicked: {
if (!root.editMode) {
root.expandClicked(widgetData, widgetIndex)
}
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: false
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
Component {
id: smallBatteryComponent
SmallBatteryButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
width: parent.width
height: 48
onClicked: {
if (!root.editMode) {
root.expandClicked(widgetData, widgetIndex)
}
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: false
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
Component {
id: toggleButtonComponent
ToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
width: parent.width
height: 60
iconName: {
switch (widgetData.id || "") {
case "nightMode": return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
case "darkMode": return "contrast"
case "doNotDisturb": return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
case "idleInhibitor": return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
default: return widgetDef?.icon || "help"
}
}
text: {
switch (widgetData.id || "") {
case "nightMode": return "Night Mode"
case "darkMode": return SessionData.isLightMode ? "Light Mode" : "Dark Mode"
case "doNotDisturb": return "Do Not Disturb"
case "idleInhibitor": return SessionService.idleInhibited ? "Keeping Awake" : "Keep Awake"
default: return widgetDef?.text || "Unknown"
}
}
secondaryText: ""
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
isActive: {
switch (widgetData.id || "") {
case "nightMode": return DisplayService.nightModeEnabled || false
case "darkMode": return !SessionData.isLightMode
case "doNotDisturb": return SessionData.doNotDisturb || false
case "idleInhibitor": return SessionService.idleInhibited || false
default: return false
}
}
enabled: (widgetDef?.enabled ?? true) && !root.editMode
onClicked: {
switch (widgetData.id || "") {
case "nightMode": {
if (DisplayService.automationAvailable) {
DisplayService.toggleNightMode()
}
break
}
case "darkMode": {
Theme.toggleLightMode()
break
}
case "doNotDisturb": {
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
break
}
case "idleInhibitor": {
SessionService.toggleIdleInhibit()
break
}
}
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: false
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
Component {
id: smallToggleComponent
SmallToggleButton {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
width: parent.width
height: 48
iconName: {
switch (widgetData.id || "") {
case "nightMode": return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
case "darkMode": return "contrast"
case "doNotDisturb": return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
case "idleInhibitor": return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
default: return widgetDef?.icon || "help"
}
}
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
isActive: {
switch (widgetData.id || "") {
case "nightMode": return DisplayService.nightModeEnabled || false
case "darkMode": return !SessionData.isLightMode
case "doNotDisturb": return SessionData.doNotDisturb || false
case "idleInhibitor": return SessionService.idleInhibited || false
default: return false
}
}
enabled: (widgetDef?.enabled ?? true) && !root.editMode
onClicked: {
switch (widgetData.id || "") {
case "nightMode": {
if (DisplayService.automationAvailable) {
DisplayService.toggleNightMode()
}
break
}
case "darkMode": {
Theme.toggleLightMode()
break
}
case "doNotDisturb": {
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
break
}
case "idleInhibitor": {
SessionService.toggleIdleInhibit()
break
}
}
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: false
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
Component {
id: diskUsagePillComponent
DiskUsagePill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
width: parent.width
height: 60
mountPath: widgetData.mountPath || "/"
instanceId: widgetData.instanceId || ""
onExpandClicked: {
if (!root.editMode) {
root.expandClicked(widgetData, widgetIndex)
}
}
EditModeOverlay {
anchors.fill: parent
editMode: root.editMode
widgetData: parent.widgetData
widgetIndex: parent.widgetIndex
showSizeControls: true
isSlider: false
onRemoveWidget: (index) => root.removeWidget(index)
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
}
}
}
}

View File

@@ -10,7 +10,7 @@ import qs.Common
import qs.Modules.ControlCenter import qs.Modules.ControlCenter
import qs.Modules.ControlCenter.Widgets import qs.Modules.ControlCenter.Widgets
import qs.Modules.ControlCenter.Details import qs.Modules.ControlCenter.Details
import qs.Modules.TopBar import qs.Modules.DankBar
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.ControlCenter.Components import qs.Modules.ControlCenter.Components
@@ -21,14 +21,11 @@ DankPopout {
id: root id: root
property string expandedSection: "" property string expandedSection: ""
property bool powerOptionsExpanded: false
property string triggerSection: "right"
property var triggerScreen: null property var triggerScreen: null
property bool editMode: false property bool editMode: false
property int expandedWidgetIndex: -1 property int expandedWidgetIndex: -1
property var expandedWidgetData: null property var expandedWidgetData: null
signal powerActionRequested(string action, string title, string message)
signal lockRequested signal lockRequested
function collapseAll() { function collapseAll() {
@@ -49,7 +46,7 @@ DankPopout {
} }
} }
readonly property color _containerBg: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60) readonly property color _containerBg: Theme.surfaceContainerHigh
function setTriggerPosition(x, y, width, section, screen) { function setTriggerPosition(x, y, width, section, screen) {
StateUtils.setTriggerPosition(root, x, y, width, section, screen) StateUtils.setTriggerPosition(root, x, y, width, section, screen)
@@ -66,9 +63,9 @@ DankPopout {
popupWidth: 550 popupWidth: 550
popupHeight: Math.min((triggerScreen?.height ?? 1080) - 100, contentLoader.item && contentLoader.item.implicitHeight > 0 ? contentLoader.item.implicitHeight + 20 : 400) popupHeight: Math.min((triggerScreen?.height ?? 1080) - 100, contentLoader.item && contentLoader.item.implicitHeight > 0 ? contentLoader.item.implicitHeight + 20 : 400)
triggerX: (triggerScreen?.width ?? 1920) - 600 - Theme.spacingL triggerX: (triggerScreen?.width ?? 1920) - 600 - Theme.spacingL
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS triggerY: Theme.barHeight - 4 + SettingsData.dankBarSpacing
triggerWidth: 80 triggerWidth: 80
positioning: "center" positioning: ""
screen: triggerScreen screen: triggerScreen
shouldBeVisible: false shouldBeVisible: false
visible: shouldBeVisible visible: shouldBeVisible
@@ -76,13 +73,17 @@ DankPopout {
onShouldBeVisibleChanged: { onShouldBeVisibleChanged: {
if (shouldBeVisible) { if (shouldBeVisible) {
Qt.callLater(() => { Qt.callLater(() => {
NetworkService.autoRefreshEnabled = NetworkService.wifiEnabled if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled
}
if (UserInfoService) if (UserInfoService)
UserInfoService.getUptime() UserInfoService.getUptime()
}) })
} else { } else {
Qt.callLater(() => { Qt.callLater(() => {
NetworkService.autoRefreshEnabled = false if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = false
}
if (BluetoothService.adapter && BluetoothService.adapter.discovering) if (BluetoothService.adapter && BluetoothService.adapter.discovering)
BluetoothService.adapter.discovering = false BluetoothService.adapter.discovering = false
editMode = false editMode = false
@@ -102,14 +103,14 @@ DankPopout {
property alias bluetoothCodecSelector: bluetoothCodecSelector property alias bluetoothCodecSelector: bluetoothCodecSelector
color: { color: {
const transparency = Theme.popupTransparency || 0.92 const transparency = Theme.popupTransparency
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1) const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
return Qt.rgba(surface.r, surface.g, surface.b, transparency) return Qt.rgba(surface.r, surface.g, surface.b, transparency)
} }
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08) Theme.outline.b, 0.08)
border.width: 1 border.width: 0
antialiasing: true antialiasing: true
smooth: true smooth: true
@@ -123,29 +124,28 @@ DankPopout {
HeaderPane { HeaderPane {
id: headerPane id: headerPane
width: parent.width width: parent.width
powerOptionsExpanded: root.powerOptionsExpanded
editMode: root.editMode editMode: root.editMode
onPowerOptionsExpandedChanged: root.powerOptionsExpanded = powerOptionsExpanded
onEditModeToggled: root.editMode = !root.editMode onEditModeToggled: root.editMode = !root.editMode
onPowerActionRequested: (action, title, message) => root.powerActionRequested(action, title, message) onPowerButtonClicked: {
if (powerMenuModalLoader) {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item) {
const popoutPos = controlContent.mapToItem(null, 0, 0)
const bounds = Qt.rect(popoutPos.x, popoutPos.y, controlContent.width, controlContent.height)
powerMenuModalLoader.item.openFromControlCenter(bounds, root.triggerScreen)
}
}
}
onLockRequested: { onLockRequested: {
root.close() root.close()
root.lockRequested() root.lockRequested()
} }
} onSettingsButtonClicked: {
PowerOptionsPane {
id: powerOptionsPane
width: parent.width
expanded: root.powerOptionsExpanded
onPowerActionRequested: (action, title, message) => {
root.powerOptionsExpanded = false
root.close() root.close()
root.powerActionRequested(action, title, message)
} }
} }
WidgetGrid { DragDropGrid {
id: widgetGrid id: widgetGrid
width: parent.width width: parent.width
editMode: root.editMode editMode: root.editMode
@@ -153,11 +153,15 @@ DankPopout {
expandedWidgetIndex: root.expandedWidgetIndex expandedWidgetIndex: root.expandedWidgetIndex
expandedWidgetData: root.expandedWidgetData expandedWidgetData: root.expandedWidgetData
model: widgetModel model: widgetModel
bluetoothCodecSelector: bluetoothCodecSelector
colorPickerModal: root.colorPickerModal
onExpandClicked: (widgetData, globalIndex) => { onExpandClicked: (widgetData, globalIndex) => {
root.expandedWidgetIndex = globalIndex root.expandedWidgetIndex = globalIndex
root.expandedWidgetData = widgetData root.expandedWidgetData = widgetData
if (widgetData.id === "diskUsage") { if (widgetData.id === "diskUsage") {
root.toggleSection("diskUsage_" + (widgetData.instanceId || "default")) root.toggleSection("diskUsage_" + (widgetData.instanceId || "default"))
} else if (widgetData.id === "brightnessSlider") {
root.toggleSection("brightnessSlider_" + (widgetData.instanceId || "default"))
} else { } else {
root.toggleSection(widgetData.id) root.toggleSection(widgetData.id)
} }
@@ -165,14 +169,18 @@ DankPopout {
onRemoveWidget: (index) => widgetModel.removeWidget(index) onRemoveWidget: (index) => widgetModel.removeWidget(index)
onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex) onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex)
onToggleWidgetSize: (index) => widgetModel.toggleWidgetSize(index) onToggleWidgetSize: (index) => widgetModel.toggleWidgetSize(index)
onCollapseRequested: root.collapseAll()
} }
EditControls { EditControls {
width: parent.width width: parent.width
visible: editMode visible: editMode
popoutContent: controlContent
availableWidgets: { availableWidgets: {
if (!editMode) return []
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id) const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id)
return widgetModel.baseWidgetDefinitions.filter(w => w.allowMultiple || !existingIds.includes(w.id)) const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets())
return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id))
} }
onAddWidget: (widgetId) => widgetModel.addWidget(widgetId) onAddWidget: (widgetId) => widgetModel.addWidget(widgetId)
onResetToDefault: () => widgetModel.resetToDefault() onResetToDefault: () => widgetModel.resetToDefault()
@@ -222,4 +230,7 @@ DankPopout {
id: batteryDetailComponent id: batteryDetailComponent
BatteryDetail {} BatteryDetail {}
} }
property var colorPickerModal: null
property var powerMenuModalLoader: null
} }

View File

@@ -14,9 +14,9 @@ Rectangle {
implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6) color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 0
Row { Row {
id: headerRow id: headerRow
@@ -30,7 +30,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: "Input Devices" text: I18n.tr("Input Devices")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -57,10 +57,6 @@ Rectangle {
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
@@ -138,9 +134,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, index % 2 === 0 ? 0.3 : 0.2) color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData === AudioService.source ? 2 : 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -198,14 +194,6 @@ Rectangle {
} }
} }
} }
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
Behavior on border.color {
ColorAnimation { duration: Theme.shortDuration }
}
} }
} }
} }

View File

@@ -14,9 +14,9 @@ Rectangle {
implicitHeight: headerRow.height + (!hasVolumeSliderInCC ? volumeSlider.height : 0) + audioContent.height + Theme.spacingM implicitHeight: headerRow.height + (!hasVolumeSliderInCC ? volumeSlider.height : 0) + audioContent.height + Theme.spacingM
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6) color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 0
Row { Row {
id: headerRow id: headerRow
@@ -30,7 +30,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: "Audio Devices" text: I18n.tr("Audio Devices")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -57,10 +57,6 @@ Rectangle {
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
@@ -143,9 +139,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, index % 2 === 0 ? 0.3 : 0.2) color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData === AudioService.sink ? 2 : 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -205,14 +201,6 @@ Rectangle {
} }
} }
} }
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
Behavior on border.color {
ColorAnimation { duration: Theme.shortDuration }
}
} }
} }
} }

View File

@@ -9,9 +9,9 @@ import qs.Widgets
Rectangle { Rectangle {
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2 implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6) color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 0
function isActiveProfile(profile) { function isActiveProfile(profile) {
if (typeof PowerProfiles === "undefined") { if (typeof PowerProfiles === "undefined") {
@@ -125,16 +125,15 @@ Rectangle {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 64 height: 64
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) color: Theme.surfaceContainerHighest
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) border.width: 0
border.width: 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Health" text: I18n.tr("Health")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -161,16 +160,15 @@ Rectangle {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 64 height: 64
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) color: Theme.surfaceContainerHighest
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) border.width: 0
border.width: 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Capacity" text: I18n.tr("Capacity")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -211,7 +209,7 @@ Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
border.color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3) border.color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
border.width: 1 border.width: 0
visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None
Column { Column {
@@ -239,7 +237,7 @@ Rectangle {
width: parent.width - Theme.iconSize - Theme.spacingM width: parent.width - Theme.iconSize - Theme.spacingM
StyledText { StyledText {
text: "Power Profile Degradation" text: I18n.tr("Power Profile Degradation")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.error color: Theme.error
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -124,7 +124,7 @@ Item {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainer color: Theme.surfaceContainer
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 0
opacity: modalVisible ? 1 : 0 opacity: modalVisible ? 1 : 0
scale: modalVisible ? 1 : 0.9 scale: modalVisible ? 1 : 0.9
@@ -170,7 +170,7 @@ Item {
} }
StyledText { StyledText {
text: "Audio Codec Selection" text: I18n.tr("Audio Codec Selection")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
} }
@@ -206,14 +206,14 @@ Item {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (modelData.name === currentCodec) if (modelData.name === currentCodec)
return Theme.surfaceContainerHigh; return Theme.surfaceContainerHighest;
else if (codecMouseArea.containsMouse) else if (codecMouseArea.containsMouse)
return Theme.surfaceHover; return Theme.surfaceHover;
else else
return "transparent"; return "transparent";
} }
border.color: "transparent" border.color: "transparent"
border.width: 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -272,12 +272,6 @@ Item {
} }
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
} }

View File

@@ -9,9 +9,9 @@ import qs.Widgets
Rectangle { Rectangle {
implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height implicitHeight: BluetoothService.adapter && BluetoothService.adapter.enabled ? headerRow.height + bluetoothContent.height + Theme.spacingM : headerRow.height
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6) color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 0
property var bluetoothCodecModalRef: null property var bluetoothCodecModalRef: null
@@ -39,7 +39,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: "Bluetooth Settings" text: I18n.tr("Bluetooth Settings")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -58,11 +58,11 @@ Rectangle {
radius: 18 radius: 18
color: { color: {
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) return Theme.surfaceContainerHigh
return scanMouseArea.containsMouse ? Theme.surfaceContainerHigh : "transparent" return scanMouseArea.containsMouse ? Theme.surfaceContainerHigh : "transparent"
} }
border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: BluetoothService.adapter && BluetoothService.adapter.enabled ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1 border.width: 0
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Row { Row {
@@ -96,13 +96,6 @@ Rectangle {
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering
} }
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
} }
@@ -159,7 +152,7 @@ Rectangle {
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12) return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
if (deviceMouseArea.containsMouse) if (deviceMouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, index % 2 === 0 ? 0.3 : 0.2) return Theme.surfaceContainerHighest
} }
border.color: { border.color: {
if (modelData.state === BluetoothDeviceState.Connecting) if (modelData.state === BluetoothDeviceState.Connecting)
@@ -168,7 +161,7 @@ Rectangle {
return Theme.primary return Theme.primary
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
} }
border.width: (modelData.connected || modelData.state === BluetoothDeviceState.Connecting) ? 2 : 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -284,14 +277,6 @@ Rectangle {
} }
} }
} }
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
Behavior on border.color {
ColorAnimation { duration: Theme.shortDuration }
}
} }
} }
@@ -347,9 +332,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.15) color: availableMouseArea.containsMouse && !isBusy ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1 border.width: 0
opacity: canConnect ? 1 : 0.6 opacity: canConnect ? 1 : 0.6
Row { Row {
@@ -427,9 +412,6 @@ Rectangle {
} }
} }
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
} }
} }
@@ -440,7 +422,7 @@ Rectangle {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: "No Bluetooth adapter found" text: I18n.tr("No Bluetooth adapter found")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
@@ -458,7 +440,7 @@ Rectangle {
background: Rectangle { background: Rectangle {
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 1 border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
} }
@@ -491,7 +473,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: "Audio Codec" text: I18n.tr("Audio Codec")
height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0 height: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected ? 32 : 0
visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected visible: bluetoothContextMenu.currentDevice && BluetoothService.isAudioDevice(bluetoothContextMenu.currentDevice) && bluetoothContextMenu.currentDevice.connected
@@ -516,7 +498,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: "Forget Device" text: I18n.tr("Forget Device")
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {

View File

@@ -0,0 +1,174 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: root
property string currentDeviceName: ""
property string instanceId: ""
signal deviceNameChanged(string newDeviceName)
implicitHeight: brightnessContent.height + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
DankFlickable {
id: brightnessContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
anchors.topMargin: Theme.spacingM
contentHeight: brightnessColumn.height
clip: true
Column {
id: brightnessColumn
width: parent.width
spacing: Theme.spacingS
Item {
width: parent.width
height: 100
visible: !DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingM
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
name: DisplayService.brightnessAvailable ? "brightness_6" : "error"
size: 32
color: DisplayService.brightnessAvailable ? Theme.primary : Theme.error
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: DisplayService.brightnessAvailable ? "No brightness devices available" : "Brightness control not available"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
}
}
}
Repeater {
model: DisplayService.devices || []
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 80
radius: Theme.cornerRadius
color: Theme.surfaceContainerHighest
border.color: modelData.name === currentDeviceName ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.name === currentDeviceName ? 2 : 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingM
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
DankIcon {
name: {
const deviceClass = modelData.class || ""
const deviceName = modelData.name || ""
if (deviceClass === "backlight" || deviceClass === "ddc") {
const brightness = modelData.percentage || 50
if (brightness <= 33) return "brightness_low"
if (brightness <= 66) return "brightness_medium"
return "brightness_high"
} else if (deviceName.includes("kbd")) {
return "keyboard"
} else {
return "lightbulb"
}
}
size: Theme.iconSize
color: modelData.name === currentDeviceName ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: (modelData.percentage || 50) + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - parent.parent.anchors.leftMargin - parent.spacing - 50 - Theme.spacingM
StyledText {
text: {
const name = modelData.name || ""
const deviceClass = modelData.class || ""
if (deviceClass === "backlight") {
return name.replace("_", " ").replace(/\b\w/g, c => c.toUpperCase())
}
return name
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData.name === currentDeviceName ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: {
const deviceClass = modelData.class || ""
if (deviceClass === "backlight") return "Backlight device"
if (deviceClass === "ddc") return "DDC/CI monitor"
if (deviceClass === "leds") return "LED device"
return deviceClass
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
currentDeviceName = modelData.name
deviceNameChanged(modelData.name)
}
}
}
}
}
}
}

View File

@@ -6,6 +6,8 @@ import qs.Services
import qs.Widgets import qs.Widgets
Rectangle { Rectangle {
id: root
property string currentMountPath: "/" property string currentMountPath: "/"
property string instanceId: "" property string instanceId: ""
@@ -13,9 +15,9 @@ Rectangle {
implicitHeight: diskContent.height + Theme.spacingM implicitHeight: diskContent.height + Theme.spacingM
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6) color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 0
Component.onCompleted: { Component.onCompleted: {
DgopService.addRef(["diskmounts"]) DgopService.addRef(["diskmounts"])
@@ -76,9 +78,9 @@ Rectangle {
width: parent.width width: parent.width
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, index % 2 === 0 ? 0.3 : 0.2) color: Theme.surfaceContainerHighest
border.color: modelData.mount === currentMountPath ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: modelData.mount === currentMountPath ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.mount === currentMountPath ? 2 : 1 border.width: modelData.mount === currentMountPath ? 2 : 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -152,16 +154,11 @@ Rectangle {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (modelData.mount !== currentMountPath) {
currentMountPath = modelData.mount currentMountPath = modelData.mount
mountPathChanged(modelData.mount) mountPathChanged(modelData.mount)
} }
} }
}
Behavior on border.color {
ColorAnimation { duration: Theme.shortDuration }
}
} }
} }
} }

View File

@@ -17,30 +17,18 @@ Rectangle {
return headerRow.height + wifiOffContent.height + Theme.spacingM return headerRow.height + wifiOffContent.height + Theme.spacingM
} }
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6) color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 0
Component.onCompleted: { Component.onCompleted: {
NetworkService.addRef() NetworkService.addRef()
if (NetworkService.wifiEnabled) {
NetworkService.scanWifi()
}
} }
Component.onDestruction: { Component.onDestruction: {
NetworkService.removeRef() NetworkService.removeRef()
} }
property var wifiPasswordModalRef: {
wifiPasswordModalLoader.active = true
return wifiPasswordModalLoader.item
}
property var networkInfoModalRef: {
networkInfoModalLoader.active = true
return networkInfoModalLoader.item
}
Row { Row {
id: headerRow id: headerRow
anchors.left: parent.left anchors.left: parent.left
@@ -53,7 +41,7 @@ Rectangle {
StyledText { StyledText {
id: headerText id: headerText
text: "Network Settings" text: I18n.tr("Network Settings")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -68,15 +56,30 @@ Rectangle {
DankButtonGroup { DankButtonGroup {
id: preferenceControls id: preferenceControls
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.ethernetConnected && NetworkService.wifiConnected visible: NetworkService.ethernetConnected
property int currentPreferenceIndex: NetworkService.userPreference === "ethernet" ? 0 : 1 property int currentPreferenceIndex: {
const pref = NetworkService.userPreference
const status = NetworkService.networkStatus
let index = 1
if (pref === "ethernet") {
index = 0
} else if (pref === "wifi") {
index = 1
} else {
index = status === "ethernet" ? 0 : 1
}
return index
}
model: ["Ethernet", "WiFi"] model: ["Ethernet", "WiFi"]
currentIndex: currentPreferenceIndex currentIndex: currentPreferenceIndex
selectionMode: "single" selectionMode: "single"
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (!selected) return if (!selected) return
console.log("NetworkDetail: Setting preference to", index === 0 ? "ethernet" : "wifi")
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi") NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi")
} }
} }
@@ -145,7 +148,7 @@ Rectangle {
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: "WiFi is off" text: I18n.tr("WiFi is off")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -158,12 +161,12 @@ Rectangle {
height: 36 height: 36
radius: 18 radius: 18
color: enableWifiButton.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) color: enableWifiButton.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
border.width: 1 border.width: 0
border.color: Theme.primary border.color: Theme.primary
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: "Enable WiFi" text: I18n.tr("Enable WiFi")
color: Theme.primary color: Theme.primary
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
@@ -177,12 +180,6 @@ Rectangle {
onClicked: NetworkService.toggleWifiRadio() onClicked: NetworkService.toggleWifiRadio()
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
} }
} }
@@ -207,7 +204,7 @@ Rectangle {
Item { Item {
width: parent.width width: parent.width
height: 200 height: 200
visible: NetworkService.wifiInterface && NetworkService.wifiNetworks?.length < 1 && !NetworkService.wifiToggling visible: NetworkService.wifiInterface && NetworkService.wifiNetworks?.length < 1 && !NetworkService.wifiToggling && NetworkService.isScanning
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -216,7 +213,7 @@ Rectangle {
color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.3) color: Qt.rgba(Theme.surfaceText.r || 0.8, Theme.surfaceText.g || 0.8, Theme.surfaceText.b || 0.8, 0.3)
RotationAnimation on rotation { RotationAnimation on rotation {
running: true running: NetworkService.isScanning
loops: Animation.Infinite loops: Animation.Infinite
from: 0 from: 0
to: 360 to: 360
@@ -226,14 +223,18 @@ Rectangle {
} }
Repeater { Repeater {
model: { model: sortedNetworks
let networks = [...NetworkService.wifiNetworks]
networks.sort((a, b) => { property var sortedNetworks: {
if (a.ssid === NetworkService.currentWifiSSID) return -1 const ssid = NetworkService.currentWifiSSID
if (b.ssid === NetworkService.currentWifiSSID) return 1 const networks = NetworkService.wifiNetworks
let sorted = [...networks]
sorted.sort((a, b) => {
if (a.ssid === ssid) return -1
if (b.ssid === ssid) return 1
return b.signal - a.signal return b.signal - a.signal
}) })
return networks return sorted
} }
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
@@ -242,9 +243,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, index % 2 === 0 ? 0.3 : 0.2) color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.surfaceContainerHighest
border.color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.ssid === NetworkService.currentWifiSSID ? 2 : 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -332,9 +333,7 @@ Rectangle {
onClicked: function(event) { onClicked: function(event) {
if (modelData.ssid !== NetworkService.currentWifiSSID) { if (modelData.ssid !== NetworkService.currentWifiSSID) {
if (modelData.secured && !modelData.saved) { if (modelData.secured && !modelData.saved) {
if (wifiPasswordModalRef) { wifiPasswordModal.show(modelData.ssid)
wifiPasswordModalRef.show(modelData.ssid)
}
} else { } else {
NetworkService.connectToWifi(modelData.ssid) NetworkService.connectToWifi(modelData.ssid)
} }
@@ -343,13 +342,6 @@ Rectangle {
} }
} }
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
Behavior on border.color {
ColorAnimation { duration: Theme.shortDuration }
}
} }
} }
} }
@@ -369,7 +361,7 @@ Rectangle {
background: Rectangle { background: Rectangle {
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 1 border.width: 0
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
} }
@@ -395,9 +387,7 @@ Rectangle {
NetworkService.disconnectWifi() NetworkService.disconnectWifi()
} else { } else {
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) { if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
if (wifiPasswordModalRef) { wifiPasswordModal.show(networkContextMenu.currentSSID)
wifiPasswordModalRef.show(networkContextMenu.currentSSID)
}
} else { } else {
NetworkService.connectToWifi(networkContextMenu.currentSSID) NetworkService.connectToWifi(networkContextMenu.currentSSID)
} }
@@ -406,7 +396,7 @@ Rectangle {
} }
MenuItem { MenuItem {
text: "Network Info" text: I18n.tr("Network Info")
height: 32 height: 32
contentItem: StyledText { contentItem: StyledText {
@@ -423,15 +413,13 @@ Rectangle {
} }
onTriggered: { onTriggered: {
if (networkInfoModalRef) {
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID) let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID)
networkInfoModalRef.showNetworkInfo(networkContextMenu.currentSSID, networkData) networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData)
}
} }
} }
MenuItem { MenuItem {
text: "Forget Network" text: I18n.tr("Forget Network")
height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0 height: networkContextMenu.currentSaved || networkContextMenu.currentConnected ? 32 : 0
visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected visible: networkContextMenu.currentSaved || networkContextMenu.currentConnected
@@ -454,23 +442,13 @@ Rectangle {
} }
} }
LazyLoader {
id: wifiPasswordModalLoader
active: false
WifiPasswordModal { WifiPasswordModal {
id: wifiPasswordModal id: wifiPasswordModal
} }
}
LazyLoader {
id: networkInfoModalLoader
active: false
NetworkInfoModal { NetworkInfoModal {
id: networkInfoModal id: networkInfoModal
} }
}
} }

View File

@@ -1,13 +1,38 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Modules.ControlCenter.BuiltinPlugins
import "../utils/widgets.js" as WidgetUtils import "../utils/widgets.js" as WidgetUtils
QtObject { QtObject {
id: root id: root
readonly property var baseWidgetDefinitions: [ property var vpnBuiltinInstance: null
{
property var vpnLoader: Loader {
active: false
sourceComponent: Component {
VpnWidget {}
}
onItemChanged: {
root.vpnBuiltinInstance = item
}
Connections {
target: SettingsData
function onControlCenterWidgetsChanged() {
const widgets = SettingsData.controlCenterWidgets || []
const hasVpnWidget = widgets.some(w => w.id === "builtin_vpn")
if (!hasVpnWidget && vpnLoader.active) {
console.log("VpnWidget: No VPN widget in control center, deactivating loader")
vpnLoader.active = false
}
}
}
}
readonly property var coreWidgetDefinitions: [{
"id": "nightMode", "id": "nightMode",
"text": "Night Mode", "text": "Night Mode",
"description": "Blue light filter", "description": "Blue light filter",
@@ -15,32 +40,28 @@ QtObject {
"type": "toggle", "type": "toggle",
"enabled": DisplayService.automationAvailable, "enabled": DisplayService.automationAvailable,
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined "warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
}, }, {
{
"id": "darkMode", "id": "darkMode",
"text": "Dark Mode", "text": "Dark Mode",
"description": "System theme toggle", "description": "System theme toggle",
"icon": "contrast", "icon": "contrast",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, }, {
{
"id": "doNotDisturb", "id": "doNotDisturb",
"text": "Do Not Disturb", "text": "Do Not Disturb",
"description": "Block notifications", "description": "Block notifications",
"icon": "do_not_disturb_on", "icon": "do_not_disturb_on",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, }, {
{
"id": "idleInhibitor", "id": "idleInhibitor",
"text": "Keep Awake", "text": "Keep Awake",
"description": "Prevent screen timeout", "description": "Prevent screen timeout",
"icon": "motion_sensor_active", "icon": "motion_sensor_active",
"type": "toggle", "type": "toggle",
"enabled": true "enabled": true
}, }, {
{
"id": "wifi", "id": "wifi",
"text": "Network", "text": "Network",
"description": "Wi-Fi and Ethernet connection", "description": "Wi-Fi and Ethernet connection",
@@ -48,8 +69,7 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": NetworkService.wifiAvailable, "enabled": NetworkService.wifiAvailable,
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined "warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
}, }, {
{
"id": "bluetooth", "id": "bluetooth",
"text": "Bluetooth", "text": "Bluetooth",
"description": "Device connections", "description": "Device connections",
@@ -57,57 +77,51 @@ QtObject {
"type": "connection", "type": "connection",
"enabled": BluetoothService.available, "enabled": BluetoothService.available,
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined "warning": !BluetoothService.available ? "Bluetooth not available" : undefined
}, }, {
{
"id": "audioOutput", "id": "audioOutput",
"text": "Audio Output", "text": "Audio Output",
"description": "Speaker settings", "description": "Speaker settings",
"icon": "volume_up", "icon": "volume_up",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, }, {
{
"id": "audioInput", "id": "audioInput",
"text": "Audio Input", "text": "Audio Input",
"description": "Microphone settings", "description": "Microphone settings",
"icon": "mic", "icon": "mic",
"type": "connection", "type": "connection",
"enabled": true "enabled": true
}, }, {
{
"id": "volumeSlider", "id": "volumeSlider",
"text": "Volume Slider", "text": "Volume Slider",
"description": "Audio volume control", "description": "Audio volume control",
"icon": "volume_up", "icon": "volume_up",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, }, {
{
"id": "brightnessSlider", "id": "brightnessSlider",
"text": "Brightness Slider", "text": "Brightness Slider",
"description": "Display brightness control", "description": "Display brightness control",
"icon": "brightness_6", "icon": "brightness_6",
"type": "slider", "type": "slider",
"enabled": DisplayService.brightnessAvailable, "enabled": DisplayService.brightnessAvailable,
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined "warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined,
}, "allowMultiple": true
{ }, {
"id": "inputVolumeSlider", "id": "inputVolumeSlider",
"text": "Input Volume Slider", "text": "Input Volume Slider",
"description": "Microphone volume control", "description": "Microphone volume control",
"icon": "mic", "icon": "mic",
"type": "slider", "type": "slider",
"enabled": true "enabled": true
}, }, {
{
"id": "battery", "id": "battery",
"text": "Battery", "text": "Battery",
"description": "Battery and power management", "description": "Battery and power management",
"icon": "battery_std", "icon": "battery_std",
"type": "action", "type": "action",
"enabled": true "enabled": true
}, }, {
{
"id": "diskUsage", "id": "diskUsage",
"text": "Disk Usage", "text": "Disk Usage",
"description": "Filesystem usage monitoring", "description": "Filesystem usage monitoring",
@@ -116,8 +130,68 @@ QtObject {
"enabled": DgopService.dgopAvailable, "enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined, "warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
"allowMultiple": true "allowMultiple": true
}, {
"id": "colorPicker",
"text": "Color Picker",
"description": "Choose colors from palette",
"icon": "palette",
"type": "action",
"enabled": true
}, {
"id": "builtin_vpn",
"text": "VPN",
"description": "VPN connections",
"icon": "vpn_key",
"type": "builtin_plugin",
"enabled": VpnService.available,
"warning": !VpnService.available ? "VPN not available" : undefined,
"isBuiltinPlugin": true
}]
function getPluginWidgets() {
const plugins = []
const loadedPlugins = PluginService.getLoadedPlugins()
for (var i = 0; i < loadedPlugins.length; i++) {
const plugin = loadedPlugins[i]
if (plugin.type === "daemon") {
continue
} }
]
const pluginComponent = PluginService.pluginWidgetComponents[plugin.id]
if (!pluginComponent) {
continue
}
const tempInstance = pluginComponent.createObject(null)
if (!tempInstance) {
continue
}
const hasCCWidget = tempInstance.ccWidgetIcon && tempInstance.ccWidgetIcon.length > 0
tempInstance.destroy()
if (!hasCCWidget) {
continue
}
plugins.push({
"id": "plugin_" + plugin.id,
"pluginId": plugin.id,
"text": plugin.name || "Plugin",
"description": plugin.description || "",
"icon": plugin.icon || "extension",
"type": "plugin",
"enabled": true,
"isPlugin": true
})
}
return plugins
}
readonly property var baseWidgetDefinitions: coreWidgetDefinitions
function getWidgetForId(widgetId) { function getWidgetForId(widgetId) {
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId) return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId)

View File

@@ -10,6 +10,12 @@ import qs.Widgets
PanelWindow { PanelWindow {
id: root id: root
readonly property string powerOptionsText: I18n.tr("Power Options")
readonly property string logOutText: I18n.tr("Log Out")
readonly property string suspendText: I18n.tr("Suspend")
readonly property string rebootText: I18n.tr("Reboot")
readonly property string powerOffText: I18n.tr("Power Off")
property bool powerMenuVisible: false property bool powerMenuVisible: false
signal powerActionRequested(string action, string title, string message) signal powerActionRequested(string action, string title, string message)
@@ -44,7 +50,7 @@ PanelWindow {
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08) Theme.outline.b, 0.08)
border.width: 1 border.width: 0
opacity: powerMenuVisible ? 1 : 0 opacity: powerMenuVisible ? 1 : 0
scale: powerMenuVisible ? 1 : 0.85 scale: powerMenuVisible ? 1 : 0.85
@@ -65,7 +71,7 @@ PanelWindow {
width: parent.width width: parent.width
StyledText { StyledText {
text: "Power Options" text: root.powerOptionsText
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -118,7 +124,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: "Log Out" text: root.logOutText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -168,7 +174,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: "Suspend" text: root.suspendText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -218,7 +224,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: "Reboot" text: root.rebootText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -268,7 +274,7 @@ PanelWindow {
} }
StyledText { StyledText {
text: "Power Off" text: root.powerOffText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -20,11 +20,7 @@ Row {
height: Theme.iconSize + Theme.spacingS * 2 height: Theme.iconSize + Theme.spacingS * 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
MouseArea { MouseArea {
id: iconArea id: iconArea
@@ -34,7 +30,9 @@ Row {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (defaultSink) { if (defaultSink) {
AudioService.suppressOSD = true
defaultSink.audio.muted = !defaultSink.audio.muted defaultSink.audio.muted = !defaultSink.audio.muted
AudioService.suppressOSD = false
} }
} }
} }
@@ -70,7 +68,7 @@ Row {
unit: "%" unit: "%"
valueOverride: actualVolumePercent valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60) trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
onSliderValueChanged: function(newValue) { onSliderValueChanged: function(newValue) {
if (defaultSink) { if (defaultSink) {
defaultSink.audio.volume = newValue / 100.0 defaultSink.audio.volume = newValue / 100.0

View File

@@ -30,9 +30,6 @@ CompoundPill {
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) { if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
return "bluetooth_disabled" return "bluetooth_disabled"
} }
if (primaryDevice) {
return BluetoothService.getDeviceIcon(primaryDevice)
}
return "bluetooth" return "bluetooth"
} }

View File

@@ -8,9 +8,49 @@ import qs.Widgets
Row { Row {
id: root id: root
property string deviceName: ""
property string instanceId: ""
signal iconClicked()
height: 40 height: 40
spacing: 0 spacing: 0
property string targetDeviceName: {
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
return ""
}
if (deviceName && deviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === deviceName)
return found ? found.name : ""
}
const currentDeviceName = DisplayService.currentDevice
if (currentDeviceName) {
const found = DisplayService.devices.find(dev => dev.name === currentDeviceName)
return found ? found.name : ""
}
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
}
property var targetDevice: {
if (!targetDeviceName || !DisplayService.devices) {
return null
}
return DisplayService.devices.find(dev => dev.name === targetDeviceName) || null
}
property real targetBrightness: {
if (!targetDeviceName) {
return 0
}
return DisplayService.getDeviceBrightness(targetDeviceName)
}
Rectangle { Rectangle {
width: Theme.iconSize + Theme.spacingS * 2 width: Theme.iconSize + Theme.spacingS * 2
height: Theme.iconSize + Theme.spacingS * 2 height: Theme.iconSize + Theme.spacingS * 2
@@ -18,41 +58,56 @@ Row {
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse color: iconArea.containsMouse
? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
: "transparent" : Theme.withAlpha(Theme.primary, 0)
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: DisplayService.devices && DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: function(event) { onClicked: {
if (DisplayService.devices.length > 1) { if (DisplayService.devices && DisplayService.devices.length > 1) {
if (deviceMenu.visible) { root.iconClicked()
deviceMenu.close()
} else {
deviceMenu.popup(iconArea, 0, iconArea.height + Theme.spacingXS)
} }
event.accepted = true
} }
onEntered: {
tooltipLoader.active = true
if (tooltipLoader.item) {
const tooltipText = targetDevice ? "bl device: " + targetDevice.name : "Backlight Control"
const p = iconArea.mapToItem(null, iconArea.width / 2, 0)
tooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
}
}
onExited: {
if (tooltipLoader.item) {
tooltipLoader.item.hide()
}
tooltipLoader.active = false
} }
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: { name: {
if (!DisplayService.brightnessAvailable) return "brightness_low" if (!DisplayService.brightnessAvailable || !targetDevice) {
return "brightness_low"
}
let brightness = DisplayService.brightnessLevel if (targetDevice.class === "backlight" || targetDevice.class === "ddc") {
const brightness = targetBrightness
if (brightness <= 33) return "brightness_low" if (brightness <= 33) return "brightness_low"
if (brightness <= 66) return "brightness_medium" if (brightness <= 66) return "brightness_medium"
return "brightness_high" return "brightness_high"
} else if (targetDevice.name.includes("kbd")) {
return "keyboard"
} else {
return "lightbulb"
}
} }
size: Theme.iconSize size: Theme.iconSize
color: DisplayService.brightnessAvailable && DisplayService.brightnessLevel > 0 ? Theme.primary : Theme.surfaceText color: DisplayService.brightnessAvailable && targetDevice && targetBrightness > 0 ? Theme.primary : Theme.surfaceText
} }
} }
} }
@@ -60,85 +115,22 @@ Row {
DankSlider { DankSlider {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.width - (Theme.iconSize + Theme.spacingS * 2) width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
enabled: DisplayService.brightnessAvailable enabled: DisplayService.brightnessAvailable && targetDeviceName.length > 0
minimum: 1 minimum: 1
maximum: 100 maximum: 100
value: { value: targetBrightness
let level = DisplayService.brightnessLevel
if (level > 100) {
let deviceInfo = DisplayService.getCurrentDeviceInfo()
if (deviceInfo && deviceInfo.max > 0) {
return Math.round((level / deviceInfo.max) * 100)
}
return 50
}
return level
}
onSliderValueChanged: function(newValue) { onSliderValueChanged: function(newValue) {
if (DisplayService.brightnessAvailable) { if (DisplayService.brightnessAvailable && targetDeviceName) {
DisplayService.setBrightness(newValue) DisplayService.setBrightness(newValue, targetDeviceName)
} }
} }
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60) trackColor: Theme.surfaceContainerHigh
} }
Menu { Loader {
id: deviceMenu id: tooltipLoader
width: 200 active: false
closePolicy: Popup.CloseOnEscape sourceComponent: DankTooltip {}
background: Rectangle {
color: Theme.popupBackground()
radius: Theme.cornerRadius
border.width: 1
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
Instantiator {
model: DisplayService.devices
delegate: MenuItem {
required property var modelData
required property int index
property string deviceName: modelData.name || ""
property string deviceClass: modelData.class || ""
text: deviceName
font.pixelSize: Theme.fontSizeMedium
height: 40
indicator: Rectangle {
visible: DisplayService.currentDevice === parent.deviceName
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingS
width: 4
height: parent.height - Theme.spacingS * 2
radius: 2
color: Theme.primary
}
contentItem: StyledText {
text: parent.text
font: parent.font
color: DisplayService.currentDevice === parent.deviceName ? Theme.primary : Theme.surfaceText
leftPadding: Theme.spacingL
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2
}
onTriggered: {
DisplayService.setCurrentDevice(deviceName, true)
deviceMenu.close()
}
}
onObjectAdded: (index, object) => deviceMenu.insertItem(index, object)
onObjectRemoved: (index, object) => deviceMenu.removeItem(object)
}
} }
} }

View File

@@ -0,0 +1,33 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
property var colorPickerModal: null
isActive: true
iconName: "palette"
iconColor: Theme.primary
primaryText: "Color Picker"
secondaryText: "Choose a color"
onToggled: {
console.log("ColorPickerPill toggled, modal:", colorPickerModal)
if (colorPickerModal) {
colorPickerModal.show()
}
}
onExpandClicked: {
console.log("ColorPickerPill expandClicked, modal:", colorPickerModal)
if (colorPickerModal) {
colorPickerModal.show()
}
}
}

View File

@@ -20,9 +20,9 @@ Rectangle {
width: parent ? parent.width : 200 width: parent ? parent.width : 200
height: 60 height: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6) color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 0
opacity: enabled ? 1.0 : 0.6 opacity: enabled ? 1.0 : 0.6
Row { Row {

View File

@@ -27,20 +27,21 @@ Rectangle {
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor) return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
} }
readonly property color _containerBg: readonly property color _containerBg: Theme.surfaceContainerHigh
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha() * 0.60)
color: _containerBg color: {
const baseColor = bodyMouse.containsMouse ? Theme.widgetBaseHoverColor : _containerBg
return baseColor
}
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.10) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.10)
border.width: 1 border.width: 0
antialiasing: true antialiasing: true
readonly property color _labelPrimary: Theme.surfaceText readonly property color _labelPrimary: Theme.surfaceText
readonly property color _labelSecondary: Theme.surfaceVariantText readonly property color _labelSecondary: Theme.surfaceVariantText
readonly property color _tileBgActive: Theme.primary readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive: { readonly property color _tileBgInactive: {
const transparency = Theme.popupTransparency || 0.92 const transparency = Theme.popupTransparency
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1) const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
return Qt.rgba(surface.r, surface.g, surface.b, transparency) return Qt.rgba(surface.r, surface.g, surface.b, transparency)
} }
@@ -48,7 +49,7 @@ Rectangle {
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileRingInactive: readonly property color _tileRingInactive:
Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18) Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18)
readonly property color _tileIconActive: Theme.primaryContainer readonly property color _tileIconActive: Theme.primaryText
readonly property color _tileIconInactive: Theme.primary readonly property color _tileIconInactive: Theme.primary
property int _padH: Theme.spacingS property int _padH: Theme.spacingS

View File

@@ -0,0 +1,51 @@
import QtQuick
import qs.Common
import qs.Widgets
StyledRect {
id: root
property string primaryMessage: ""
property string secondaryMessage: ""
radius: Theme.cornerRadius
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.1)
border.color: Theme.warning
border.width: 1
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
DankIcon {
name: "warning"
size: 16
color: Theme.warning
anchors.top: parent.top
anchors.topMargin: 2
}
Column {
width: parent.width - 16 - parent.spacing
spacing: Theme.spacingXS
StyledText {
width: parent.width
text: root.primaryMessage
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
font.weight: Font.Medium
wrapMode: Text.WordWrap
}
StyledText {
width: parent.width
text: root.secondaryMessage
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
visible: text.length > 0
}
}
}
}

View File

@@ -20,11 +20,7 @@ Row {
height: Theme.iconSize + Theme.spacingS * 2 height: Theme.iconSize + Theme.spacingS * 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
MouseArea { MouseArea {
id: iconArea id: iconArea
@@ -34,7 +30,9 @@ Row {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (defaultSource) { if (defaultSource) {
AudioService.suppressOSD = true
defaultSource.audio.muted = !defaultSource.audio.muted defaultSource.audio.muted = !defaultSource.audio.muted
AudioService.suppressOSD = false
} }
} }
} }
@@ -68,7 +66,10 @@ Row {
unit: "%" unit: "%"
valueOverride: actualVolumePercent valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceContainer thumbOutlineColor: Theme.surfaceContainer
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60) trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.surfaceContainerHigh
onIsDraggingChanged: {
AudioService.suppressOSD = isDragging
}
onSliderValueChanged: function(newValue) { onSliderValueChanged: function(newValue) {
if (defaultSource) { if (defaultSource) {
defaultSource.audio.volume = newValue / 100.0 defaultSource.audio.volume = newValue / 100.0

View File

@@ -1,78 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
isActive: {
if (NetworkService.wifiToggling) {
return false
}
if (NetworkService.networkStatus === "ethernet") {
return true
}
if (NetworkService.networkStatus === "wifi") {
return true
}
return NetworkService.wifiEnabled
}
iconName: {
if (NetworkService.wifiToggling) {
return "sync"
}
if (NetworkService.networkStatus === "ethernet") {
return "settings_ethernet"
}
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalIcon
}
if (NetworkService.wifiEnabled) {
return "wifi_off"
}
return "wifi_off"
}
primaryText: {
if (NetworkService.wifiToggling) {
return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
}
if (NetworkService.networkStatus === "ethernet") {
return "Ethernet"
}
if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID) {
return NetworkService.currentWifiSSID
}
if (NetworkService.wifiEnabled) {
return "Not connected"
}
return "WiFi off"
}
secondaryText: {
if (NetworkService.wifiToggling) {
return "Please wait..."
}
if (NetworkService.networkStatus === "ethernet") {
return "Connected"
}
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
}
if (NetworkService.wifiEnabled) {
return "Select network"
}
return ""
}
onToggled: {
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
NetworkService.toggleWifiRadio()
}
}
}

View File

@@ -25,15 +25,17 @@ Rectangle {
} }
readonly property color _tileBgActive: Theme.primary readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive: readonly property color _tileBgInactive: Theme.surfaceContainerHigh
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha() * 0.60)
readonly property color _tileRingActive: readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryContainer readonly property color _tileIconActive: Theme.primaryText
readonly property color _tileIconInactive: Theme.primary readonly property color _tileIconInactive: Theme.primary
color: isActive ? _tileBgActive : _tileBgInactive color: {
if (isActive) return _tileBgActive
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : _tileBgInactive
return baseColor
}
border.color: isActive ? _tileRingActive : "transparent" border.color: isActive ? _tileRingActive : "transparent"
border.width: isActive ? 1 : 0 border.width: isActive ? 1 : 0
antialiasing: true antialiasing: true
@@ -89,13 +91,6 @@ Rectangle {
onClicked: root.clicked() onClicked: root.clicked()
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on radius { Behavior on radius {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration

View File

@@ -12,6 +12,7 @@ Rectangle {
property real iconRotation: 0 property real iconRotation: 0
signal clicked() signal clicked()
signal iconRotationCompleted()
width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48 width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48
height: 48 height: 48
@@ -26,15 +27,17 @@ Rectangle {
} }
readonly property color _tileBgActive: Theme.primary readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive: readonly property color _tileBgInactive: Theme.surfaceContainerHigh
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha() * 0.60)
readonly property color _tileRingActive: readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryContainer readonly property color _tileIconActive: Theme.primaryText
readonly property color _tileIconInactive: Theme.primary readonly property color _tileIconInactive: Theme.primary
color: isActive ? _tileBgActive : _tileBgInactive color: {
if (isActive) return _tileBgActive
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : _tileBgInactive
return baseColor
}
border.color: isActive ? _tileRingActive : "transparent" border.color: isActive ? _tileRingActive : "transparent"
border.width: isActive ? 1 : 0 border.width: isActive ? 1 : 0
antialiasing: true antialiasing: true
@@ -56,6 +59,7 @@ Rectangle {
size: Theme.iconSize size: Theme.iconSize
color: isActive ? _tileIconActive : _tileIconInactive color: isActive ? _tileIconActive : _tileIconInactive
rotation: iconRotation rotation: iconRotation
onRotationCompleted: root.iconRotationCompleted()
} }
MouseArea { MouseArea {
@@ -67,13 +71,6 @@ Rectangle {
onClicked: root.clicked() onClicked: root.clicked()
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on radius { Behavior on radius {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration

View File

@@ -14,6 +14,7 @@ Rectangle {
property real iconRotation: 0 property real iconRotation: 0
signal clicked() signal clicked()
signal iconRotationCompleted()
width: parent ? parent.width : 200 width: parent ? parent.width : 200
height: 60 height: 60
@@ -23,15 +24,17 @@ Rectangle {
} }
readonly property color _tileBgActive: Theme.primary readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive: readonly property color _tileBgInactive: Theme.surfaceContainerHigh
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha() * 0.60)
readonly property color _tileRingActive: readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22) Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
color: isActive ? _tileBgActive : _tileBgInactive color: {
if (isActive) return _tileBgActive
const baseColor = mouseArea.containsMouse ? Theme.widgetBaseHoverColor : _tileBgInactive
return baseColor
}
border.color: isActive ? _tileRingActive : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: isActive ? _tileRingActive : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: isActive ? 1 : 1 border.width: 0
opacity: enabled ? 1.0 : 0.6 opacity: enabled ? 1.0 : 0.6
function hoverTint(base) { function hoverTint(base) {
@@ -39,14 +42,12 @@ Rectangle {
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor) return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
} }
readonly property color _containerBg: readonly property color _containerBg: Theme.surfaceContainerHigh
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha() * 0.60)
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: mouseArea.containsMouse ? hoverTint(_containerBg) : "transparent" color: mouseArea.containsMouse ? hoverTint(_containerBg) : Theme.withAlpha(_containerBg, 0)
opacity: mouseArea.containsMouse ? 0.08 : 0.0 opacity: mouseArea.containsMouse ? 0.08 : 0.0
Behavior on opacity { Behavior on opacity {
@@ -63,9 +64,10 @@ Rectangle {
DankIcon { DankIcon {
name: root.iconName name: root.iconName
size: Theme.iconSize size: Theme.iconSize
color: isActive ? Theme.primaryContainer : Theme.primary color: isActive ? Theme.primaryText : Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
rotation: root.iconRotation rotation: root.iconRotation
onRotationCompleted: root.iconRotationCompleted()
} }
Item { Item {
@@ -82,7 +84,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.text text: root.text
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: isActive ? Theme.primaryContainer : Theme.surfaceText color: isActive ? Theme.primaryText : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
@@ -92,7 +94,7 @@ Rectangle {
width: parent.width width: parent.width
text: root.secondaryText text: root.secondaryText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText color: isActive ? Theme.primaryText : Theme.surfaceVariantText
visible: text.length > 0 visible: text.length > 0
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
@@ -110,13 +112,6 @@ Rectangle {
onClicked: root.clicked() onClicked: root.clicked()
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on radius { Behavior on radius {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration

View File

@@ -15,6 +15,11 @@ function addWidget(widgetId) {
widget.mountPath = "/" widget.mountPath = "/"
} }
if (widgetId === "brightnessSlider") {
widget.instanceId = generateUniqueId()
widget.deviceName = ""
}
widgets.push(widget) widgets.push(widget)
SettingsData.setControlCenterWidgets(widgets) SettingsData.setControlCenterWidgets(widgets)
} }

View File

@@ -0,0 +1,179 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
Item {
id: root
required property var barWindow
required property var axis
required property var appDrawerLoader
required property var dankDashPopoutLoader
required property var processListPopoutLoader
required property var notificationCenterLoader
required property var batteryPopoutLoader
required property var vpnPopoutLoader
required property var controlCenterLoader
required property var clipboardHistoryModalPopup
required property var systemUpdateLoader
required property var notepadInstance
property alias reveal: core.reveal
property alias autoHide: core.autoHide
property alias backgroundTransparency: core.backgroundTransparency
property alias hasActivePopout: core.hasActivePopout
property alias mouseArea: topBarMouseArea
Item {
id: inputMask
readonly property int barThickness: barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing)
readonly property bool showing: SettingsData.dankBarVisible && (core.reveal
|| (CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview)
|| !core.autoHide)
readonly property int maskThickness: showing ? barThickness : 1
x: {
if (!axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Left: return 0
case SettingsData.Position.Right: return parent.width - maskThickness
default: return 0
}
}
}
y: {
if (axis.isVertical) {
return 0
} else {
switch (SettingsData.dankBarPosition) {
case SettingsData.Position.Top: return 0
case SettingsData.Position.Bottom: return parent.height - maskThickness
default: return 0
}
}
}
width: axis.isVertical ? maskThickness : parent.width
height: axis.isVertical ? parent.height : maskThickness
}
Region {
id: mask
item: inputMask
}
property alias maskRegion: mask
QtObject {
id: core
property real backgroundTransparency: SettingsData.dankBarTransparency
property bool autoHide: SettingsData.dankBarAutoHide
property bool revealSticky: false
property bool notepadInstanceVisible: notepadInstance?.isVisible ?? false
readonly property bool hasActivePopout: {
const loaders = [{
"loader": appDrawerLoader,
"prop": "shouldBeVisible"
}, {
"loader": dankDashPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": processListPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": notificationCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": batteryPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": vpnPopoutLoader,
"prop": "shouldBeVisible"
}, {
"loader": controlCenterLoader,
"prop": "shouldBeVisible"
}, {
"loader": clipboardHistoryModalPopup,
"prop": "visible"
}, {
"loader": systemUpdateLoader,
"prop": "shouldBeVisible"
}]
return notepadInstanceVisible || loaders.some(item => {
if (item.loader) {
return item.loader?.item?.[item.prop]
}
return false
})
}
property bool reveal: {
if (CompositorService.isNiri && NiriService.inOverview) {
return SettingsData.dankBarOpenOnOverview
}
return SettingsData.dankBarVisible && (!autoHide || topBarMouseArea.containsMouse || hasActivePopout || revealSticky)
}
onHasActivePopoutChanged: {
if (!hasActivePopout && autoHide && !topBarMouseArea.containsMouse) {
revealSticky = true
revealHold.restart()
}
}
}
Timer {
id: revealHold
interval: 250
repeat: false
onTriggered: core.revealSticky = false
}
Connections {
function onDankBarTransparencyChanged() {
core.backgroundTransparency = SettingsData.dankBarTransparency
}
target: SettingsData
}
Connections {
target: topBarMouseArea
function onContainsMouseChanged() {
if (topBarMouseArea.containsMouse) {
core.revealSticky = true
revealHold.stop()
} else {
if (core.autoHide && !core.hasActivePopout) {
revealHold.restart()
}
}
}
}
MouseArea {
id: topBarMouseArea
y: !barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? parent.height - height : 0) : 0
x: barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.width - width : 0) : 0
height: !barWindow.isVertical ? barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
width: barWindow.isVertical ? barWindow.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined
anchors {
left: !barWindow.isVertical ? parent.left : (SettingsData.dankBarPosition === SettingsData.Position.Left ? parent.left : undefined)
right: !barWindow.isVertical ? parent.right : (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.right : undefined)
top: barWindow.isVertical ? parent.top : undefined
bottom: barWindow.isVertical ? parent.bottom : undefined
}
hoverEnabled: SettingsData.dankBarAutoHide && !core.reveal
acceptedButtons: Qt.NoButton
enabled: SettingsData.dankBarAutoHide && !core.reveal
}
}

View File

@@ -0,0 +1,57 @@
import QtQuick
QtObject {
id: root
property string edge: "top"
readonly property string orientation: isVertical ? "vertical" : "horizontal"
readonly property bool isVertical: edge === "left" || edge === "right"
readonly property bool isHorizontal: !isVertical
function primarySize(item) {
return isVertical ? item.height : item.width
}
function crossSize(item) {
return isVertical ? item.width : item.height
}
function setPrimaryPos(item, value) {
if (isVertical) {
item.y = value
} else {
item.x = value
}
}
function getPrimaryPos(item) {
return isVertical ? item.y : item.x
}
function primaryAnchor(anchors) {
return isVertical ? anchors.verticalCenter : anchors.horizontalCenter
}
function crossAnchor(anchors) {
return isVertical ? anchors.horizontalCenter : anchors.verticalCenter
}
function outerVisualEdge() {
if (edge === "bottom") return "bottom"
if (edge === "left") return "right"
if (edge === "right") return "left"
if (edge === "top") return "top"
return "bottom"
}
signal axisEdgeChanged()
signal axisOrientationChanged()
signal changed() // Single coalesced signal
onEdgeChanged: {
axisEdgeChanged()
axisOrientationChanged()
changed()
}
}

View File

@@ -0,0 +1,372 @@
import QtQuick
import Quickshell.Hyprland
import qs.Common
import qs.Services
Item {
id: root
required property var barWindow
required property var axis
anchors.fill: parent
anchors.left: parent.left
anchors.top: parent.top
anchors.leftMargin: -(SettingsData.dankBarGothCornersEnabled && axis.isVertical && axis.edge === "right" ? barWindow._wingR : 0)
anchors.rightMargin: -(SettingsData.dankBarGothCornersEnabled && axis.isVertical && axis.edge === "left" ? barWindow._wingR : 0)
anchors.topMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "bottom" ? barWindow._wingR : 0)
anchors.bottomMargin: -(SettingsData.dankBarGothCornersEnabled && !axis.isVertical && axis.edge === "top" ? barWindow._wingR : 0)
readonly property real dpr: {
if (CompositorService.isNiri && barWindow.screen) {
const niriScale = NiriService.displayScales[barWindow.screen.name]
if (niriScale !== undefined) return niriScale
}
if (CompositorService.isHyprland && barWindow.screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === barWindow.screen.name)
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return barWindow.screen?.devicePixelRatio || 1
}
function requestRepaint() {
debounceTimer.restart()
}
Timer {
id: debounceTimer
interval: 50
repeat: false
onTriggered: {
barShape.requestPaint()
barTint.requestPaint()
barBorder.requestPaint()
}
}
Canvas {
id: barShape
anchors.fill: parent
antialiasing: true
renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative
readonly property real correctWidth: Theme.px(root.width, dpr)
readonly property real correctHeight: Theme.px(root.height, dpr)
canvasSize: Qt.size(correctWidth, correctHeight)
property real wing: SettingsData.dankBarGothCornersEnabled ? Theme.px(barWindow._wingR, dpr) : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr)
onWingChanged: root.requestRepaint()
onRtChanged: root.requestRepaint()
onCorrectWidthChanged: root.requestRepaint()
onCorrectHeightChanged: root.requestRepaint()
onVisibleChanged: if (visible) root.requestRepaint()
Component.onCompleted: root.requestRepaint()
Connections {
target: root
function onDprChanged() { root.requestRepaint() }
}
Connections {
target: barWindow
function on_BgColorChanged() { root.requestRepaint() }
}
Connections {
target: Theme
function onIsLightModeChanged() { root.requestRepaint() }
function onSurfaceContainerChanged() { root.requestRepaint() }
}
onPaint: {
const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const R = wing
const RT = rt
const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
function drawTopPath() {
ctx.beginPath()
ctx.moveTo(RT, 0)
ctx.lineTo(W - RT, 0)
ctx.arcTo(W, 0, W, RT, RT)
ctx.lineTo(W, H)
if (R > 0) {
ctx.lineTo(W, H + R)
ctx.arc(W - R, H + R, R, 0, -Math.PI / 2, true)
ctx.lineTo(R, H)
ctx.arc(R, H + R, R, -Math.PI / 2, -Math.PI, true)
ctx.lineTo(0, H + R)
} else {
ctx.lineTo(W, H - RT)
ctx.arcTo(W, H, W - RT, H, RT)
ctx.lineTo(RT, H)
ctx.arcTo(0, H, 0, H - RT, RT)
}
ctx.lineTo(0, RT)
ctx.arcTo(0, 0, RT, 0, RT)
ctx.closePath()
}
ctx.reset()
ctx.clearRect(0, 0, W, H_raw)
ctx.save()
if (isBottom) {
ctx.translate(W, H_raw)
ctx.rotate(Math.PI)
} else if (isLeft) {
ctx.translate(0, W)
ctx.rotate(-Math.PI / 2)
} else if (isRight) {
ctx.translate(H_raw, 0)
ctx.rotate(Math.PI / 2)
}
drawTopPath()
ctx.restore()
ctx.fillStyle = barWindow._bgColor
ctx.fill()
}
}
Canvas {
id: barTint
anchors.fill: parent
antialiasing: true
renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative
readonly property real correctWidth: Theme.px(root.width, dpr)
readonly property real correctHeight: Theme.px(root.height, dpr)
canvasSize: Qt.size(correctWidth, correctHeight)
property real wing: SettingsData.dankBarGothCornersEnabled ? Theme.px(barWindow._wingR, dpr) : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr)
property real alphaTint: (barWindow._bgColor?.a ?? 1) < 0.99 ? (Theme.stateLayerOpacity ?? 0) : 0
onWingChanged: root.requestRepaint()
onRtChanged: root.requestRepaint()
onAlphaTintChanged: root.requestRepaint()
onCorrectWidthChanged: root.requestRepaint()
onCorrectHeightChanged: root.requestRepaint()
onVisibleChanged: if (visible) root.requestRepaint()
Component.onCompleted: root.requestRepaint()
Connections {
target: root
function onDprChanged() { root.requestRepaint() }
}
Connections {
target: barWindow
function on_BgColorChanged() { root.requestRepaint() }
}
Connections {
target: Theme
function onIsLightModeChanged() { root.requestRepaint() }
function onSurfaceChanged() { root.requestRepaint() }
}
onPaint: {
const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const R = wing
const RT = rt
const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
function drawTopPath() {
ctx.beginPath()
ctx.moveTo(RT, 0)
ctx.lineTo(W - RT, 0)
ctx.arcTo(W, 0, W, RT, RT)
ctx.lineTo(W, H)
if (R > 0) {
ctx.lineTo(W, H + R)
ctx.arc(W - R, H + R, R, 0, -Math.PI / 2, true)
ctx.lineTo(R, H)
ctx.arc(R, H + R, R, -Math.PI / 2, -Math.PI, true)
ctx.lineTo(0, H + R)
} else {
ctx.lineTo(W, H - RT)
ctx.arcTo(W, H, W - RT, H, RT)
ctx.lineTo(RT, H)
ctx.arcTo(0, H, 0, H - RT, RT)
}
ctx.lineTo(0, RT)
ctx.arcTo(0, 0, RT, 0, RT)
ctx.closePath()
}
ctx.reset()
ctx.clearRect(0, 0, W, H_raw)
ctx.save()
if (isBottom) {
ctx.translate(W, H_raw)
ctx.rotate(Math.PI)
} else if (isLeft) {
ctx.translate(0, W)
ctx.rotate(-Math.PI / 2)
} else if (isRight) {
ctx.translate(H_raw, 0)
ctx.rotate(Math.PI / 2)
}
drawTopPath()
ctx.restore()
ctx.fillStyle = Qt.rgba(Theme.surface.r, Theme.surface.g, Theme.surface.b, alphaTint)
ctx.fill()
}
}
Canvas {
id: barBorder
anchors.fill: parent
antialiasing: false
visible: SettingsData.dankBarBorderEnabled
renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative
readonly property real correctWidth: Theme.px(root.width, dpr)
readonly property real correctHeight: Theme.px(root.height, dpr)
canvasSize: Qt.size(correctWidth, correctHeight)
property real wing: SettingsData.dankBarGothCornersEnabled ? Theme.px(barWindow._wingR, dpr) : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.px(Theme.cornerRadius, dpr)
property bool borderEnabled: SettingsData.dankBarBorderEnabled
onWingChanged: root.requestRepaint()
onRtChanged: root.requestRepaint()
onBorderEnabledChanged: root.requestRepaint()
onCorrectWidthChanged: root.requestRepaint()
onCorrectHeightChanged: root.requestRepaint()
onVisibleChanged: if (visible) root.requestRepaint()
Component.onCompleted: root.requestRepaint()
Connections {
target: root
function onDprChanged() { root.requestRepaint() }
}
Connections {
target: Theme
function onIsLightModeChanged() { root.requestRepaint() }
function onSurfaceTextChanged() { root.requestRepaint() }
function onPrimaryChanged() { root.requestRepaint() }
function onSecondaryChanged() { root.requestRepaint() }
function onOutlineChanged() { root.requestRepaint() }
}
Connections {
target: SettingsData
function onDankBarBorderColorChanged() { root.requestRepaint() }
function onDankBarBorderOpacityChanged() { root.requestRepaint() }
function onDankBarBorderThicknessChanged() { root.requestRepaint() }
function onDankBarSpacingChanged() { root.requestRepaint() }
function onDankBarSquareCornersChanged() { root.requestRepaint() }
function onDankBarTransparencyChanged() { root.requestRepaint() }
}
onPaint: {
if (!borderEnabled) return
const ctx = getContext("2d")
const W = barWindow.isVertical ? correctHeight : correctWidth
const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const R = wing
const RT = rt
const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
const spacing = SettingsData.dankBarSpacing
const hasEdgeGap = spacing > 0 || RT > 0
function drawTopBorder() {
ctx.beginPath()
if (!hasEdgeGap) {
ctx.moveTo(0, H)
ctx.lineTo(W, H)
} else {
ctx.moveTo(RT, 0)
ctx.lineTo(W - RT, 0)
ctx.arcTo(W, 0, W, RT, RT)
ctx.lineTo(W, H)
if (R > 0) {
ctx.lineTo(W, H + R)
ctx.arc(W - R, H + R, R, 0, -Math.PI / 2, true)
ctx.lineTo(R, H)
ctx.arc(R, H + R, R, -Math.PI / 2, -Math.PI, true)
ctx.lineTo(0, H + R)
} else {
ctx.lineTo(W, H - RT)
ctx.arcTo(W, H, W - RT, H, RT)
ctx.lineTo(RT, H)
ctx.arcTo(0, H, 0, H - RT, RT)
}
ctx.lineTo(0, RT)
ctx.arcTo(0, 0, RT, 0, RT)
}
ctx.closePath()
}
ctx.reset()
ctx.clearRect(0, 0, W, H_raw)
ctx.save()
if (isBottom) {
ctx.translate(W, H_raw)
ctx.rotate(Math.PI)
} else if (isLeft) {
ctx.translate(0, W)
ctx.rotate(-Math.PI / 2)
} else if (isRight) {
ctx.translate(H_raw, 0)
ctx.rotate(Math.PI / 2)
}
drawTopBorder()
ctx.restore()
const key = SettingsData.dankBarBorderColor || "surfaceText"
const base = (key === "surfaceText") ? Theme.surfaceText
: (key === "primary") ? Theme.primary
: Theme.secondary
const color = Theme.withAlpha(base, SettingsData.dankBarBorderOpacity ?? 1.0)
const thickness = Math.max(1, SettingsData.dankBarBorderThickness ?? 1)
ctx.globalCompositeOperation = "source-over"
ctx.lineWidth = thickness
ctx.strokeStyle = color
ctx.stroke()
}
}
}

View File

@@ -0,0 +1,460 @@
import QtQuick
import qs.Common
import qs.Services
Item {
id: root
property var widgetsModel: null
property var components: null
property bool noBackground: false
required property var axis
property string section: "center"
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
readonly property bool isVertical: axis?.isVertical ?? false
readonly property real spacing: noBackground ? 2 : Theme.spacingXS
property var centerWidgets: []
property int totalWidgets: 0
property real totalSize: 0
function updateLayout() {
const containerSize = isVertical ? height : width
if (containerSize <= 0 || !visible) {
return
}
centerWidgets = []
totalWidgets = 0
totalSize = 0
let configuredWidgets = 0
for (var i = 0; i < centerRepeater.count; i++) {
const item = centerRepeater.itemAt(i)
if (item && getWidgetVisible(item.widgetId)) {
configuredWidgets++
if (item.active && item.item) {
centerWidgets.push(item.item)
totalWidgets++
totalSize += isVertical ? item.item.height : item.item.width
}
}
}
if (totalWidgets > 1) {
totalSize += spacing * (totalWidgets - 1)
}
positionWidgets(configuredWidgets)
}
function positionWidgets(configuredWidgets) {
if (totalWidgets === 0 || (isVertical ? height : width) <= 0) {
return
}
const parentCenter = (isVertical ? height : width) / 2
const isOdd = configuredWidgets % 2 === 1
centerWidgets.forEach(widget => {
if (isVertical) {
widget.anchors.verticalCenter = undefined
} else {
widget.anchors.horizontalCenter = undefined
}
})
if (isOdd) {
const middleIndex = Math.floor(configuredWidgets / 2)
let currentActiveIndex = 0
let middleWidget = null
for (var i = 0; i < centerRepeater.count; i++) {
const item = centerRepeater.itemAt(i)
if (item && getWidgetVisible(item.widgetId)) {
if (currentActiveIndex === middleIndex && item.active && item.item) {
middleWidget = item.item
break
}
currentActiveIndex++
}
}
if (middleWidget) {
const middleSize = isVertical ? middleWidget.height : middleWidget.width
if (isVertical) {
middleWidget.y = parentCenter - (middleSize / 2)
} else {
middleWidget.x = parentCenter - (middleSize / 2)
}
let leftWidgets = []
let rightWidgets = []
let foundMiddle = false
for (var i = 0; i < centerWidgets.length; i++) {
if (centerWidgets[i] === middleWidget) {
foundMiddle = true
continue
}
if (!foundMiddle) {
leftWidgets.push(centerWidgets[i])
} else {
rightWidgets.push(centerWidgets[i])
}
}
let currentPos = isVertical ? middleWidget.y : middleWidget.x
for (var i = leftWidgets.length - 1; i >= 0; i--) {
const size = isVertical ? leftWidgets[i].height : leftWidgets[i].width
currentPos -= (spacing + size)
if (isVertical) {
leftWidgets[i].y = currentPos
} else {
leftWidgets[i].x = currentPos
}
}
currentPos = (isVertical ? middleWidget.y : middleWidget.x) + middleSize
for (var i = 0; i < rightWidgets.length; i++) {
currentPos += spacing
if (isVertical) {
rightWidgets[i].y = currentPos
} else {
rightWidgets[i].x = currentPos
}
currentPos += isVertical ? rightWidgets[i].height : rightWidgets[i].width
}
}
} else {
let configuredLeftIndex = (configuredWidgets / 2) - 1
let configuredRightIndex = configuredWidgets / 2
const halfSpacing = spacing / 2
let leftWidget = null
let rightWidget = null
let leftWidgets = []
let rightWidgets = []
let currentConfigIndex = 0
for (var i = 0; i < centerRepeater.count; i++) {
const item = centerRepeater.itemAt(i)
if (item && getWidgetVisible(item.widgetId)) {
if (item.active && item.item) {
if (currentConfigIndex < configuredLeftIndex) {
leftWidgets.push(item.item)
} else if (currentConfigIndex === configuredLeftIndex) {
leftWidget = item.item
} else if (currentConfigIndex === configuredRightIndex) {
rightWidget = item.item
} else {
rightWidgets.push(item.item)
}
}
currentConfigIndex++
}
}
if (leftWidget && rightWidget) {
const leftSize = isVertical ? leftWidget.height : leftWidget.width
if (isVertical) {
leftWidget.y = parentCenter - halfSpacing - leftSize
rightWidget.y = parentCenter + halfSpacing
} else {
leftWidget.x = parentCenter - halfSpacing - leftSize
rightWidget.x = parentCenter + halfSpacing
}
let currentPos = isVertical ? leftWidget.y : leftWidget.x
for (var i = leftWidgets.length - 1; i >= 0; i--) {
const size = isVertical ? leftWidgets[i].height : leftWidgets[i].width
currentPos -= (spacing + size)
if (isVertical) {
leftWidgets[i].y = currentPos
} else {
leftWidgets[i].x = currentPos
}
}
currentPos = (isVertical ? rightWidget.y + rightWidget.height : rightWidget.x + rightWidget.width)
for (var i = 0; i < rightWidgets.length; i++) {
currentPos += spacing
if (isVertical) {
rightWidgets[i].y = currentPos
} else {
rightWidgets[i].x = currentPos
}
currentPos += isVertical ? rightWidgets[i].height : rightWidgets[i].width
}
} else if (leftWidget && !rightWidget) {
const leftSize = isVertical ? leftWidget.height : leftWidget.width
if (isVertical) {
leftWidget.y = parentCenter - halfSpacing - leftSize
} else {
leftWidget.x = parentCenter - halfSpacing - leftSize
}
let currentPos = isVertical ? leftWidget.y : leftWidget.x
for (var i = leftWidgets.length - 1; i >= 0; i--) {
const size = isVertical ? leftWidgets[i].height : leftWidgets[i].width
currentPos -= (spacing + size)
if (isVertical) {
leftWidgets[i].y = currentPos
} else {
leftWidgets[i].x = currentPos
}
}
currentPos = (isVertical ? leftWidget.y + leftWidget.height : leftWidget.x + leftWidget.width) + spacing
for (var i = 0; i < rightWidgets.length; i++) {
currentPos += spacing
if (isVertical) {
rightWidgets[i].y = currentPos
} else {
rightWidgets[i].x = currentPos
}
currentPos += isVertical ? rightWidgets[i].height : rightWidgets[i].width
}
} else if (!leftWidget && rightWidget) {
if (isVertical) {
rightWidget.y = parentCenter + halfSpacing
} else {
rightWidget.x = parentCenter + halfSpacing
}
let currentPos = (isVertical ? rightWidget.y : rightWidget.x) - spacing
for (var i = leftWidgets.length - 1; i >= 0; i--) {
const size = isVertical ? leftWidgets[i].height : leftWidgets[i].width
currentPos -= size
if (isVertical) {
leftWidgets[i].y = currentPos
} else {
leftWidgets[i].x = currentPos
}
currentPos -= spacing
}
currentPos = (isVertical ? rightWidget.y + rightWidget.height : rightWidget.x + rightWidget.width)
for (var i = 0; i < rightWidgets.length; i++) {
currentPos += spacing
if (isVertical) {
rightWidgets[i].y = currentPos
} else {
rightWidgets[i].x = currentPos
}
currentPos += isVertical ? rightWidgets[i].height : rightWidgets[i].width
}
} else if (totalWidgets === 1 && centerWidgets[0]) {
const size = isVertical ? centerWidgets[0].height : centerWidgets[0].width
if (isVertical) {
centerWidgets[0].y = parentCenter - (size / 2)
} else {
centerWidgets[0].x = parentCenter - (size / 2)
}
}
}
}
function getWidgetVisible(widgetId) {
const widgetVisibility = {
"cpuUsage": DgopService.dgopAvailable,
"memUsage": DgopService.dgopAvailable,
"cpuTemp": DgopService.dgopAvailable,
"gpuTemp": DgopService.dgopAvailable,
"network_speed_monitor": DgopService.dgopAvailable
}
return widgetVisibility[widgetId] ?? true
}
function getWidgetComponent(widgetId) {
// Build dynamic component map including plugins
let baseMap = {
"launcherButton": "launcherButtonComponent",
"workspaceSwitcher": "workspaceSwitcherComponent",
"focusedWindow": "focusedWindowComponent",
"runningApps": "runningAppsComponent",
"clock": "clockComponent",
"music": "mediaComponent",
"weather": "weatherComponent",
"systemTray": "systemTrayComponent",
"privacyIndicator": "privacyIndicatorComponent",
"clipboard": "clipboardComponent",
"cpuUsage": "cpuUsageComponent",
"memUsage": "memUsageComponent",
"diskUsage": "diskUsageComponent",
"cpuTemp": "cpuTempComponent",
"gpuTemp": "gpuTempComponent",
"notificationButton": "notificationButtonComponent",
"battery": "batteryComponent",
"controlCenterButton": "controlCenterButtonComponent",
"idleInhibitor": "idleInhibitorComponent",
"spacer": "spacerComponent",
"separator": "separatorComponent",
"network_speed_monitor": "networkComponent",
"keyboard_layout_name": "keyboardLayoutNameComponent",
"vpn": "vpnComponent",
"notepadButton": "notepadButtonComponent",
"colorPicker": "colorPickerComponent",
"systemUpdate": "systemUpdateComponent"
}
// For built-in components, get from components property
const componentKey = baseMap[widgetId]
if (componentKey && root.components[componentKey]) {
return root.components[componentKey]
}
// For plugin components, get from PluginService
var parts = widgetId.split(":")
var pluginId = parts[0]
let pluginComponents = PluginService.getWidgetComponents()
return pluginComponents[pluginId] || null
}
height: parent.height
width: parent.width
anchors.centerIn: parent
Timer {
id: layoutTimer
interval: 0
repeat: false
onTriggered: root.updateLayout()
}
Component.onCompleted: {
layoutTimer.restart()
}
onWidthChanged: {
if (width > 0) {
layoutTimer.restart()
}
}
onHeightChanged: {
if (height > 0) {
layoutTimer.restart()
}
}
onVisibleChanged: {
if (visible && (isVertical ? height : width) > 0) {
layoutTimer.restart()
}
}
Repeater {
id: centerRepeater
model: root.widgetsModel
Loader {
property string widgetId: model.widgetId
property var widgetData: model
property int spacerSize: model.size || 20
anchors.verticalCenter: !root.isVertical ? parent.verticalCenter : undefined
anchors.horizontalCenter: root.isVertical ? parent.horizontalCenter : undefined
active: root.getWidgetVisible(model.widgetId) && (model.widgetId !== "music" || MprisController.activePlayer !== null)
sourceComponent: root.getWidgetComponent(model.widgetId)
opacity: (model.enabled !== false) ? 1 : 0
asynchronous: false
onLoaded: {
if (!item) {
return
}
item.widthChanged.connect(() => layoutTimer.restart())
item.heightChanged.connect(() => layoutTimer.restart())
if (model.widgetId === "spacer") {
item.spacerSize = Qt.binding(() => model.size || 20)
}
if (root.axis && "axis" in item) {
item.axis = Qt.binding(() => root.axis)
}
if (root.axis && "isVertical" in item) {
try {
item.isVertical = Qt.binding(() => root.axis.isVertical)
} catch (e) {
}
}
// Inject properties for plugin widgets
if ("section" in item) {
item.section = root.section
}
if ("parentScreen" in item) {
item.parentScreen = Qt.binding(() => root.parentScreen)
}
if ("widgetThickness" in item) {
item.widgetThickness = Qt.binding(() => root.widgetThickness)
}
if ("barThickness" in item) {
item.barThickness = Qt.binding(() => root.barThickness)
}
// Inject PluginService for plugin widgets
if (item.pluginService !== undefined) {
var parts = model.widgetId.split(":")
var pluginId = parts[0]
var variantId = parts.length > 1 ? parts[1] : null
if (item.pluginId !== undefined) {
item.pluginId = pluginId
}
if (item.variantId !== undefined) {
item.variantId = variantId
}
if (item.variantData !== undefined && variantId) {
item.variantData = PluginService.getPluginVariantData(pluginId, variantId)
}
item.pluginService = PluginService
}
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
layoutTimer.restart()
}
onActiveChanged: {
layoutTimer.restart()
}
}
}
Connections {
target: widgetsModel
function onCountChanged() {
layoutTimer.restart()
}
}
// Listen for plugin changes and refresh components
Connections {
target: PluginService
function onPluginLoaded(pluginId) {
// Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i)
if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(item.widgetId)
}
}
}
function onPluginUnloaded(pluginId) {
// Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i)
if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(item.widgetId)
}
}
}
}
}

1146
Modules/DankBar/DankBar.qml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
import QtQuick
import qs.Common
Item {
id: root
property var widgetsModel: null
property var components: null
property bool noBackground: false
required property var axis
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
readonly property bool isVertical: axis?.isVertical ?? false
implicitHeight: layoutLoader.item ? (layoutLoader.item.implicitHeight || layoutLoader.item.height) : 0
implicitWidth: layoutLoader.item ? (layoutLoader.item.implicitWidth || layoutLoader.item.width) : 0
Loader {
id: layoutLoader
anchors.fill: parent
sourceComponent: root.isVertical ? columnComp : rowComp
}
Component {
id: rowComp
Row {
spacing: noBackground ? 2 : Theme.spacingXS
Repeater {
model: root.widgetsModel
Item {
width: widgetLoader.item ? widgetLoader.item.width : 0
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
id: widgetLoader
anchors.verticalCenter: parent.verticalCenter
widgetId: model.widgetId
widgetData: model
spacerSize: model.size || 20
components: root.components
isInColumn: false
axis: root.axis
section: "left"
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
}
}
}
}
}
Component {
id: columnComp
Column {
width: Math.max(parent.width, 200)
spacing: noBackground ? 2 : Theme.spacingXS
Repeater {
model: root.widgetsModel
Item {
width: parent.width
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
id: widgetLoader
anchors.horizontalCenter: parent.horizontalCenter
widgetId: model.widgetId
widgetData: model
spacerSize: model.size || 20
components: root.components
isInColumn: true
axis: root.axis
section: "left"
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
}
}
}
}
}
}

View File

@@ -11,7 +11,6 @@ import qs.Widgets
DankPopout { DankPopout {
id: root id: root
property string triggerSection: "right"
property var triggerScreen: null property var triggerScreen: null
function setTriggerPosition(x, y, width, section, screen) { function setTriggerPosition(x, y, width, section, screen) {
@@ -45,9 +44,9 @@ DankPopout {
popupWidth: 400 popupWidth: 400
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 400 popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 400
triggerX: Screen.width - 380 - Theme.spacingL triggerX: Screen.width - 380 - Theme.spacingL
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingS triggerY: Theme.barHeight - 4 + SettingsData.dankBarSpacing
triggerWidth: 70 triggerWidth: 70
positioning: "center" positioning: ""
screen: triggerScreen screen: triggerScreen
shouldBeVisible: false shouldBeVisible: false
visible: shouldBeVisible visible: shouldBeVisible
@@ -60,7 +59,7 @@ DankPopout {
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Theme.outlineMedium border.color: Theme.outlineMedium
border.width: 1 border.width: 0
antialiasing: true antialiasing: true
smooth: true smooth: true
focus: true focus: true
@@ -96,7 +95,7 @@ DankPopout {
color: "transparent" color: "transparent"
radius: parent.radius + 3 radius: parent.radius + 3
border.color: Qt.rgba(0, 0, 0, 0.05) border.color: Qt.rgba(0, 0, 0, 0.05)
border.width: 1 border.width: 0
z: -3 z: -3
} }
@@ -106,7 +105,7 @@ DankPopout {
color: "transparent" color: "transparent"
radius: parent.radius + 2 radius: parent.radius + 2
border.color: Theme.shadowMedium border.color: Theme.shadowMedium
border.width: 1 border.width: 0
z: -2 z: -2
} }
@@ -114,7 +113,7 @@ DankPopout {
anchors.fill: parent anchors.fill: parent
color: "transparent" color: "transparent"
border.color: Theme.outlineStrong border.color: Theme.outlineStrong
border.width: 1 border.width: 0
radius: parent.radius radius: parent.radius
z: -1 z: -1
} }
@@ -274,7 +273,7 @@ DankPopout {
height: 32 height: 32
radius: 16 radius: 16
color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent" color: closeBatteryArea.containsMouse ? Theme.errorHover : "transparent"
anchors.verticalCenter: parent.verticalCenter anchors.top: parent.top
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -304,16 +303,15 @@ DankPopout {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 64 height: 64
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) border.width: 0
border.width: 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Health" text: I18n.tr("Health")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -340,16 +338,15 @@ DankPopout {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 64 height: 64
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) border.width: 0
border.width: 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: "Capacity" text: I18n.tr("Capacity")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -367,6 +364,210 @@ DankPopout {
} }
} }
// Individual battery details for multiple batteries
Column {
width: parent.width
spacing: Theme.spacingS
visible: !BatteryService.usePreferred && BatteryService.batteries.length > 1
StyledText {
text: I18n.tr("Individual Batteries")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
}
Repeater {
model: BatteryService.batteries
delegate: StyledRect {
required property var modelData
required property int index
width: parent.width
height: batteryColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.width: 0
Column {
id: batteryColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
// Top row: name and percentage
Row {
width: parent.width
spacing: Theme.spacingM
Column {
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
width: parent.width - percentText.width - chargingIcon.width - Theme.spacingM * 2
StyledText {
text: modelData.model || `Battery ${index + 1}`
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.nativePath
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
elide: Text.ElideMiddle
width: parent.width
}
}
Item {
width: 1
height: parent.height
}
StyledText {
id: percentText
text: `${Math.round(100 * modelData.percentage)}%`
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Bold
anchors.verticalCenter: parent.verticalCenter
}
DankIcon {
id: chargingIcon
name: modelData.state === UPowerDeviceState.Charging ? "bolt" : ""
size: Theme.iconSizeSmall
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
visible: modelData.state === UPowerDeviceState.Charging
}
}
// Bottom row: Health, Capacity and Time
Flow {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: (parent.width - Theme.spacingS * 2) / 3
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.width: 0
Column {
anchors.centerIn: parent
spacing: 2
StyledText {
text: I18n.tr("Health")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
return "N/A"
return `${Math.round(modelData.healthPercentage)}%`
}
font.pixelSize: Theme.fontSizeSmall
color: {
if (!modelData.healthSupported || modelData.healthPercentage <= 0)
return Theme.surfaceText
return modelData.healthPercentage < 80 ? Theme.error : Theme.surfaceText
}
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
StyledRect {
width: (parent.width - Theme.spacingS * 2) / 3
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.width: 0
Column {
anchors.centerIn: parent
spacing: 2
StyledText {
text: I18n.tr("Capacity")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: modelData.energyCapacity > 0 ? `${modelData.energyCapacity.toFixed(1)}` : "N/A"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
StyledRect {
width: (parent.width - Theme.spacingS * 2) / 3
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.width: 0
Column {
anchors.centerIn: parent
spacing: 2
StyledText {
text: modelData.state === UPowerDeviceState.Charging
? I18n.tr("To Full")
: modelData.state === UPowerDeviceState.Discharging
? I18n.tr("Left") : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: {
const time = modelData.state === UPowerDeviceState.Charging
? modelData.timeToFull
: modelData.state === UPowerDeviceState.Discharging && BatteryService.changeRate > 0
? (3600 * modelData.energy) / BatteryService.changeRate : 0
if (!time || time <= 0 || time > 86400)
return "N/A"
const hours = Math.floor(time / 3600)
const minutes = Math.floor((time % 3600) / 60)
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
}
}
}
}
DankButtonGroup { DankButtonGroup {
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance] property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
property int currentProfileIndex: { property int currentProfileIndex: {
@@ -390,7 +591,7 @@ DankPopout {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
border.color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3) border.color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
border.width: 1 border.width: 0
visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None
Column { Column {
@@ -418,7 +619,7 @@ DankPopout {
width: parent.width - Theme.iconSize - Theme.spacingM width: parent.width - Theme.iconSize - Theme.spacingM
StyledText { StyledText {
text: "Power Profile Degradation" text: I18n.tr("Power Profile Degradation")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.error color: Theme.error
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -13,7 +13,10 @@ import qs.Widgets
DankPopout { DankPopout {
id: root id: root
property string triggerSection: "right" Ref {
service: VpnService
}
property var triggerScreen: null property var triggerScreen: null
function setTriggerPosition(x, y, width, section, screen) { function setTriggerPosition(x, y, width, section, screen) {
@@ -27,9 +30,9 @@ DankPopout {
popupWidth: 360 popupWidth: 360
popupHeight: Math.min(Screen.height - 100, contentLoader.item ? contentLoader.item.implicitHeight : 260) popupHeight: Math.min(Screen.height - 100, contentLoader.item ? contentLoader.item.implicitHeight : 260)
triggerX: Screen.width - 380 - Theme.spacingL triggerX: Screen.width - 380 - Theme.spacingL
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingS triggerY: Theme.barHeight - 4 + SettingsData.dankBarSpacing
triggerWidth: 70 triggerWidth: 70
positioning: "center" positioning: ""
screen: triggerScreen screen: triggerScreen
shouldBeVisible: false shouldBeVisible: false
visible: shouldBeVisible visible: shouldBeVisible
@@ -42,7 +45,7 @@ DankPopout {
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Theme.outlineMedium border.color: Theme.outlineMedium
border.width: 1 border.width: 0
antialiasing: true antialiasing: true
smooth: true smooth: true
focus: true focus: true
@@ -60,7 +63,7 @@ DankPopout {
color: "transparent" color: "transparent"
radius: parent.radius + 3 radius: parent.radius + 3
border.color: Qt.rgba(0, 0, 0, 0.05) border.color: Qt.rgba(0, 0, 0, 0.05)
border.width: 1 border.width: 0
z: -3 z: -3
} }
@@ -70,7 +73,7 @@ DankPopout {
color: "transparent" color: "transparent"
radius: parent.radius + 2 radius: parent.radius + 2
border.color: Theme.shadowMedium border.color: Theme.shadowMedium
border.width: 1 border.width: 0
z: -2 z: -2
} }
@@ -78,7 +81,7 @@ DankPopout {
anchors.fill: parent anchors.fill: parent
color: "transparent" color: "transparent"
border.color: Theme.outlineStrong border.color: Theme.outlineStrong
border.width: 1 border.width: 0
radius: parent.radius radius: parent.radius
z: -1 z: -1
} }
@@ -97,7 +100,7 @@ DankPopout {
height: 32 height: 32
StyledText { StyledText {
text: "VPN Connections" text: I18n.tr("VPN Connections")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -142,7 +145,7 @@ DankPopout {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, Theme.getContentBackgroundAlpha() * 0.6) color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, Theme.getContentBackgroundAlpha() * 0.6)
border.color: Theme.outlineStrong border.color: Theme.outlineStrong
border.width: 1 border.width: 0
clip: true clip: true
Column { Column {
@@ -193,7 +196,7 @@ DankPopout {
visible: VpnService.connected visible: VpnService.connected
width: 130 width: 130
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
border.width: 1 border.width: 0
border.color: Theme.outlineLight border.color: Theme.outlineLight
Row { Row {
@@ -207,7 +210,7 @@ DankPopout {
} }
StyledText { StyledText {
text: "Disconnect" text: I18n.tr("Disconnect")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -263,14 +266,14 @@ DankPopout {
} }
StyledText { StyledText {
text: "No VPN profiles found" text: I18n.tr("No VPN profiles found")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: "Add a VPN in NetworkManager" text: I18n.tr("Add a VPN in NetworkManager")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -0,0 +1,84 @@
import QtQuick
import qs.Common
Item {
id: root
property var widgetsModel: null
property var components: null
property bool noBackground: false
required property var axis
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
readonly property bool isVertical: axis?.isVertical ?? false
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
Loader {
id: layoutLoader
width: parent.width
height: parent.height
sourceComponent: root.isVertical ? columnComp : rowComp
}
Component {
id: rowComp
Row {
spacing: noBackground ? 2 : Theme.spacingXS
anchors.right: parent ? parent.right : undefined
Repeater {
model: root.widgetsModel
Item {
width: widgetLoader.item ? widgetLoader.item.width : 0
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
id: widgetLoader
anchors.verticalCenter: parent.verticalCenter
widgetId: model.widgetId
widgetData: model
spacerSize: model.size || 20
components: root.components
isInColumn: false
axis: root.axis
section: "right"
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
}
}
}
}
}
Component {
id: columnComp
Column {
width: parent ? parent.width : 0
spacing: noBackground ? 2 : Theme.spacingXS
Repeater {
model: root.widgetsModel
Item {
width: parent.width
height: widgetLoader.item ? widgetLoader.item.height : 0
WidgetHost {
id: widgetLoader
anchors.horizontalCenter: parent.horizontalCenter
widgetId: model.widgetId
widgetData: model
spacerSize: model.size || 20
components: root.components
isInColumn: true
axis: root.axis
section: "right"
parentScreen: root.parentScreen
widgetThickness: root.widgetThickness
barThickness: root.barThickness
}
}
}
}
}
}

View File

@@ -0,0 +1,166 @@
import QtQuick
import Quickshell.Services.Mpris
import qs.Services
Loader {
id: root
property string widgetId: ""
property var widgetData: null
property int spacerSize: 20
property var components: null
property bool isInColumn: false
property var axis: null
property string section: "center"
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
asynchronous: false
active: getWidgetVisible(widgetId, DgopService.dgopAvailable) &&
(widgetId !== "music" || MprisController.activePlayer !== null)
sourceComponent: getWidgetComponent(widgetId, components)
opacity: getWidgetEnabled(widgetData?.enabled) ? 1 : 0
signal contentItemReady(var item)
Binding {
target: root.item
when: root.item && "parentScreen" in root.item
property: "parentScreen"
value: root.parentScreen
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "section" in root.item
property: "section"
value: root.section
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "widgetThickness" in root.item
property: "widgetThickness"
value: root.widgetThickness
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "barThickness" in root.item
property: "barThickness"
value: root.barThickness
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "axis" in root.item
property: "axis"
value: root.axis
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "widgetData" in root.item
property: "widgetData"
value: root.widgetData
restoreMode: Binding.RestoreNone
}
onLoaded: {
if (item) {
contentItemReady(item)
if (axis && "isVertical" in item) {
try {
item.isVertical = axis.isVertical
} catch (e) {
}
}
if (item.pluginService !== undefined) {
var parts = widgetId.split(":")
var pluginId = parts[0]
var variantId = parts.length > 1 ? parts[1] : null
if (item.pluginId !== undefined) {
item.pluginId = pluginId
}
if (item.variantId !== undefined) {
item.variantId = variantId
}
if (item.variantData !== undefined && variantId) {
item.variantData = PluginService.getPluginVariantData(pluginId, variantId)
}
item.pluginService = PluginService
}
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
}
}
}
function getWidgetComponent(widgetId, components) {
const componentMap = {
"launcherButton": components.launcherButtonComponent,
"workspaceSwitcher": components.workspaceSwitcherComponent,
"focusedWindow": components.focusedWindowComponent,
"runningApps": components.runningAppsComponent,
"clock": components.clockComponent,
"music": components.mediaComponent,
"weather": components.weatherComponent,
"systemTray": components.systemTrayComponent,
"privacyIndicator": components.privacyIndicatorComponent,
"clipboard": components.clipboardComponent,
"cpuUsage": components.cpuUsageComponent,
"memUsage": components.memUsageComponent,
"diskUsage": components.diskUsageComponent,
"cpuTemp": components.cpuTempComponent,
"gpuTemp": components.gpuTempComponent,
"notificationButton": components.notificationButtonComponent,
"battery": components.batteryComponent,
"controlCenterButton": components.controlCenterButtonComponent,
"idleInhibitor": components.idleInhibitorComponent,
"spacer": components.spacerComponent,
"separator": components.separatorComponent,
"network_speed_monitor": components.networkComponent,
"keyboard_layout_name": components.keyboardLayoutNameComponent,
"vpn": components.vpnComponent,
"notepadButton": components.notepadButtonComponent,
"colorPicker": components.colorPickerComponent,
"systemUpdate": components.systemUpdateComponent
}
if (componentMap[widgetId]) {
return componentMap[widgetId]
}
var parts = widgetId.split(":")
var pluginId = parts[0]
let pluginMap = PluginService.getWidgetComponents()
return pluginMap[pluginId] || null
}
function getWidgetVisible(widgetId, dgopAvailable) {
const widgetVisibility = {
"cpuUsage": dgopAvailable,
"memUsage": dgopAvailable,
"cpuTemp": dgopAvailable,
"gpuTemp": dgopAvailable,
"network_speed_monitor": dgopAvailable
}
return widgetVisibility[widgetId] ?? true
}
function getWidgetEnabled(enabled) {
return enabled !== false
}
}

View File

@@ -0,0 +1,126 @@
import QtQuick
import Quickshell.Services.UPower
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: battery
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property bool batteryPopupVisible: false
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
signal toggleBatteryPopup()
width: isVertical ? widgetThickness : (batteryContent.implicitWidth + horizontalPadding * 2)
height: isVertical ? (batteryColumn.implicitHeight + horizontalPadding * 2) : widgetThickness
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent";
}
const baseColor = batteryArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
}
visible: true
Column {
id: batteryColumn
visible: battery.isVertical
anchors.centerIn: parent
spacing: 1
DankIcon {
name: BatteryService.getBatteryIcon()
size: Theme.barIconSize(barThickness)
color: {
if (!BatteryService.batteryAvailable) {
return Theme.surfaceText
}
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error
}
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
return Theme.primary
}
return Theme.surfaceText
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: BatteryService.batteryLevel.toString()
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: BatteryService.batteryAvailable
}
}
Row {
id: batteryContent
visible: !battery.isVertical
anchors.centerIn: parent
spacing: SettingsData.dankBarNoBackground ? 1 : 2
DankIcon {
name: BatteryService.getBatteryIcon()
size: Theme.barIconSize(barThickness, -4)
color: {
if (!BatteryService.batteryAvailable) {
return Theme.surfaceText;
}
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
return Theme.error;
}
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
return Theme.primary;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: `${BatteryService.batteryLevel}%`
font.pixelSize: Theme.barTextSize(barThickness)
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable
}
}
MouseArea {
id: batteryArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
if (popupTarget && popupTarget.setTriggerPosition) {
const globalPos = mapToGlobal(0, 0)
const currentScreen = parentScreen || Screen
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width)
popupTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen)
}
toggleBatteryPopup();
}
}
}

View File

@@ -0,0 +1,57 @@
import QtQuick
import qs.Common
import qs.Widgets
Item {
id: root
property bool isActive: false
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "right"
property var clipboardHistoryModal: null
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetThickness / 30))
signal clicked()
width: widgetThickness
height: widgetThickness
MouseArea {
id: clipboardArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
root.clicked()
}
}
Rectangle {
id: clipboardContent
anchors.fill: parent
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.dankBarNoBackground) {
return "transparent"
}
const baseColor = clipboardArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency)
}
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
name: "content_paste"
size: Theme.barIconSize(barThickness)
color: Theme.surfaceText
}
}
}

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